Version 2.19.0-15.0.dev
Merge commit 'd6e78f27afa271c6628d5dafe6894f33fb41600b' into 'dev'
diff --git a/pkg/compiler/lib/src/ir/annotations.dart b/pkg/compiler/lib/src/ir/annotations.dart
index 11c80ec..7594410 100644
--- a/pkg/compiler/lib/src/ir/annotations.dart
+++ b/pkg/compiler/lib/src/ir/annotations.dart
@@ -368,14 +368,15 @@
return null;
}
-List<PragmaAnnotationData> computePragmaAnnotationDataFromIr(ir.Member member) {
+List<PragmaAnnotationData> computePragmaAnnotationDataFromIr(
+ ir.Annotatable node) {
List<PragmaAnnotationData> annotations = [];
- for (ir.Expression metadata in member.annotations) {
+ for (ir.Expression metadata in node.annotations) {
if (metadata is! ir.ConstantExpression) continue;
ir.ConstantExpression constantExpression = metadata;
ir.Constant constant = constantExpression.constant;
assert(constant is! ir.UnevaluatedConstant,
- "Unexpected unevaluated constant on $member: $metadata");
+ "Unexpected unevaluated constant on $node: $metadata");
PragmaAnnotationData? data = _getPragmaAnnotation(constant);
if (data != null) {
annotations.add(data);
diff --git a/pkg/compiler/lib/src/js_backend/annotations.dart b/pkg/compiler/lib/src/js_backend/annotations.dart
index 1879291..048ec24 100644
--- a/pkg/compiler/lib/src/js_backend/annotations.dart
+++ b/pkg/compiler/lib/src/js_backend/annotations.dart
@@ -173,14 +173,23 @@
};
}
+ir.Library _enclosingLibrary(ir.TreeNode node) {
+ while (true) {
+ if (node is ir.Library) return node;
+ if (node is ir.Member) return node.enclosingLibrary;
+ node = node.parent!;
+ }
+}
+
EnumSet<PragmaAnnotation> processMemberAnnotations(
CompilerOptions options,
DiagnosticReporter reporter,
- ir.Member member,
+ ir.Annotatable node,
List<PragmaAnnotationData> pragmaAnnotationData) {
EnumSet<PragmaAnnotation> annotations = EnumSet<PragmaAnnotation>();
- Uri uri = member.enclosingLibrary.importUri;
+ ir.Library library = _enclosingLibrary(node);
+ Uri uri = library.importUri;
bool platformAnnotationsAllowed =
options.testMode || uri.isScheme('dart') || maybeEnableNative(uri);
@@ -195,23 +204,23 @@
if (data.hasOptions) {
reporter.reportErrorMessage(
- computeSourceSpanFromTreeNode(member),
+ computeSourceSpanFromTreeNode(node),
MessageKind.GENERIC,
{'text': "@pragma('$name') annotation does not take options"});
}
if (annotation.forFunctionsOnly) {
- if (member is! ir.Procedure && member is! ir.Constructor) {
+ if (node is! ir.Procedure && node is! ir.Constructor) {
reporter.reportErrorMessage(
- computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, {
+ computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('$name') annotation is only supported "
"for methods and constructors."
});
}
}
if (annotation.forFieldsOnly) {
- if (member is! ir.Field) {
+ if (node is! ir.Field) {
reporter.reportErrorMessage(
- computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, {
+ computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('$name') annotation is only supported "
"for fields."
});
@@ -219,7 +228,7 @@
}
if (annotation.internalOnly && !platformAnnotationsAllowed) {
reporter.reportErrorMessage(
- computeSourceSpanFromTreeNode(member),
+ computeSourceSpanFromTreeNode(node),
MessageKind.GENERIC,
{'text': "Unrecognized dart2js pragma @pragma('$name')"});
}
@@ -228,7 +237,7 @@
}
if (!found) {
reporter.reportErrorMessage(
- computeSourceSpanFromTreeNode(member),
+ computeSourceSpanFromTreeNode(node),
MessageKind.GENERIC,
{'text': "Unknown dart2js pragma @pragma('$name')"});
}
@@ -242,7 +251,7 @@
for (PragmaAnnotation other in implies) {
if (annotations.contains(other)) {
reporter.reportHintMessage(
- computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, {
+ computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('dart2js:${annotation.name}') implies "
"@pragma('dart2js:${other.name}')."
});
@@ -255,7 +264,7 @@
if (annotations.contains(other) &&
!(reportedExclusions[other]?.contains(annotation) ?? false)) {
reporter.reportErrorMessage(
- computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, {
+ computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('dart2js:${annotation.name}') must not be used "
"with @pragma('dart2js:${other.name}')."
});
@@ -268,7 +277,7 @@
for (PragmaAnnotation other in requires) {
if (!annotations.contains(other)) {
reporter.reportErrorMessage(
- computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, {
+ computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('dart2js:${annotation.name}') should always be "
"combined with @pragma('dart2js:${other.name}')."
});
@@ -282,8 +291,9 @@
abstract class AnnotationsData {
/// Deserializes a [AnnotationsData] object from [source].
factory AnnotationsData.readFromDataSource(
- CompilerOptions options, DataSourceReader source) =
- AnnotationsDataImpl.readFromDataSource;
+ CompilerOptions options,
+ DiagnosticReporter reporter,
+ DataSourceReader source) = AnnotationsDataImpl.readFromDataSource;
/// Serializes this [AnnotationsData] to [sink].
void writeToDataSink(DataSinkWriter sink);
@@ -300,6 +310,8 @@
bool hasTryInline(MemberEntity member);
/// Returns `true` if inlining is disabled at call sites inside [member].
+ // TODO(49475): This should be a property of call site, but ssa/builder.dart
+ // does not currently always pass the call site ir.TreeNode.
bool hasDisableInlining(MemberEntity member);
/// Returns `true` if [member] has a `@pragma('dart2js:disableFinal')`
@@ -320,11 +332,13 @@
/// What the compiler should do with parameter type assertions in [member].
///
/// If [member] is `null`, the default policy is returned.
+ // TODO(49475): Need this be nullable?
CheckPolicy getParameterCheckPolicy(MemberEntity? member);
/// What the compiler should do with implicit downcasts in [member].
///
/// If [member] is `null`, the default policy is returned.
+ // TODO(49475): Need this be nullable?
CheckPolicy getImplicitDowncastCheckPolicy(MemberEntity? member);
/// What the compiler should do with a boolean value in a condition context
@@ -332,22 +346,28 @@
/// it to be null.
///
/// If [member] is `null`, the default policy is returned.
+ // TODO(49475): Need this be nullable?
CheckPolicy getConditionCheckPolicy(MemberEntity? member);
/// What the compiler should do with explicit casts in [member].
///
/// If [member] is `null`, the default policy is returned.
+ // TODO(49475): Need this be nullable?
CheckPolicy getExplicitCastCheckPolicy(MemberEntity? member);
/// What the compiler should do with index bounds checks `[]`, `[]=` and
/// `removeLast()` operations in the body of [member].
///
/// If [member] is `null`, the default policy is returned.
+ // TODO(49475): Need this be nullable?
CheckPolicy getIndexBoundsCheckPolicy(MemberEntity? member);
- /// What the compiler should do with late field checks in the body of
- /// [member]. [member] is usually the getter or setter for a late field.
- CheckPolicy getLateVariableCheckPolicy(MemberEntity member);
+ /// What the compiler should do with late field checks at a position in the
+ /// body of a method. The method is usually the getter or setter for a late
+ /// field.
+ // If we change our late field lowering to happen later, [node] could be the
+ // [ir.Field].
+ CheckPolicy getLateVariableCheckPolicyAt(ir.TreeNode node);
}
class AnnotationsDataImpl implements AnnotationsData {
@@ -355,6 +375,9 @@
/// debugging data stream.
static const String tag = 'annotations-data';
+ final CompilerOptions _options;
+ final DiagnosticReporter _reporter;
+
final CheckPolicy _defaultParameterCheckPolicy;
final CheckPolicy _defaultImplicitDowncastCheckPolicy;
final CheckPolicy _defaultConditionCheckPolicy;
@@ -362,10 +385,28 @@
final CheckPolicy _defaultIndexBoundsCheckPolicy;
final CheckPolicy _defaultLateVariableCheckPolicy;
final bool _defaultDisableInlining;
+
+ /// Pragma annotations for members. These are captured for the K-entities and
+ /// translated to J-entities.
+ // TODO(49475): Change the queries that use [pragmaAnnotation] to use the
+ // DirectivesContext so that we can avoid the need for persisting annotations.
final Map<MemberEntity, EnumSet<PragmaAnnotation>> pragmaAnnotations;
- AnnotationsDataImpl(CompilerOptions options, this.pragmaAnnotations)
- : this._defaultParameterCheckPolicy = options.defaultParameterCheckPolicy,
+ /// Pragma annotation environments for annotatable places in the Kernel
+ /// AST. These annotations generated on demand and not precomputed or
+ /// persisted. This map is a cache of the pragma annotation environment and
+ /// its enclosing enviroments.
+ // TODO(49475): Periodically clear this map to release references to tree
+ // nodes.
+ final Map<ir.Annotatable, DirectivesContext> _nodeToContextMap = {};
+
+ /// Root annotation environment that allows similar
+ final DirectivesContext _root = DirectivesContext.root();
+
+ AnnotationsDataImpl(
+ CompilerOptions options, this._reporter, this.pragmaAnnotations)
+ : this._options = options,
+ this._defaultParameterCheckPolicy = options.defaultParameterCheckPolicy,
this._defaultImplicitDowncastCheckPolicy =
options.defaultImplicitDowncastCheckPolicy,
this._defaultConditionCheckPolicy = options.defaultConditionCheckPolicy,
@@ -376,14 +417,14 @@
this._defaultLateVariableCheckPolicy = CheckPolicy.checked,
this._defaultDisableInlining = options.disableInlining;
- factory AnnotationsDataImpl.readFromDataSource(
- CompilerOptions options, DataSourceReader source) {
+ factory AnnotationsDataImpl.readFromDataSource(CompilerOptions options,
+ DiagnosticReporter reporter, DataSourceReader source) {
source.begin(tag);
Map<MemberEntity, EnumSet<PragmaAnnotation>> pragmaAnnotations =
source.readMemberMap(
(MemberEntity member) => EnumSet.fromValue(source.readInt()));
source.end(tag);
- return AnnotationsDataImpl(options, pragmaAnnotations);
+ return AnnotationsDataImpl(options, reporter, pragmaAnnotations);
}
@override
@@ -522,18 +563,33 @@
}
@override
- CheckPolicy getLateVariableCheckPolicy(MemberEntity member) {
- EnumSet<PragmaAnnotation>? annotations = pragmaAnnotations[member];
- if (annotations != null) {
+ CheckPolicy getLateVariableCheckPolicyAt(ir.TreeNode node) {
+ return _getLateVariableCheckPolicyAt(_findContext(node));
+ }
+
+ CheckPolicy _getLateVariableCheckPolicyAt(DirectivesContext? context) {
+ while (context != null) {
+ EnumSet<PragmaAnnotation>? annotations = context.annotations;
if (annotations.contains(PragmaAnnotation.lateTrust)) {
return CheckPolicy.trusted;
} else if (annotations.contains(PragmaAnnotation.lateCheck)) {
return CheckPolicy.checked;
}
+ context = context.parent;
}
- // TODO(sra): Look for annotations on enclosing class and library.
return _defaultLateVariableCheckPolicy;
}
+
+ DirectivesContext _findContext(ir.TreeNode startNode) {
+ ir.TreeNode? node = startNode;
+ while (node is! ir.Annotatable) {
+ if (node == null) return _root;
+ node = node.parent;
+ }
+ return _nodeToContextMap[node] ??= _findContext(node.parent!).extend(
+ processMemberAnnotations(_options, _reporter, node,
+ computePragmaAnnotationDataFromIr(node)));
+ }
}
class AnnotationsDataBuilder {
@@ -546,7 +602,74 @@
}
}
- AnnotationsData close(CompilerOptions options) {
- return AnnotationsDataImpl(options, pragmaAnnotations);
+ AnnotationsData close(CompilerOptions options, DiagnosticReporter reporter) {
+ return AnnotationsDataImpl(options, reporter, pragmaAnnotations);
+ }
+}
+
+/// A [DirectivesContext] is a chain of enclosing parent annotatable
+/// scopes.
+///
+/// The context chain for a location always starts with a node that reflects the
+/// annotations at the location so that it is possible to determine if an
+/// annotation is 'on' the element. Chains for different locations that have the
+/// same structure are shared. `method1` and `method2` have the same annotations
+/// in scope, with no annotations on the method itself but inheriting
+/// `late:trust` from the class scope. This is represented by DirectivesContext
+/// [D].
+///
+/// Links in the context chain above the element may be compressed, so `class
+/// DD` and `method4` share the chain [F] with no annotations on the element but
+/// inheriting `late:check` from the enclosing library scope.
+///
+/// @pragma('dart2js:late:check')
+/// library foo; // [B]
+///
+/// @pragma('dart2js:late:trust')
+/// class CC { // [C]
+/// method1(){} // [D]
+/// method2(){} // [D]
+/// @pragma('dart2js:noInline')
+/// method3(){} // [E]
+/// }
+///
+/// class DD { // [F]
+/// method4(); // [F]
+/// }
+///
+///
+/// A: parent: null, pragmas: {}
+///
+/// B: parent: A, pragmas: {late:check}
+///
+/// C: parent: B, pragmas: {late:trust}
+///
+/// D: parent: C, pragmas: {}
+/// E: parent: C, pragmas: {noInline}
+///
+/// F: parent: B, pragmas: {}
+///
+/// The root scope [A] is empty. We could remove it and start the root scope at
+/// the library, but the shared root might be a good place to put a set of
+/// annotations derived from the command-line.
+///
+/// If we ever introduce a singled annotation that means something different in
+/// different positions (e.g. on a class vs. on a method), we might want to make
+/// the [DirectivesContext] have a 'scope-kind'.
+class DirectivesContext {
+ final DirectivesContext? parent;
+ final EnumSet<PragmaAnnotation> annotations;
+
+ Map<EnumSet<PragmaAnnotation>, DirectivesContext>? _children;
+
+ DirectivesContext._(this.parent, this.annotations);
+
+ DirectivesContext.root() : this._(null, EnumSet<PragmaAnnotation>());
+
+ DirectivesContext extend(EnumSet<PragmaAnnotation> annotations) {
+ // Shorten chains of equivalent sets of annotations.
+ if (this.annotations == annotations) return this;
+ final children = _children ??= {};
+ return children[annotations] ??= DirectivesContext._(this, annotations);
}
}
diff --git a/pkg/compiler/lib/src/js_model/js_strategy.dart b/pkg/compiler/lib/src/js_model/js_strategy.dart
index 54a43e6..3353396 100644
--- a/pkg/compiler/lib/src/js_model/js_strategy.dart
+++ b/pkg/compiler/lib/src/js_model/js_strategy.dart
@@ -172,8 +172,12 @@
closedWorld.annotationsData);
ClosureDataBuilder closureDataBuilder = ClosureDataBuilder(
_compiler.reporter, _elementMap, closedWorld.annotationsData);
- JsClosedWorldBuilder closedWorldBuilder = JsClosedWorldBuilder(_elementMap,
- closureDataBuilder, _compiler.options, _compiler.abstractValueStrategy);
+ JsClosedWorldBuilder closedWorldBuilder = JsClosedWorldBuilder(
+ _elementMap,
+ closureDataBuilder,
+ _compiler.options,
+ _compiler.reporter,
+ _compiler.abstractValueStrategy);
JClosedWorld jClosedWorld = closedWorldBuilder.convertClosedWorld(
closedWorld, strategy.closureModels, outputUnitData);
_elementMap.lateOutputUnitDataBuilder =
diff --git a/pkg/compiler/lib/src/js_model/js_world.dart b/pkg/compiler/lib/src/js_model/js_world.dart
index 6d039aa..1d405d4 100644
--- a/pkg/compiler/lib/src/js_model/js_world.dart
+++ b/pkg/compiler/lib/src/js_model/js_world.dart
@@ -163,7 +163,7 @@
source.readClassMap(() => source.readClasses().toSet());
AnnotationsData annotationsData =
- AnnotationsData.readFromDataSource(options, source);
+ AnnotationsData.readFromDataSource(options, reporter, source);
ClosureData closureData =
ClosureData.readFromDataSource(elementMap, source);
diff --git a/pkg/compiler/lib/src/js_model/js_world_builder.dart b/pkg/compiler/lib/src/js_model/js_world_builder.dart
index 7957441..673cf82 100644
--- a/pkg/compiler/lib/src/js_model/js_world_builder.dart
+++ b/pkg/compiler/lib/src/js_model/js_world_builder.dart
@@ -46,10 +46,11 @@
final Map<ClassEntity, ClassSet> _classSets = <ClassEntity, ClassSet>{};
final ClosureDataBuilder _closureDataBuilder;
final CompilerOptions _options;
+ final DiagnosticReporter _reporter;
final AbstractValueStrategy _abstractValueStrategy;
JsClosedWorldBuilder(this._elementMap, this._closureDataBuilder,
- this._options, this._abstractValueStrategy);
+ this._options, this._reporter, this._abstractValueStrategy);
ElementEnvironment get _elementEnvironment => _elementMap.elementEnvironment;
CommonElements get _commonElements => _elementMap.commonElements;
@@ -200,7 +201,7 @@
JFieldAnalysis.from(closedWorld, map, _options);
AnnotationsDataImpl oldAnnotationsData = closedWorld.annotationsData;
- AnnotationsData annotationsData = AnnotationsDataImpl(_options,
+ AnnotationsData annotationsData = AnnotationsDataImpl(_options, _reporter,
map.toBackendMemberMap(oldAnnotationsData.pragmaAnnotations, identity));
OutputUnitData outputUnitData =
diff --git a/pkg/compiler/lib/src/kernel/kernel_strategy.dart b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
index 80486a8..922b7da 100644
--- a/pkg/compiler/lib/src/kernel/kernel_strategy.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
@@ -154,8 +154,8 @@
// TODO(johnniwinther): This is a hack. The annotation data is built while
// using it. With CFE constants the annotations data can be built fully
// before creating the resolution enqueuer.
- AnnotationsData annotationsData = AnnotationsDataImpl(
- compiler.options, annotationsDataBuilder.pragmaAnnotations);
+ AnnotationsData annotationsData = AnnotationsDataImpl(compiler.options,
+ compiler.reporter, annotationsDataBuilder.pragmaAnnotations);
InterceptorDataBuilder interceptorDataBuilder = InterceptorDataBuilderImpl(
nativeBasicData, elementEnvironment, commonElements);
return ResolutionEnqueuer(
@@ -392,6 +392,9 @@
node,
pragmaAnnotationData);
_annotationsDataBuilder.registerPragmaAnnotations(element, annotations);
+ // TODO(sra): Replace the above three statements with a single call to a
+ // new API on AnnotationsData that causes the annotations to be parsed and
+ // checked.
ModularMemberData modularMemberData =
_modularStrategy.getModularMemberData(node, annotations);
diff --git a/pkg/compiler/lib/src/ssa/builder.dart b/pkg/compiler/lib/src/ssa/builder.dart
index 67b995a..3aa7df2 100644
--- a/pkg/compiler/lib/src/ssa/builder.dart
+++ b/pkg/compiler/lib/src/ssa/builder.dart
@@ -4958,8 +4958,8 @@
HInstruction value = arguments[0];
HInstruction name = options.omitLateNames ? null : arguments[1];
- CheckPolicy policy = closedWorld.annotationsData
- .getLateVariableCheckPolicy(_currentFrame.member);
+ CheckPolicy policy =
+ closedWorld.annotationsData.getLateVariableCheckPolicyAt(invocation);
push(HLateReadCheck(value, name, policy.isTrusted,
_abstractValueDomain.excludeLateSentinel(value.instructionType))
@@ -4983,8 +4983,8 @@
HInstruction value = arguments[0];
HInstruction name = options.omitLateNames ? null : arguments[1];
- CheckPolicy policy = closedWorld.annotationsData
- .getLateVariableCheckPolicy(_currentFrame.member);
+ CheckPolicy policy =
+ closedWorld.annotationsData.getLateVariableCheckPolicyAt(invocation);
push(HLateWriteOnceCheck(
value, name, policy.isTrusted, _abstractValueDomain.dynamicType)
@@ -5008,8 +5008,8 @@
HInstruction value = arguments[0];
HInstruction name = options.omitLateNames ? null : arguments[1];
- CheckPolicy policy = closedWorld.annotationsData
- .getLateVariableCheckPolicy(_currentFrame.member);
+ CheckPolicy policy =
+ closedWorld.annotationsData.getLateVariableCheckPolicyAt(invocation);
push(HLateInitializeOnceCheck(
value, name, policy.isTrusted, _abstractValueDomain.dynamicType)
diff --git a/pkg/compiler/lib/src/universe/resolution_world_builder.dart b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
index a49196d..40433ca 100644
--- a/pkg/compiler/lib/src/universe/resolution_world_builder.dart
+++ b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
@@ -1002,7 +1002,7 @@
mixinUses: _classHierarchyBuilder.mixinUses,
typesImplementedBySubclasses: typesImplementedBySubclasses,
classHierarchy: _classHierarchyBuilder.close(),
- annotationsData: _annotationsDataBuilder.close(_options),
+ annotationsData: _annotationsDataBuilder.close(_options, reporter),
isChecks: _isChecks,
staticTypeArgumentDependencies: staticTypeArgumentDependencies,
dynamicTypeArgumentDependencies: dynamicTypeArgumentDependencies,
diff --git a/tests/web/late_field_checks_common.dart b/tests/web/late_field_checks_common.dart
new file mode 100644
index 0000000..e0e380c
--- /dev/null
+++ b/tests/web/late_field_checks_common.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library late_field_checks.common;
+
+import 'package:expect/expect.dart';
+
+// True for DDC and VM.
+final bool isAlwaysChecked = !const bool.fromEnvironment('dart2js');
+
+/// Interface for test object classes that have a field. All the test objects
+/// implement this interface.
+abstract class Field {
+ abstract int field;
+}
+
+/// Tag interface for a class where the `late` field is also `final`.
+class Final {}
+
+/// Tag interface for a class where late field is checked.
+class Checked {}
+
+/// Tag interface for a class where the late field checks are not performed for
+/// the late field.
+class Trusted {}
+
+/// The library 'name' e.g. 'LibraryCheck'
+/// Nullable so it can be cleared to ensure the library's main() sets it.
+String? libraryName;
+
+String describeClass(Field object) {
+ final name = '${libraryName!}.${object.runtimeType}';
+ final interfaces = [
+ if (object is Final) 'Final',
+ 'Field',
+ if (object is Checked) 'Checked',
+ if (object is Trusted) 'Trusted'
+ ];
+
+ return '$name implements ${interfaces.join(", ")}';
+}
+
+void test(Field Function() factory) {
+ // Consistency checks.
+ final o1 = factory();
+ // Get the class description to ensure library name is set.
+ final description = describeClass(o1);
+ print('-- $description');
+ Expect.isTrue(o1 is Checked || o1 is Trusted,
+ 'Test class must implement one of Checked or Trusted: $description');
+ Expect.isFalse(o1 is Checked && o1 is Trusted,
+ 'Test class must not implement both of Checked or Trusted: $description');
+
+ // Setter then Getter should not throw.
+ final o2 = factory();
+ o2.field = 100;
+ Expect.equals(100, o2.field);
+
+ testGetterBeforeSetter(factory());
+ testDoubleSetter(factory());
+}
+
+void testGetterBeforeSetter(Field object) {
+ final isChecked = isAlwaysChecked || object is Checked;
+
+ bool threw = false;
+ try {
+ _sink = object.field;
+ } catch (e, s) {
+ threw = true;
+ }
+
+ if (threw == isChecked) return;
+ _fail(object, threw, 'getter before setter');
+}
+
+void testDoubleSetter(Field object) {
+ final isChecked = isAlwaysChecked || object is Checked;
+
+ object.field = 101;
+ bool threw = false;
+ try {
+ object.field = 102;
+ } catch (e, s) {
+ threw = true;
+ }
+
+ if (object is Final) {
+ if (threw == isChecked) return;
+ _fail(object, threw, 'double setter');
+ }
+
+ Expect.equals(102, object.field);
+}
+
+int _sink = 0;
+
+void _fail(Field object, bool threw, String testDescription) {
+ final classDescription = describeClass(object);
+ if (threw) {
+ Expect.fail('Should not throw for $testDescription: $classDescription');
+ } else {
+ Expect.fail('Failed to throw for $testDescription: $classDescription');
+ }
+}
diff --git a/tests/web/late_field_checks_lib_check.dart b/tests/web/late_field_checks_lib_check.dart
new file mode 100644
index 0000000..0cd85ea
--- /dev/null
+++ b/tests/web/late_field_checks_lib_check.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@pragma('dart2js:late:check')
+library late_field_checks.lib_check;
+
+import 'late_field_checks_common.dart';
+
+void main() {
+ libraryName = 'LibraryCheck';
+
+ test(() => ClassNoneFieldNone());
+ test(() => ClassNoneFinalFieldNone());
+ test(() => ClassNoneFieldTrust());
+ test(() => ClassNoneFinalFieldTrust());
+ test(() => ClassNoneFieldCheck());
+ test(() => ClassNoneFinalFieldCheck());
+
+ test(() => ClassTrustFieldNone());
+ test(() => ClassTrustFinalFieldNone());
+ test(() => ClassTrustFieldTrust());
+ test(() => ClassTrustFinalFieldTrust());
+ test(() => ClassTrustFieldCheck());
+ test(() => ClassTrustFinalFieldCheck());
+
+ test(() => ClassCheckFieldNone());
+ test(() => ClassCheckFinalFieldNone());
+ test(() => ClassCheckFieldTrust());
+ test(() => ClassCheckFinalFieldTrust());
+ test(() => ClassCheckFieldCheck());
+ test(() => ClassCheckFinalFieldCheck());
+}
+
+class ClassNoneFieldNone implements Field, Checked {
+ late int field;
+}
+
+class ClassNoneFinalFieldNone implements Field, Final, Checked {
+ late final int field;
+}
+
+class ClassNoneFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+class ClassNoneFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+class ClassNoneFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+class ClassNoneFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldNone implements Field, Trusted {
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldNone implements Field, Final, Trusted {
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldNone implements Field, Checked {
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldNone implements Field, Final, Checked {
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
diff --git a/tests/web/late_field_checks_lib_none.dart b/tests/web/late_field_checks_lib_none.dart
new file mode 100644
index 0000000..d39d61f
--- /dev/null
+++ b/tests/web/late_field_checks_lib_none.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// No annotation here.
+library late_field_checks.lib_none;
+
+import 'late_field_checks_common.dart';
+
+void main() {
+ libraryName = 'LibraryNone';
+
+ test(() => ClassNoneFieldNone());
+ test(() => ClassNoneFinalFieldNone());
+ test(() => ClassNoneFieldTrust());
+ test(() => ClassNoneFinalFieldTrust());
+ test(() => ClassNoneFieldCheck());
+ test(() => ClassNoneFinalFieldCheck());
+
+ test(() => ClassTrustFieldNone());
+ test(() => ClassTrustFinalFieldNone());
+ test(() => ClassTrustFieldTrust());
+ test(() => ClassTrustFinalFieldTrust());
+ test(() => ClassTrustFieldCheck());
+ test(() => ClassTrustFinalFieldCheck());
+
+ test(() => ClassCheckFieldNone());
+ test(() => ClassCheckFinalFieldNone());
+ test(() => ClassCheckFieldTrust());
+ test(() => ClassCheckFinalFieldTrust());
+ test(() => ClassCheckFieldCheck());
+ test(() => ClassCheckFinalFieldCheck());
+}
+
+class ClassNoneFieldNone implements Field, Checked {
+ late int field;
+}
+
+class ClassNoneFinalFieldNone implements Field, Final, Checked {
+ late final int field;
+}
+
+class ClassNoneFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+class ClassNoneFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+class ClassNoneFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+class ClassNoneFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldNone implements Field, Trusted {
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldNone implements Field, Final, Trusted {
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldNone implements Field, Checked {
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldNone implements Field, Final, Checked {
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
diff --git a/tests/web/late_field_checks_lib_trust.dart b/tests/web/late_field_checks_lib_trust.dart
new file mode 100644
index 0000000..13c19c3
--- /dev/null
+++ b/tests/web/late_field_checks_lib_trust.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@pragma('dart2js:late:trust')
+library late_field_checks.lib_trust;
+
+import 'late_field_checks_common.dart';
+
+void main() {
+ libraryName = 'LibraryTrust';
+
+ test(() => ClassNoneFieldNone());
+ test(() => ClassNoneFinalFieldNone());
+ test(() => ClassNoneFieldTrust());
+ test(() => ClassNoneFinalFieldTrust());
+ test(() => ClassNoneFieldCheck());
+ test(() => ClassNoneFinalFieldCheck());
+
+ test(() => ClassTrustFieldNone());
+ test(() => ClassTrustFinalFieldNone());
+ test(() => ClassTrustFieldTrust());
+ test(() => ClassTrustFinalFieldTrust());
+ test(() => ClassTrustFieldCheck());
+ test(() => ClassTrustFinalFieldCheck());
+
+ test(() => ClassCheckFieldNone());
+ test(() => ClassCheckFinalFieldNone());
+ test(() => ClassCheckFieldTrust());
+ test(() => ClassCheckFinalFieldTrust());
+ test(() => ClassCheckFieldCheck());
+ test(() => ClassCheckFinalFieldCheck());
+}
+
+class ClassNoneFieldNone implements Field, Trusted {
+ late int field;
+}
+
+class ClassNoneFinalFieldNone implements Field, Final, Trusted {
+ late final int field;
+}
+
+class ClassNoneFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+class ClassNoneFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+class ClassNoneFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+class ClassNoneFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldNone implements Field, Trusted {
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldNone implements Field, Final, Trusted {
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+@pragma('dart2js:late:trust')
+class ClassTrustFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldNone implements Field, Checked {
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldNone implements Field, Final, Checked {
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldTrust implements Field, Trusted {
+ @pragma('dart2js:late:trust')
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldTrust implements Field, Final, Trusted {
+ @pragma('dart2js:late:trust')
+ late final int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFieldCheck implements Field, Checked {
+ @pragma('dart2js:late:check')
+ late int field;
+}
+
+@pragma('dart2js:late:check')
+class ClassCheckFinalFieldCheck implements Field, Final, Checked {
+ @pragma('dart2js:late:check')
+ late final int field;
+}
diff --git a/tests/web/late_field_checks_test.dart b/tests/web/late_field_checks_test.dart
new file mode 100644
index 0000000..f8777e1
--- /dev/null
+++ b/tests/web/late_field_checks_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// dart2jsOptions=-Ddart2js=true
+
+// Behavioral test for annotations that control checking of late fields.
+// See `late_field_checks_common.dart` for details.
+
+import 'package:expect/expect.dart';
+
+import 'late_field_checks_common.dart' show libraryName;
+import 'late_field_checks_lib_none.dart' as libNone;
+import 'late_field_checks_lib_trust.dart' as libTrust;
+import 'late_field_checks_lib_check.dart' as libCheck;
+
+void main() {
+ libraryName = null;
+ libNone.main();
+ Expect.equals('LibraryNone', libraryName);
+
+ libraryName = null;
+ libCheck.main();
+ Expect.equals('LibraryCheck', libraryName);
+
+ libraryName = null;
+ libTrust.main();
+ Expect.equals('LibraryTrust', libraryName);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 9586891..0e7f484 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 19
PATCH 0
-PRERELEASE 14
+PRERELEASE 15
PRERELEASE_PATCH 0
\ No newline at end of file