Version 2.15.0-2.0.dev
Merge commit '79181abb70700922e20c018288271a48ff702eac' into 'dev'
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 79abc75..f96a7ee 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -200,6 +200,7 @@
CompileTimeErrorCode.FOR_IN_WITH_CONST_VARIABLE,
CompileTimeErrorCode.GENERIC_FUNCTION_TYPE_CANNOT_BE_BOUND,
CompileTimeErrorCode.GENERIC_FUNCTION_TYPE_CANNOT_BE_TYPE_ARGUMENT,
+ CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
CompileTimeErrorCode.GETTER_NOT_ASSIGNABLE_SETTER_TYPES,
CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES,
CompileTimeErrorCode.IF_ELEMENT_CONDITION_FROM_DEFERRED_LIBRARY,
diff --git a/pkg/analyzer/lib/src/dart/analysis/experiments.dart b/pkg/analyzer/lib/src/dart/analysis/experiments.dart
index b8f0909..f03c6aa 100644
--- a/pkg/analyzer/lib/src/dart/analysis/experiments.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/experiments.dart
@@ -46,11 +46,6 @@
/// The latest known language version.
static final Version latestSdkLanguageVersion = Version.parse('2.13.0');
- static final FeatureSet latestWithNullSafety = ExperimentStatus.fromStrings2(
- sdkLanguageVersion: latestSdkLanguageVersion,
- flags: [],
- );
-
/// A map containing information about all known experimental flags.
static final Map<String, ExperimentalFeature> knownFeatures = _knownFeatures;
diff --git a/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart b/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart
index 090d0ad..b725ead2 100644
--- a/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart
@@ -165,7 +165,8 @@
// Example:
// class C { C.named(); }
// C.named
- return _toConstructorReference(node: node, classElement: element);
+ return _toConstructorReference_prefixed(
+ node: node, classElement: element);
} else if (element is TypeAliasElement) {
var aliasedType = element.aliasedType;
if (aliasedType is InterfaceType) {
@@ -173,13 +174,98 @@
// class C { C.named(); }
// typedef X = C;
// X.named
- return _toConstructorReference(
+ return _toConstructorReference_prefixed(
node: node, classElement: aliasedType.element);
}
}
return node;
}
+ AstNode propertyAccess(Scope nameScope, PropertyAccess node) {
+ if (node.isCascaded) {
+ // For example, `List..filled`: this is a property access on an instance
+ // `Type`.
+ return node;
+ }
+ var receiver = node.target!;
+ if (receiver is! FunctionReference) {
+ return node;
+ }
+ var propertyName = node.propertyName;
+ if (propertyName.isSynthetic) {
+ // This isn't a constructor reference.
+ return node;
+ }
+ // A [ConstructorReference] with explicit type arguments is initially parsed
+ // as a [PropertyAccess] with a [FunctionReference] target; for example:
+ // `List<int>.filled` or `core.List<int>.filled`.
+ var receiverIdentifier = receiver.function;
+ if (receiverIdentifier is! Identifier) {
+ // If [receiverIdentifier] is not an Identifier then [node] is not a
+ // ConstructorReference.
+ return node;
+ }
+
+ Element? element;
+ if (receiverIdentifier is SimpleIdentifier) {
+ element = nameScope.lookup(receiverIdentifier.name).getter;
+ } else if (receiverIdentifier is PrefixedIdentifier) {
+ var prefixElement =
+ nameScope.lookup(receiverIdentifier.prefix.name).getter;
+ if (prefixElement is PrefixElement) {
+ element = prefixElement.scope
+ .lookup(receiverIdentifier.identifier.name)
+ .getter;
+ } else {
+ // This expression is something like `foo.List<int>.filled` where `foo`
+ // is not an import prefix.
+ // TODO(srawlins): Tease out a `null` prefixElement from others for
+ // specific errors.
+ return node;
+ }
+ }
+
+ if (element is ClassElement) {
+ // Example:
+ // class C<T> { C.named(); }
+ // C<int>.named
+ return _toConstructorReference_propertyAccess(
+ node: node,
+ receiver: receiverIdentifier,
+ typeArguments: receiver.typeArguments!,
+ classElement: element,
+ );
+ } else if (element is TypeAliasElement) {
+ var aliasedType = element.aliasedType;
+ if (aliasedType is InterfaceType) {
+ // Example:
+ // class C<T> { C.named(); }
+ // typedef X<T> = C<T>;
+ // X<int>.named
+ return _toConstructorReference_propertyAccess(
+ node: node,
+ receiver: receiverIdentifier,
+ typeArguments: receiver.typeArguments!,
+ classElement: aliasedType.element,
+ );
+ }
+ }
+
+ // If [receiverIdentifier] is an Identifier, but could not be resolved to
+ // an Element, we cannot assume [node] is a ConstructorReference.
+ //
+ // TODO(srawlins): However, take an example like `Lisst<int>.filled;`
+ // (where 'Lisst' does not resolve to any element). Possibilities include:
+ // the user tried to write a TypeLiteral or a FunctionReference, then access
+ // a property on that (these include: hashCode, runtimeType, tearoff of
+ // toString, and extension methods on Type); or the user tried to write a
+ // ConstructReference. It seems much more likely that the user is trying to
+ // do the latter. Consider doing the work so that the user gets an error in
+ // this case about `Lisst` not being a type, or `Lisst.filled` not being a
+ // known constructor.
+ return node;
+ }
+
AstNode _instanceCreation_prefix_type_name({
required MethodInvocation node,
required PrefixedIdentifier typeNameIdentifier,
@@ -210,7 +296,7 @@
return instanceCreationExpression;
}
- AstNode _toConstructorReference(
+ AstNode _toConstructorReference_prefixed(
{required PrefixedIdentifier node, required ClassElement classElement}) {
var name = node.identifier.name;
var constructorElement = name == 'new'
@@ -229,6 +315,31 @@
return constructorReference;
}
+ AstNode _toConstructorReference_propertyAccess({
+ required PropertyAccess node,
+ required Identifier receiver,
+ required TypeArgumentList typeArguments,
+ required ClassElement classElement,
+ }) {
+ var name = node.propertyName.name;
+ var constructorElement = name == 'new'
+ ? classElement.unnamedConstructor
+ : classElement.getNamedConstructor(name);
+ if (constructorElement == null) {
+ return node;
+ }
+
+ var operator = node.operator;
+
+ var typeName = astFactory.typeName(receiver, typeArguments);
+ var constructorName =
+ astFactory.constructorName(typeName, operator, node.propertyName);
+ var constructorReference =
+ astFactory.constructorReference(constructorName: constructorName);
+ NodeReplacer.replace(node, constructorReference);
+ return constructorReference;
+ }
+
InstanceCreationExpression _toInstanceCreation_prefix_type({
required MethodInvocation node,
required SimpleIdentifier prefixIdentifier,
diff --git a/pkg/analyzer/lib/src/dart/resolver/constructor_reference_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/constructor_reference_resolver.dart
index 0d82de1..ce1fd84 100644
--- a/pkg/analyzer/lib/src/dart/resolver/constructor_reference_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/constructor_reference_resolver.dart
@@ -17,7 +17,10 @@
ConstructorReferenceResolver(this._resolver);
void resolve(ConstructorReferenceImpl node) {
- if (!_resolver.isConstructorTearoffsEnabled) {
+ if (!_resolver.isConstructorTearoffsEnabled &&
+ node.constructorName.type.typeArguments == null) {
+ // Only report this if [node] has no explicit type arguments; otherwise
+ // the parser has already reported an error.
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONSTRUCTOR_TEAROFFS_NOT_ENABLED, node, []);
}
diff --git a/pkg/analyzer/lib/src/dart/resolver/for_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/for_resolver.dart
index 1c4f15f..49a31d0 100644
--- a/pkg/analyzer/lib/src/dart/resolver/for_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/for_resolver.dart
@@ -114,9 +114,10 @@
iterable.accept(_resolver);
iterable = forEachParts.iterable;
- _resolver.nullableDereferenceVerifier.expression(iterable,
- errorCode:
- CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_AS_ITERATOR);
+ _resolver.nullableDereferenceVerifier.expression(
+ CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_AS_ITERATOR,
+ iterable,
+ );
loopVariable?.accept(_resolver);
var elementType = _computeForEachElementType(iterable, isAsync);
diff --git a/pkg/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
index 8543e5e..48a6c65 100644
--- a/pkg/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
@@ -24,7 +24,7 @@
FunctionExpressionInvocationResolver({
required ResolverVisitor resolver,
- }) : _resolver = resolver,
+ }) : _resolver = resolver,
_typePropertyResolver = resolver.typePropertyResolver,
_inferenceHelper = resolver.inferenceHelper;
@@ -60,8 +60,10 @@
return;
}
- _nullableDereferenceVerifier.expression(function,
- errorCode: CompileTimeErrorCode.UNCHECKED_INVOCATION_OF_NULLABLE_VALUE);
+ _nullableDereferenceVerifier.expression(
+ CompileTimeErrorCode.UNCHECKED_INVOCATION_OF_NULLABLE_VALUE,
+ function,
+ );
if (receiverType is FunctionType) {
_resolve(node, receiverType, whyNotPromotedList);
diff --git a/pkg/analyzer/lib/src/dart/resolver/function_reference_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/function_reference_resolver.dart
index 76697c1..0bffbf1 100644
--- a/pkg/analyzer/lib/src/dart/resolver/function_reference_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/function_reference_resolver.dart
@@ -278,8 +278,11 @@
function.prefix.staticType = prefixType;
if (prefixType != null && prefixType.isDynamic) {
- // TODO(srawlins): Report error. See spec text: "We do not allow dynamic
- // explicit instantiation."
+ _errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
+ function,
+ [],
+ );
node.staticType = DynamicTypeImpl.instance;
return;
}
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
index 44388f6..5021eec 100644
--- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -495,9 +495,11 @@
);
} else {
_setDynamicResolution(node, whyNotPromotedList: whyNotPromotedList);
- _resolver.nullableDereferenceVerifier.report(methodName, receiverType,
- errorCode: CompileTimeErrorCode
- .UNCHECKED_METHOD_INVOCATION_OF_NULLABLE_VALUE);
+ _resolver.nullableDereferenceVerifier.report(
+ CompileTimeErrorCode.UNCHECKED_METHOD_INVOCATION_OF_NULLABLE_VALUE,
+ methodName,
+ receiverType,
+ );
}
return;
}
diff --git a/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
index 357caa6..b367f4a 100644
--- a/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
@@ -870,6 +870,16 @@
}
@override
+ void visitPropertyAccess(PropertyAccess node) {
+ var newNode = _astRewriter.propertyAccess(_nameScope, node);
+ if (newNode != node) {
+ return newNode.accept(this);
+ }
+
+ super.visitPropertyAccess(node);
+ }
+
+ @override
void visitSimpleFormalParameter(covariant SimpleFormalParameterImpl node) {
ParameterElementImpl element;
if (node.parent is DefaultFormalParameter) {
diff --git a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
index 6183525..5fa2a91 100644
--- a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
@@ -143,8 +143,8 @@
}
}
_resolver.nullableDereferenceVerifier.report(
- propertyErrorEntity, receiverType,
- errorCode: errorCode, arguments: [name], messages: messages);
+ errorCode, propertyErrorEntity, receiverType,
+ arguments: [name], messages: messages);
_reportedGetterError = true;
_reportedSetterError = true;
diff --git a/pkg/analyzer/lib/src/dart/resolver/yield_statement_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/yield_statement_resolver.dart
index 4238870..6e47fa5 100644
--- a/pkg/analyzer/lib/src/dart/resolver/yield_statement_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/yield_statement_resolver.dart
@@ -126,9 +126,10 @@
node.expression.accept(_resolver);
if (node.star != null) {
- _resolver.nullableDereferenceVerifier.expression(node.expression,
- errorCode: CompileTimeErrorCode
- .UNCHECKED_USE_OF_NULLABLE_VALUE_IN_YIELD_EACH);
+ _resolver.nullableDereferenceVerifier.expression(
+ CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_IN_YIELD_EACH,
+ node.expression,
+ );
}
bodyContext.addYield(node);
diff --git a/pkg/analyzer/lib/src/error/bool_expression_verifier.dart b/pkg/analyzer/lib/src/error/bool_expression_verifier.dart
index e56e3ad..108cc22 100644
--- a/pkg/analyzer/lib/src/error/bool_expression_verifier.dart
+++ b/pkg/analyzer/lib/src/error/bool_expression_verifier.dart
@@ -25,7 +25,7 @@
required ResolverVisitor resolver,
required ErrorReporter errorReporter,
required NullableDereferenceVerifier nullableDereferenceVerifier,
- }) : _resolver = resolver,
+ }) : _resolver = resolver,
_errorReporter = errorReporter,
_nullableDereferenceVerifier = nullableDereferenceVerifier,
_boolType = resolver.typeSystem.typeProvider.boolType;
@@ -53,9 +53,10 @@
if (!_checkForUseOfVoidResult(expression) &&
!_resolver.typeSystem.isAssignableTo(type, _boolType)) {
if (type.isDartCoreBool) {
- _nullableDereferenceVerifier.report(expression, type,
- errorCode: CompileTimeErrorCode
- .UNCHECKED_USE_OF_NULLABLE_VALUE_AS_CONDITION,
+ _nullableDereferenceVerifier.report(
+ CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_AS_CONDITION,
+ expression,
+ type,
messages: _resolver.computeWhyNotPromotedMessages(
expression, whyNotPromoted?.call()));
} else {
diff --git a/pkg/analyzer/lib/src/error/codes.dart b/pkg/analyzer/lib/src/error/codes.dart
index 7a9de95..fd77bb3 100644
--- a/pkg/analyzer/lib/src/error/codes.dart
+++ b/pkg/analyzer/lib/src/error/codes.dart
@@ -5208,6 +5208,19 @@
"type, or using 'dynamic' as the type argument here.");
/**
+ * No parameters.
+ */
+ static const CompileTimeErrorCode
+ GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC = CompileTimeErrorCode(
+ 'GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC',
+ "A method tearoff on a target whose type is 'dynamic' can't have type "
+ "arguments.",
+ correction:
+ "Specify the type of the target, or remove the type arguments from the "
+ "method tearoff.",
+ );
+
+ /**
* 10.3 Setters: It is a compile-time error if a class has a setter named
* `v=` with argument type `T` and a getter named `v` with return type `S`,
* and `S` may not be assigned to `T`.
diff --git a/pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart b/pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart
index fb96d1b..3c5caba 100644
--- a/pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart
+++ b/pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart
@@ -27,28 +27,26 @@
required TypeSystemImpl typeSystem,
required ErrorReporter errorReporter,
required ResolverVisitor resolver,
- }) : _typeSystem = typeSystem,
+ }) : _typeSystem = typeSystem,
_errorReporter = errorReporter,
_resolver = resolver;
- bool expression(Expression expression,
- {DartType? type, ErrorCode? errorCode}) {
+ bool expression(ErrorCode errorCode, Expression expression,
+ {DartType? type}) {
if (!_typeSystem.isNonNullableByDefault) {
return false;
}
type ??= expression.typeOrThrow;
- return _check(expression, type, errorCode: errorCode);
+ return _check(errorCode, expression, type);
}
- void report(SyntacticEntity errorEntity, DartType receiverType,
- {ErrorCode? errorCode,
- List<String> arguments = const <String>[],
+ void report(
+ ErrorCode errorCode, SyntacticEntity errorEntity, DartType receiverType,
+ {List<String> arguments = const <String>[],
List<DiagnosticMessage>? messages}) {
if (receiverType == _typeSystem.typeProvider.nullType) {
errorCode = CompileTimeErrorCode.INVALID_USE_OF_NULL_VALUE;
- } else {
- errorCode ??= CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE;
}
if (errorEntity is AstNode) {
_errorReporter.reportErrorForNode(
@@ -67,8 +65,11 @@
/// receiver is the implicit `this`, the name of the invocation.
///
/// Returns whether [receiverType] was reported.
- bool _check(AstNode errorNode, DartType receiverType,
- {ErrorCode? errorCode}) {
+ bool _check(
+ ErrorCode errorCode,
+ AstNode errorNode,
+ DartType receiverType,
+ ) {
if (identical(receiverType, DynamicTypeImpl.instance) ||
!_typeSystem.isPotentiallyNullable(receiverType)) {
return false;
@@ -79,7 +80,7 @@
messages = _resolver.computeWhyNotPromotedMessages(
errorNode, _resolver.flowAnalysis?.flow?.whyNotPromoted(errorNode)());
}
- report(errorNode, receiverType, errorCode: errorCode, messages: messages);
+ report(errorCode, errorNode, receiverType, messages: messages);
return true;
}
}
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index bd08b70..a01167d 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1896,9 +1896,10 @@
super.visitSpreadElement(node);
if (!node.isNullAware) {
- nullableDereferenceVerifier.expression(node.expression,
- errorCode:
- CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_IN_SPREAD);
+ nullableDereferenceVerifier.expression(
+ CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_IN_SPREAD,
+ node.expression,
+ );
}
}
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk_elements.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk_elements.dart
index cb56f4d..a3d5f14 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk_elements.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk_elements.dart
@@ -2,10 +2,10 @@
// 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.
+import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
-import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
@@ -866,7 +866,7 @@
'dart.async',
0,
0,
- ExperimentStatus.latestWithNullSafety,
+ FeatureSet.latestLanguageVersion(),
);
var asyncUnit = CompilationUnitElementImpl();
@@ -959,7 +959,7 @@
'dart.core',
0,
0,
- ExperimentStatus.latestWithNullSafety,
+ FeatureSet.latestLanguageVersion(),
);
coreLibrary.definingCompilationUnit = coreUnit;
diff --git a/pkg/analyzer/test/error/error_test.dart b/pkg/analyzer/test/error/error_test.dart
index 6e14b89..b0a7875 100644
--- a/pkg/analyzer/test/error/error_test.dart
+++ b/pkg/analyzer/test/error/error_test.dart
@@ -5,9 +5,9 @@
import 'dart:core';
import 'dart:io';
+import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
-import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -78,7 +78,7 @@
return parseString(
path: filePath,
content: File(filePath).readAsStringSync(),
- featureSet: ExperimentStatus.latestWithNullSafety,
+ featureSet: FeatureSet.latestLanguageVersion(),
).unit;
}
diff --git a/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart b/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart
index b52a502..0c94b3c 100644
--- a/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart
@@ -2,6 +2,7 @@
// 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.
+import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -10,12 +11,383 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ConstructorReferenceResolutionTest);
+ defineReflectiveTests(ConstructorReferenceResolution_TypeArgsTest);
defineReflectiveTests(
ConstructorReferenceResolutionWithoutConstructorTearoffsTest);
});
}
@reflectiveTest
+class ConstructorReferenceResolution_TypeArgsTest
+ extends PubPackageResolutionTest {
+ test_alias_generic_named() async {
+ await assertNoErrorsInCode('''
+class A<T, U> {
+ A.foo();
+}
+typedef TA<T, U> = A<U, T>;
+
+void bar() {
+ TA<int, String>.foo;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('TA<int, String>.foo;'),
+ elementMatcher(classElement.getNamedConstructor('foo')!,
+ substitution: {'T': 'String', 'U': 'int'}),
+ classElement,
+ 'A<String, int> Function()',
+ expectedTypeNameType: 'A<String, int>',
+ expectedTypeNameElement: findElement.typeAlias('TA'),
+ );
+ }
+
+ test_alias_generic_unnamed() async {
+ await assertNoErrorsInCode('''
+class A<T> {
+ A();
+}
+typedef TA<T> = A<T>;
+
+void bar() {
+ TA<int>.new;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('TA<int>.new;'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ expectedTypeNameElement: findElement.typeAlias('TA'),
+ );
+ }
+
+ test_alias_genericWithBound_unnamed() async {
+ await assertNoErrorsInCode('''
+class A<T> {
+ A();
+}
+typedef TA<T extends num> = A<T>;
+
+void bar() {
+ TA<int>.new;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('TA<int>.new;'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ expectedTypeNameElement: findElement.typeAlias('TA'),
+ );
+ }
+
+ test_alias_genericWithBound_unnamed_badBound() async {
+ await assertErrorsInCode('''
+class A<T> {
+ A();
+}
+typedef TA<T extends num> = A<T>;
+
+void bar() {
+ TA<String>.new;
+}
+''', [
+ error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 75, 6),
+ ]);
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('TA<String>.new;'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'String'}),
+ classElement,
+ 'A<String> Function()',
+ expectedTypeNameType: 'A<String>',
+ expectedTypeNameElement: findElement.typeAlias('TA'),
+ );
+ }
+
+ test_class_generic_named() async {
+ await assertNoErrorsInCode('''
+class A<T> {
+ A.foo();
+}
+
+void bar() {
+ A<int>.foo;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('A<int>.foo;'),
+ elementMatcher(classElement.getNamedConstructor('foo')!,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ );
+ }
+
+ test_class_generic_named_cascade() async {
+ await assertErrorsInCode('''
+class A<T> {
+ A.foo();
+}
+
+void bar() {
+ A<int>..foo;
+}
+''', [
+ error(CompileTimeErrorCode.UNDEFINED_GETTER, 50, 3),
+ ]);
+
+ // The nodes are not rewritten into a [ConstructorReference].
+ var cascade = findNode.cascade('A<int>..foo;');
+ assertType(cascade, 'Type');
+ var section = cascade.cascadeSections.first as PropertyAccess;
+ assertType(section, 'dynamic');
+ assertType(section.propertyName, 'dynamic');
+ }
+
+ test_class_generic_named_nullAware() async {
+ await assertNoErrorsInCode('''
+class A<T> {
+ A.foo();
+}
+
+void bar() {
+ A<int>?.foo;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('A<int>?.foo;'),
+ elementMatcher(classElement.getNamedConstructor('foo')!,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ );
+ }
+
+ test_class_generic_named_typeArgs() async {
+ await assertErrorsInCode('''
+class A<T> {
+ A.foo();
+}
+
+void bar() {
+ A<int>.foo<int>;
+}
+''', [
+ error(CompileTimeErrorCode.DISALLOWED_TYPE_INSTANTIATION_EXPRESSION, 42,
+ 10),
+ // TODO(srawlins): Stop reporting the error below; the code is not
+ // precise, and it is duplicate with the code above.
+ error(
+ CompileTimeErrorCode
+ .WRONG_NUMBER_OF_TYPE_ARGUMENTS_ANONYMOUS_FUNCTION,
+ 52,
+ 5),
+ ]);
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('A<int>.foo<int>;'),
+ elementMatcher(classElement.getNamedConstructor('foo')!,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ );
+ }
+
+ test_class_generic_unnamed() async {
+ await assertNoErrorsInCode('''
+class A<T> {
+ A();
+}
+
+void bar() {
+ A<int>.new;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('A<int>.new;'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ );
+ }
+
+ test_class_generic_unnamed_partOfPropertyAccess() async {
+ await assertNoErrorsInCode('''
+class A<T> {
+ A();
+}
+
+void bar() {
+ A<int>.new.runtimeType;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('A<int>.new'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ );
+ }
+
+ test_class_genericWithBound_unnamed() async {
+ await assertNoErrorsInCode('''
+class A<T extends num> {
+ A();
+}
+
+void bar() {
+ A<int>.new;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('A<int>.new;'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ );
+ }
+
+ test_class_genericWithBound_unnamed_badBound() async {
+ await assertErrorsInCode('''
+class A<T extends num> {
+ A();
+}
+
+void bar() {
+ A<String>.new;
+}
+''', [
+ error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 52, 6),
+ ]);
+
+ var classElement = findElement.class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('A<String>.new;'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'String'}),
+ classElement,
+ 'A<String> Function()',
+ expectedTypeNameType: 'A<String>',
+ );
+ }
+
+ test_prefixedAlias_generic_unnamed() async {
+ newFile('$testPackageLibPath/a.dart', content: '''
+class A<T> {
+ A();
+}
+typedef TA<T> = A<T>;
+''');
+ await assertNoErrorsInCode('''
+import 'a.dart' as a;
+void bar() {
+ a.TA<int>.new;
+}
+''');
+
+ var classElement =
+ findElement.importFind('package:test/a.dart').class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('a.TA<int>.new;'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ expectedPrefix: findElement.import('package:test/a.dart').prefix,
+ expectedTypeNameElement:
+ findElement.importFind('package:test/a.dart').typeAlias('TA'),
+ );
+ }
+
+ test_prefixedClass_generic_named() async {
+ newFile('$testPackageLibPath/a.dart', content: '''
+class A<T> {
+ A.foo();
+}
+''');
+ await assertNoErrorsInCode('''
+import 'a.dart' as a;
+void bar() {
+ a.A<int>.foo;
+}
+''');
+
+ var classElement =
+ findElement.importFind('package:test/a.dart').class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('a.A<int>.foo;'),
+ elementMatcher(classElement.getNamedConstructor('foo')!,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ expectedPrefix: findElement.import('package:test/a.dart').prefix,
+ );
+ }
+
+ test_prefixedClass_generic_unnamed() async {
+ newFile('$testPackageLibPath/a.dart', content: '''
+class A<T> {
+ A();
+}
+''');
+ await assertNoErrorsInCode('''
+import 'a.dart' as a;
+void bar() {
+ a.A<int>.new;
+}
+''');
+
+ var classElement =
+ findElement.importFind('package:test/a.dart').class_('A');
+ assertConstructorReference(
+ findNode.constructorReference('a.A<int>.new;'),
+ elementMatcher(classElement.unnamedConstructor,
+ substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ expectedPrefix: findElement.import('package:test/a.dart').prefix,
+ );
+ }
+}
+
+@reflectiveTest
class ConstructorReferenceResolutionTest extends PubPackageResolutionTest {
test_class_generic_inferFromContext_badTypeArgument() async {
await assertErrorsInCode('''
@@ -41,6 +413,28 @@
);
}
+ test_class_generic_named_inferTypeFromContext() async {
+ await assertNoErrorsInCode('''
+class A<T> {
+ A.foo();
+}
+
+A<int> Function() bar() {
+ return A.foo;
+}
+''');
+
+ var classElement = findElement.class_('A');
+ var constructorElement = classElement.getNamedConstructor('foo')!;
+ assertConstructorReference(
+ findNode.constructorReference('A.foo;'),
+ elementMatcher(constructorElement, substitution: {'T': 'int'}),
+ classElement,
+ 'A<int> Function()',
+ expectedTypeNameType: 'A<int>',
+ );
+ }
+
test_class_generic_named_uninstantiated() async {
await assertNoErrorsInCode('''
class A<T> {
@@ -127,28 +521,6 @@
);
}
- test_classs_generic_named_inferTypeFromContext() async {
- await assertNoErrorsInCode('''
-class A<T> {
- A.foo();
-}
-
-A<int> Function() bar() {
- return A.foo;
-}
-''');
-
- var classElement = findElement.class_('A');
- var constructorElement = classElement.getNamedConstructor('foo')!;
- assertConstructorReference(
- findNode.constructorReference('A.foo;'),
- elementMatcher(constructorElement, substitution: {'T': 'int'}),
- classElement,
- 'A<int> Function()',
- expectedTypeNameType: 'A<int>',
- );
- }
-
test_typeAlias_generic_named_uninstantiated() async {
await assertNoErrorsInCode('''
class A<T, U> {
diff --git a/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart b/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart
index 3675f92..4947dab 100644
--- a/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart
@@ -582,6 +582,20 @@
assertType(reference, 'void Function(dynamic)');
}
+ test_receiverIsDynamic() async {
+ await assertErrorsInCode('''
+bar(dynamic a) {
+ a.foo<int>;
+}
+''', [
+ error(CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
+ 19, 5),
+ ]);
+
+ var reference = findNode.functionReference('a.foo<int>;');
+ assertType(reference, 'dynamic');
+ }
+
test_staticMethod() async {
await assertNoErrorsInCode('''
class A {
diff --git a/pkg/analyzer/test/src/dart/resolution/language_version_test.dart b/pkg/analyzer/test/src/dart/resolution/language_version_test.dart
index c94064b..bafbda4 100644
--- a/pkg/analyzer/test/src/dart/resolution/language_version_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/language_version_test.dart
@@ -18,8 +18,7 @@
}
@reflectiveTest
-class NullSafetyExperimentGlobalTest extends _FeaturesTest
- with WithNullSafetyMixin {
+class NullSafetyExperimentGlobalTest extends _FeaturesTest {
test_jsonConfig_legacyContext_nonNullDependency() async {
_configureTestWithJsonConfig('''
{
diff --git a/pkg/analyzer/test/src/dart/resolution/type_inference/local_variable_test.dart b/pkg/analyzer/test/src/dart/resolution/type_inference/local_variable_test.dart
index 5d3126f..556c1d0 100644
--- a/pkg/analyzer/test/src/dart/resolution/type_inference/local_variable_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/type_inference/local_variable_test.dart
@@ -9,13 +9,25 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(LocalVariableTest);
- defineReflectiveTests(LocalVariableWithNullSafetyTest);
+ defineReflectiveTests(LocalVariableWithoutNullSafetyTest);
});
}
@reflectiveTest
class LocalVariableTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with LocalVariableTestCases {
+ test_Never() async {
+ await resolveTestCode('''
+void f(Never a) {
+ var v = a;
+ v;
+}
+''');
+ _assertTypeOfV('Never');
+ }
+}
+
+mixin LocalVariableTestCases on PubPackageResolutionTest {
test_int() async {
await resolveTestCode('''
void f() {
@@ -43,15 +55,5 @@
}
@reflectiveTest
-class LocalVariableWithNullSafetyTest extends LocalVariableTest
- with WithNullSafetyMixin {
- test_Never() async {
- await resolveTestCode('''
-void f(Never a) {
- var v = a;
- v;
-}
-''');
- _assertTypeOfV('Never');
- }
-}
+class LocalVariableWithoutNullSafetyTest extends PubPackageResolutionTest
+ with LocalVariableTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/experiment_not_enabled_test.dart b/pkg/analyzer/test/src/diagnostics/experiment_not_enabled_test.dart
index a305fc4..4dbaeab 100644
--- a/pkg/analyzer/test/src/diagnostics/experiment_not_enabled_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/experiment_not_enabled_test.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
+import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/context_collection_resolution.dart';
@@ -27,6 +28,7 @@
}
''', [
error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 86, 5),
+ error(CompileTimeErrorCode.UNDEFINED_METHOD, 96, 3),
]);
}
@@ -42,6 +44,7 @@
}
''', [
error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 83, 5),
+ error(CompileTimeErrorCode.UNDEFINED_METHOD, 93, 3),
]);
}
diff --git a/pkg/analyzer/test/src/diagnostics/missing_enum_constant_in_switch_test.dart b/pkg/analyzer/test/src/diagnostics/missing_enum_constant_in_switch_test.dart
index e3523e4..7ea8972 100644
--- a/pkg/analyzer/test/src/diagnostics/missing_enum_constant_in_switch_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/missing_enum_constant_in_switch_test.dart
@@ -10,13 +10,63 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(MissingEnumConstantInSwitchTest);
- defineReflectiveTests(MissingEnumConstantInSwitchWithNullSafetyTest);
+ defineReflectiveTests(MissingEnumConstantInSwitchWithoutNullSafetyTest);
});
}
@reflectiveTest
class MissingEnumConstantInSwitchTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with MissingEnumConstantInSwitchTestCases {
+ test_nullable() async {
+ await assertErrorsInCode('''
+enum E { one, two }
+
+void f(E? e) {
+ switch (e) {
+ case E.one:
+ case E.two:
+ break;
+ }
+}
+''', [
+ error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 38, 10),
+ ]);
+ }
+
+ test_nullable_default() async {
+ await assertNoErrorsInCode('''
+enum E { one, two }
+
+void f(E? e) {
+ switch (e) {
+ case E.one:
+ break;
+ default:
+ break;
+ }
+}
+''');
+ }
+
+ test_nullable_null() async {
+ await assertNoErrorsInCode('''
+enum E { one, two }
+
+void f(E? e) {
+ switch (e) {
+ case E.one:
+ break;
+ case E.two:
+ break;
+ case null:
+ break;
+ }
+}
+''');
+ }
+}
+
+mixin MissingEnumConstantInSwitchTestCases on PubPackageResolutionTest {
test_default() async {
await assertNoErrorsInCode('''
enum E { one, two, three }
@@ -99,53 +149,6 @@
}
@reflectiveTest
-class MissingEnumConstantInSwitchWithNullSafetyTest
- extends MissingEnumConstantInSwitchTest with WithNullSafetyMixin {
- test_nullable() async {
- await assertErrorsInCode('''
-enum E { one, two }
-
-void f(E? e) {
- switch (e) {
- case E.one:
- case E.two:
- break;
- }
-}
-''', [
- error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 38, 10),
- ]);
- }
-
- test_nullable_default() async {
- await assertNoErrorsInCode('''
-enum E { one, two }
-
-void f(E? e) {
- switch (e) {
- case E.one:
- break;
- default:
- break;
- }
-}
-''');
- }
-
- test_nullable_null() async {
- await assertNoErrorsInCode('''
-enum E { one, two }
-
-void f(E? e) {
- switch (e) {
- case E.one:
- break;
- case E.two:
- break;
- case null:
- break;
- }
-}
-''');
- }
-}
+class MissingEnumConstantInSwitchWithoutNullSafetyTest
+ extends PubPackageResolutionTest
+ with MissingEnumConstantInSwitchTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/mixin_of_non_class_test.dart b/pkg/analyzer/test/src/diagnostics/mixin_of_non_class_test.dart
index 53935fa..47c9fd2 100644
--- a/pkg/analyzer/test/src/diagnostics/mixin_of_non_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/mixin_of_non_class_test.dart
@@ -10,13 +10,23 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(MixinOfNonClassTest);
- defineReflectiveTests(MixinOfNonClassWithNullSafetyTest);
+ defineReflectiveTests(MixinOfNonClassWithoutNullSafetyTest);
});
}
@reflectiveTest
class MixinOfNonClassTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with MixinOfNonClassTestCases {
+ test_Never() async {
+ await assertErrorsInCode('''
+class A with Never {}
+''', [
+ error(CompileTimeErrorCode.MIXIN_OF_NON_CLASS, 13, 5),
+ ]);
+ }
+}
+
+mixin MixinOfNonClassTestCases on PubPackageResolutionTest {
test_class() async {
await assertErrorsInCode(r'''
int A = 7;
@@ -165,13 +175,5 @@
}
@reflectiveTest
-class MixinOfNonClassWithNullSafetyTest extends MixinOfNonClassTest
- with WithNullSafetyMixin {
- test_Never() async {
- await assertErrorsInCode('''
-class A with Never {}
-''', [
- error(CompileTimeErrorCode.MIXIN_OF_NON_CLASS, 13, 5),
- ]);
- }
-}
+class MixinOfNonClassWithoutNullSafetyTest extends PubPackageResolutionTest
+ with MixinOfNonClassTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/mixin_super_class_constraint_non_interface_test.dart b/pkg/analyzer/test/src/diagnostics/mixin_super_class_constraint_non_interface_test.dart
index 204e614..08db55e 100644
--- a/pkg/analyzer/test/src/diagnostics/mixin_super_class_constraint_non_interface_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/mixin_super_class_constraint_non_interface_test.dart
@@ -10,18 +10,12 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(MixinSuperClassConstraintNonInterfaceTest);
- defineReflectiveTests(
- MixinSuperClassConstraintNonInterfaceWithNullSafetyTest);
});
}
@reflectiveTest
-class MixinSuperClassConstraintNonInterfaceTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {}
-
-@reflectiveTest
-class MixinSuperClassConstraintNonInterfaceWithNullSafetyTest
- extends MixinSuperClassConstraintNonInterfaceTest with WithNullSafetyMixin {
+class MixinSuperClassConstraintNonInterfaceTest
+ extends PubPackageResolutionTest {
test_Never() async {
await assertErrorsInCode('''
mixin M on Never {}
diff --git a/pkg/analyzer/test/src/diagnostics/non_constant_case_expression_test.dart b/pkg/analyzer/test/src/diagnostics/non_constant_case_expression_test.dart
index d36da75..d77df25 100644
--- a/pkg/analyzer/test/src/diagnostics/non_constant_case_expression_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/non_constant_case_expression_test.dart
@@ -10,13 +10,15 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(NonConstantCaseExpressionTest);
- defineReflectiveTests(NonConstantCaseExpressionWithNullSafetyTest);
+ defineReflectiveTests(NonConstantCaseExpressionWithoutNullSafetyTest);
});
}
@reflectiveTest
class NonConstantCaseExpressionTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with NonConstantCaseExpressionTestCases {}
+
+mixin NonConstantCaseExpressionTestCases on PubPackageResolutionTest {
test_constField() async {
await assertNoErrorsInCode(r'''
void f(C e) {
@@ -66,5 +68,6 @@
}
@reflectiveTest
-class NonConstantCaseExpressionWithNullSafetyTest
- extends NonConstantCaseExpressionTest with WithNullSafetyMixin {}
+class NonConstantCaseExpressionWithoutNullSafetyTest
+ extends PubPackageResolutionTest
+ with NonConstantCaseExpressionTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/not_map_spread_test.dart b/pkg/analyzer/test/src/diagnostics/not_map_spread_test.dart
index c025714..3c008f9 100644
--- a/pkg/analyzer/test/src/diagnostics/not_map_spread_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/not_map_spread_test.dart
@@ -10,13 +10,13 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(NotMapSpreadTest);
- defineReflectiveTests(NotMapSpreadNullSafetyTest);
+ defineReflectiveTests(NotMapSpreadWithoutNullSafetyTest);
});
}
@reflectiveTest
-class NotMapSpreadNullSafetyTest extends NotMapSpreadTest
- with WithNullSafetyMixin {
+class NotMapSpreadTest extends PubPackageResolutionTest
+ with NotMapSpreadTestCases {
test_map_typeParameter_bound_mapQuestion() async {
await assertNoErrorsInCode('''
void f<T extends Map<int, String>?>(T a) {
@@ -27,9 +27,7 @@
}
}
-@reflectiveTest
-class NotMapSpreadTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+mixin NotMapSpreadTestCases on PubPackageResolutionTest {
test_map() async {
await assertNoErrorsInCode('''
var a = {0: 0};
@@ -100,3 +98,7 @@
]);
}
}
+
+@reflectiveTest
+class NotMapSpreadWithoutNullSafetyTest extends PubPackageResolutionTest
+ with NotMapSpreadTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/return_of_invalid_type_from_catch_error_test.dart b/pkg/analyzer/test/src/diagnostics/return_of_invalid_type_from_catch_error_test.dart
index 1e976ed..9a5d8e7 100644
--- a/pkg/analyzer/test/src/diagnostics/return_of_invalid_type_from_catch_error_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/return_of_invalid_type_from_catch_error_test.dart
@@ -10,13 +10,46 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ReturnOfInvalidTypeForCatchErrorTest);
- defineReflectiveTests(ReturnOfInvalidTypeForCatchErrorWithNullSafetyTest);
+ defineReflectiveTests(
+ ReturnOfInvalidTypeForCatchErrorWithoutNullSafetyTest);
});
}
@reflectiveTest
class ReturnOfInvalidTypeForCatchErrorTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with ReturnOfInvalidTypeForCatchErrorTestCases {
+ test_nullableType_emptyBody() async {
+ await assertNoErrorsInCode('''
+void f(Future<int?> future) {
+ future.catchError((e, st) {});
+}
+''');
+ }
+
+ test_nullableType_emptyReturn() async {
+ await assertErrorsInCode('''
+void f(Future<int?> future) {
+ future.catchError((e, st) {
+ return;
+ });
+}
+''', [
+ error(CompileTimeErrorCode.RETURN_WITHOUT_VALUE, 64, 6),
+ ]);
+ }
+
+ test_nullableType_invalidReturnType() async {
+ await assertErrorsInCode('''
+void f(Future<int?> future) {
+ future.catchError((e, st) => '');
+}
+''', [
+ error(HintCode.RETURN_OF_INVALID_TYPE_FROM_CATCH_ERROR, 61, 2),
+ ]);
+ }
+}
+
+mixin ReturnOfInvalidTypeForCatchErrorTestCases on PubPackageResolutionTest {
test_async_okReturnType() async {
await assertNoErrorsInCode('''
void f(Future<int> future) {
@@ -167,35 +200,6 @@
}
@reflectiveTest
-class ReturnOfInvalidTypeForCatchErrorWithNullSafetyTest
- extends ReturnOfInvalidTypeForCatchErrorTest with WithNullSafetyMixin {
- test_nullableType_emptyBody() async {
- await assertNoErrorsInCode('''
-void f(Future<int?> future) {
- future.catchError((e, st) {});
-}
-''');
- }
-
- test_nullableType_emptyReturn() async {
- await assertErrorsInCode('''
-void f(Future<int?> future) {
- future.catchError((e, st) {
- return;
- });
-}
-''', [
- error(CompileTimeErrorCode.RETURN_WITHOUT_VALUE, 64, 6),
- ]);
- }
-
- test_nullableType_invalidReturnType() async {
- await assertErrorsInCode('''
-void f(Future<int?> future) {
- future.catchError((e, st) => '');
-}
-''', [
- error(HintCode.RETURN_OF_INVALID_TYPE_FROM_CATCH_ERROR, 61, 2),
- ]);
- }
-}
+class ReturnOfInvalidTypeForCatchErrorWithoutNullSafetyTest
+ extends PubPackageResolutionTest
+ with ReturnOfInvalidTypeForCatchErrorTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/return_of_invalid_type_test.dart b/pkg/analyzer/test/src/diagnostics/return_of_invalid_type_test.dart
index cfe8902..5f7f7cb 100644
--- a/pkg/analyzer/test/src/diagnostics/return_of_invalid_type_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/return_of_invalid_type_test.dart
@@ -11,13 +11,85 @@
defineReflectiveSuite(() {
defineReflectiveTests(ReturnOfInvalidTypeTest);
defineReflectiveTests(ReturnOfInvalidTypeWithNoImplicitCastsTest);
- defineReflectiveTests(ReturnOfInvalidTypeWithNullSafetyTest);
+ defineReflectiveTests(ReturnOfInvalidTypeWithoutNullSafetyTest);
});
}
@reflectiveTest
class ReturnOfInvalidTypeTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with ReturnOfInvalidTypeTestCases {
+ test_function_async_block_int__to_Future_void() async {
+ await assertErrorsInCode(r'''
+Future<void> f() async {
+ return 0;
+}
+''', [
+ error(CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION, 34, 1),
+ ]);
+ }
+
+ test_function_async_block_void__to_Future_Null() async {
+ await assertErrorsInCode(r'''
+Future<Null> f(void a) async {
+ return a;
+}
+''', [
+ error(CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION, 40, 1),
+ ]);
+ }
+
+ test_function_async_block_void__to_FutureOr_ObjectQ() async {
+ await assertErrorsInCode(r'''
+import 'dart:async';
+
+FutureOr<Object?> f(void a) async {
+ return a;
+}
+''', [
+ error(CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION, 67, 1),
+ ]);
+ }
+
+ test_function_async_expression_dynamic__to_Future_int() async {
+ await assertNoErrorsInCode(r'''
+Future<int> f(dynamic a) async => a;
+''');
+ }
+
+ test_functionExpression_async_futureOr_void__to_Object() async {
+ await assertNoErrorsInCode(r'''
+void a = null;
+
+Object Function() f = () async {
+ return a;
+};
+''');
+ }
+
+ test_functionExpression_async_futureQ_void__to_Object() async {
+ await assertNoErrorsInCode(r'''
+Future<void>? a = (throw 0);
+
+Object Function() f = () async {
+ return a;
+};
+''');
+ }
+
+ test_functionExpression_async_void__to_FutureOr_ObjectQ() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:async';
+
+void a = (throw 0);
+
+FutureOr<Object?> Function() f = () async {
+ return a;
+};
+''');
+ }
+}
+
+mixin ReturnOfInvalidTypeTestCases on PubPackageResolutionTest {
test_closure() async {
await assertErrorsInCode('''
typedef Td = int Function();
@@ -415,75 +487,5 @@
}
@reflectiveTest
-class ReturnOfInvalidTypeWithNullSafetyTest extends ReturnOfInvalidTypeTest
- with WithNullSafetyMixin {
- test_function_async_block_int__to_Future_void() async {
- await assertErrorsInCode(r'''
-Future<void> f() async {
- return 0;
-}
-''', [
- error(CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION, 34, 1),
- ]);
- }
-
- test_function_async_block_void__to_Future_Null() async {
- await assertErrorsInCode(r'''
-Future<Null> f(void a) async {
- return a;
-}
-''', [
- error(CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION, 40, 1),
- ]);
- }
-
- test_function_async_block_void__to_FutureOr_ObjectQ() async {
- await assertErrorsInCode(r'''
-import 'dart:async';
-
-FutureOr<Object?> f(void a) async {
- return a;
-}
-''', [
- error(CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION, 67, 1),
- ]);
- }
-
- test_function_async_expression_dynamic__to_Future_int() async {
- await assertNoErrorsInCode(r'''
-Future<int> f(dynamic a) async => a;
-''');
- }
-
- test_functionExpression_async_futureOr_void__to_Object() async {
- await assertNoErrorsInCode(r'''
-void a = null;
-
-Object Function() f = () async {
- return a;
-};
-''');
- }
-
- test_functionExpression_async_futureQ_void__to_Object() async {
- await assertNoErrorsInCode(r'''
-Future<void>? a = (throw 0);
-
-Object Function() f = () async {
- return a;
-};
-''');
- }
-
- test_functionExpression_async_void__to_FutureOr_ObjectQ() async {
- await assertNoErrorsInCode(r'''
-import 'dart:async';
-
-void a = (throw 0);
-
-FutureOr<Object?> Function() f = () async {
- return a;
-};
-''');
- }
-}
+class ReturnOfInvalidTypeWithoutNullSafetyTest extends PubPackageResolutionTest
+ with ReturnOfInvalidTypeTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/return_type_invalid_for_catch_error_test.dart b/pkg/analyzer/test/src/diagnostics/return_type_invalid_for_catch_error_test.dart
index fd534d0..b5a19e8 100644
--- a/pkg/analyzer/test/src/diagnostics/return_type_invalid_for_catch_error_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/return_type_invalid_for_catch_error_test.dart
@@ -10,13 +10,25 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ReturnTypeInvalidForCatchErrorTest);
- defineReflectiveTests(ReturnTypeInvalidForCatchErrorWithNullSafetyTest);
+ defineReflectiveTests(ReturnTypeInvalidForCatchErrorWithoutNullSafetyTest);
});
}
@reflectiveTest
class ReturnTypeInvalidForCatchErrorTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with ReturnTypeInvalidForCatchErrorTestCases {
+ test_nullableReturnType() async {
+ await assertErrorsInCode('''
+void f(Future<int> future, String? Function(dynamic, StackTrace) cb) {
+ future.catchError(cb);
+}
+''', [
+ error(HintCode.RETURN_TYPE_INVALID_FOR_CATCH_ERROR, 91, 2),
+ ]);
+ }
+}
+
+mixin ReturnTypeInvalidForCatchErrorTestCases on PubPackageResolutionTest {
test_dynamic_returnTypeIsUnrelatedFuture() async {
await assertNoErrorsInCode('''
void f(
@@ -87,15 +99,6 @@
}
@reflectiveTest
-class ReturnTypeInvalidForCatchErrorWithNullSafetyTest
- extends ReturnTypeInvalidForCatchErrorTest with WithNullSafetyMixin {
- test_nullableReturnType() async {
- await assertErrorsInCode('''
-void f(Future<int> future, String? Function(dynamic, StackTrace) cb) {
- future.catchError(cb);
-}
-''', [
- error(HintCode.RETURN_TYPE_INVALID_FOR_CATCH_ERROR, 91, 2),
- ]);
- }
-}
+class ReturnTypeInvalidForCatchErrorWithoutNullSafetyTest
+ extends PubPackageResolutionTest
+ with ReturnTypeInvalidForCatchErrorTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/return_without_value_test.dart b/pkg/analyzer/test/src/diagnostics/return_without_value_test.dart
index f90e7e6..b6149bb 100644
--- a/pkg/analyzer/test/src/diagnostics/return_without_value_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/return_without_value_test.dart
@@ -10,13 +10,15 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ReturnWithoutValueTest);
- defineReflectiveTests(ReturnWithoutValueWithNullSafetyTest);
+ defineReflectiveTests(ReturnWithoutValueWithoutNullSafetyTest);
});
}
@reflectiveTest
class ReturnWithoutValueTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with ReturnWithoutValueTestCases {}
+
+mixin ReturnWithoutValueTestCases on PubPackageResolutionTest {
test_async_futureInt() async {
await assertErrorsInCode('''
Future<int> f() async {
@@ -163,5 +165,5 @@
}
@reflectiveTest
-class ReturnWithoutValueWithNullSafetyTest extends ReturnWithoutValueTest
- with WithNullSafetyMixin {}
+class ReturnWithoutValueWithoutNullSafetyTest extends PubPackageResolutionTest
+ with ReturnWithoutValueTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/sdk_version_never_test.dart b/pkg/analyzer/test/src/diagnostics/sdk_version_never_test.dart
index 4773eae..ad2aa2d 100644
--- a/pkg/analyzer/test/src/diagnostics/sdk_version_never_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/sdk_version_never_test.dart
@@ -11,25 +11,12 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(SdkVersionNeverTest);
- defineReflectiveTests(SdkVersionNeverWithNullSafetyTest);
+ defineReflectiveTests(SdkVersionNeverWithoutNullSafetyTest);
});
}
@reflectiveTest
-class SdkVersionNeverTest extends SdkConstraintVerifierTest
- with WithoutNullSafetyMixin {
- test_languageVersionBeforeNullSafety() async {
- await verifyVersion('2.7.0', r'''
-Never foo;
-''', expectedErrors: [
- error(HintCode.SDK_VERSION_NEVER, 0, 5),
- ]);
- }
-}
-
-@reflectiveTest
-class SdkVersionNeverWithNullSafetyTest extends SdkConstraintVerifierTest
- with WithNullSafetyMixin {
+class SdkVersionNeverTest extends SdkConstraintVerifierTest {
test_experimentEnabled() async {
await verifyVersion('2.7.0', r'''
Never foo = (throw 42);
@@ -45,3 +32,15 @@
]);
}
}
+
+@reflectiveTest
+class SdkVersionNeverWithoutNullSafetyTest extends SdkConstraintVerifierTest
+ with WithoutNullSafetyMixin {
+ test_languageVersionBeforeNullSafety() async {
+ await verifyVersion('2.7.0', r'''
+Never foo;
+''', expectedErrors: [
+ error(HintCode.SDK_VERSION_NEVER, 0, 5),
+ ]);
+ }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/type_parameter_supertype_of_its_bound_test.dart b/pkg/analyzer/test/src/diagnostics/type_parameter_supertype_of_its_bound_test.dart
index 001d947..fdfa182 100644
--- a/pkg/analyzer/test/src/diagnostics/type_parameter_supertype_of_its_bound_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/type_parameter_supertype_of_its_bound_test.dart
@@ -10,13 +10,16 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(TypeParameterSupertypeOfItsBoundTest);
- defineReflectiveTests(TypeParameterSupertypeOfItsBoundWithNullSafetyTest);
+ defineReflectiveTests(
+ TypeParameterSupertypeOfItsBoundWithoutNullSafetyTest);
});
}
@reflectiveTest
class TypeParameterSupertypeOfItsBoundTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with TypeParameterSupertypeOfItsBoundTestCases {}
+
+mixin TypeParameterSupertypeOfItsBoundTestCases on PubPackageResolutionTest {
test_1of1() async {
await assertErrorsInCode(r'''
class A<T extends T> {
@@ -50,5 +53,6 @@
}
@reflectiveTest
-class TypeParameterSupertypeOfItsBoundWithNullSafetyTest
- extends TypeParameterSupertypeOfItsBoundTest with WithNullSafetyMixin {}
+class TypeParameterSupertypeOfItsBoundWithoutNullSafetyTest
+ extends PubPackageResolutionTest
+ with TypeParameterSupertypeOfItsBoundTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/undefined_hidden_name_test.dart b/pkg/analyzer/test/src/diagnostics/undefined_hidden_name_test.dart
index 6fcfad3..cf1eba2 100644
--- a/pkg/analyzer/test/src/diagnostics/undefined_hidden_name_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/undefined_hidden_name_test.dart
@@ -10,13 +10,15 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(UndefinedHiddenNameTest);
- defineReflectiveTests(UndefinedHiddenNameWithNullSafetyTest);
+ defineReflectiveTests(UndefinedHiddenNameWithoutNullSafetyTest);
});
}
@reflectiveTest
class UndefinedHiddenNameTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with UndefinedHiddenNameTestCases {}
+
+mixin UndefinedHiddenNameTestCases on PubPackageResolutionTest {
test_export() async {
newFile('$testPackageLibPath/lib1.dart');
await assertErrorsInCode(r'''
@@ -38,5 +40,5 @@
}
@reflectiveTest
-class UndefinedHiddenNameWithNullSafetyTest extends UndefinedHiddenNameTest
- with WithNullSafetyMixin {}
+class UndefinedHiddenNameWithoutNullSafetyTest extends PubPackageResolutionTest
+ with UndefinedHiddenNameTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/undefined_identifier_test.dart b/pkg/analyzer/test/src/diagnostics/undefined_identifier_test.dart
index 2b0b5a5..c339039 100644
--- a/pkg/analyzer/test/src/diagnostics/undefined_identifier_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/undefined_identifier_test.dart
@@ -11,13 +11,38 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(UndefinedIdentifierTest);
- defineReflectiveTests(UndefinedIdentifierWithNullSafetyTest);
+ defineReflectiveTests(UndefinedIdentifierWithoutNullSafetyTest);
});
}
@reflectiveTest
class UndefinedIdentifierTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with UndefinedIdentifierTestCases {
+ test_get_from_external_variable_final_valid() async {
+ await assertNoErrorsInCode('''
+external final int x;
+int f() => x;
+''');
+ }
+
+ test_get_from_external_variable_valid() async {
+ await assertNoErrorsInCode('''
+external int x;
+int f() => x;
+''');
+ }
+
+ test_set_external_variable_valid() async {
+ await assertNoErrorsInCode('''
+external int x;
+void f(int value) {
+ x = value;
+}
+''');
+ }
+}
+
+mixin UndefinedIdentifierTestCases on PubPackageResolutionTest {
test_annotation_favors_scope_resolution_over_this_resolution_class() async {
// If an annotation on a class type parameter cannot be resolved using the
// normal scope resolution mechanism, it is resolved via implicit `this`.
@@ -397,28 +422,5 @@
}
@reflectiveTest
-class UndefinedIdentifierWithNullSafetyTest extends UndefinedIdentifierTest
- with WithNullSafetyMixin {
- test_get_from_external_variable_final_valid() async {
- await assertNoErrorsInCode('''
-external final int x;
-int f() => x;
-''');
- }
-
- test_get_from_external_variable_valid() async {
- await assertNoErrorsInCode('''
-external int x;
-int f() => x;
-''');
- }
-
- test_set_external_variable_valid() async {
- await assertNoErrorsInCode('''
-external int x;
-void f(int value) {
- x = value;
-}
-''');
- }
-}
+class UndefinedIdentifierWithoutNullSafetyTest extends PubPackageResolutionTest
+ with UndefinedIdentifierTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/undefined_shown_name_test.dart b/pkg/analyzer/test/src/diagnostics/undefined_shown_name_test.dart
index ea89f9a..f486367 100644
--- a/pkg/analyzer/test/src/diagnostics/undefined_shown_name_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/undefined_shown_name_test.dart
@@ -10,13 +10,15 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(UndefinedShownNameTest);
- defineReflectiveTests(UndefinedShownNameWithNullSafetyTest);
+ defineReflectiveTests(UndefinedShownNameWithoutNullSafetyTest);
});
}
@reflectiveTest
class UndefinedShownNameTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with UndefinedShownNameTestCases {}
+
+mixin UndefinedShownNameTestCases on PubPackageResolutionTest {
test_export() async {
newFile('$testPackageLibPath/lib1.dart');
await assertErrorsInCode(r'''
@@ -38,5 +40,5 @@
}
@reflectiveTest
-class UndefinedShownNameWithNullSafetyTest extends UndefinedShownNameTest
- with WithNullSafetyMixin {}
+class UndefinedShownNameWithoutNullSafetyTest extends PubPackageResolutionTest
+ with UndefinedShownNameTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/unnecessary_type_check_test.dart b/pkg/analyzer/test/src/diagnostics/unnecessary_type_check_test.dart
index 65e5479..fef92e8 100644
--- a/pkg/analyzer/test/src/diagnostics/unnecessary_type_check_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/unnecessary_type_check_test.dart
@@ -10,15 +10,36 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(UnnecessaryTypeCheckFalseTest);
- defineReflectiveTests(UnnecessaryTypeCheckFalseWithNullSafetyTest);
+ defineReflectiveTests(UnnecessaryTypeCheckFalseWithoutNullSafetyTest);
defineReflectiveTests(UnnecessaryTypeCheckTrueTest);
- defineReflectiveTests(UnnecessaryTypeCheckTrueWithNullSafetyTest);
+ defineReflectiveTests(UnnecessaryTypeCheckTrueWithoutNullSafetyTest);
});
}
@reflectiveTest
class UnnecessaryTypeCheckFalseTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with UnnecessaryTypeCheckFalseTestCases {
+ @override
+ test_type_not_object() async {
+ await assertNoErrorsInCode(r'''
+void f<T>(T a) {
+ a is! Object;
+}
+''');
+ }
+
+ test_type_not_objectQuestion() async {
+ await assertErrorsInCode(r'''
+void f<T>(T a) {
+ a is! Object?;
+}
+''', [
+ error(HintCode.UNNECESSARY_TYPE_CHECK_FALSE, 19, 13),
+ ]);
+ }
+}
+
+mixin UnnecessaryTypeCheckFalseTestCases on PubPackageResolutionTest {
test_null_not_Null() async {
await assertErrorsInCode(r'''
var b = null is! Null;
@@ -49,31 +70,34 @@
}
@reflectiveTest
-class UnnecessaryTypeCheckFalseWithNullSafetyTest
- extends UnnecessaryTypeCheckFalseTest with WithNullSafetyMixin {
+class UnnecessaryTypeCheckFalseWithoutNullSafetyTest
+ extends PubPackageResolutionTest
+ with UnnecessaryTypeCheckFalseTestCases, WithoutNullSafetyMixin {}
+
+@reflectiveTest
+class UnnecessaryTypeCheckTrueTest extends PubPackageResolutionTest
+ with UnnecessaryTypeCheckTrueTestCases {
@override
- test_type_not_object() async {
+ test_type_is_object() async {
await assertNoErrorsInCode(r'''
void f<T>(T a) {
- a is! Object;
+ a is Object;
}
''');
}
- test_type_not_objectQuestion() async {
+ test_type_is_objectQuestion() async {
await assertErrorsInCode(r'''
void f<T>(T a) {
- a is! Object?;
+ a is Object?;
}
''', [
- error(HintCode.UNNECESSARY_TYPE_CHECK_FALSE, 19, 13),
+ error(HintCode.UNNECESSARY_TYPE_CHECK_TRUE, 19, 12),
]);
}
}
-@reflectiveTest
-class UnnecessaryTypeCheckTrueTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+mixin UnnecessaryTypeCheckTrueTestCases on PubPackageResolutionTest {
test_null_is_Null() async {
await assertErrorsInCode(r'''
var b = null is Null;
@@ -104,24 +128,6 @@
}
@reflectiveTest
-class UnnecessaryTypeCheckTrueWithNullSafetyTest
- extends UnnecessaryTypeCheckTrueTest with WithNullSafetyMixin {
- @override
- test_type_is_object() async {
- await assertNoErrorsInCode(r'''
-void f<T>(T a) {
- a is Object;
-}
-''');
- }
-
- test_type_is_objectQuestion() async {
- await assertErrorsInCode(r'''
-void f<T>(T a) {
- a is Object?;
-}
-''', [
- error(HintCode.UNNECESSARY_TYPE_CHECK_TRUE, 19, 12),
- ]);
- }
-}
+class UnnecessaryTypeCheckTrueWithoutNullSafetyTest
+ extends PubPackageResolutionTest
+ with UnnecessaryTypeCheckTrueTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/analyzer/test/src/diagnostics/yield_of_invalid_type_test.dart b/pkg/analyzer/test/src/diagnostics/yield_of_invalid_type_test.dart
index 072b733..ddb8c9c 100644
--- a/pkg/analyzer/test/src/diagnostics/yield_of_invalid_type_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/yield_of_invalid_type_test.dart
@@ -10,13 +10,15 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(YieldOfInvalidTypeTest);
- defineReflectiveTests(YieldOfInvalidTypeWithNullSafetyTest);
+ defineReflectiveTests(YieldOfInvalidTypeWithoutNullSafetyTest);
});
}
@reflectiveTest
class YieldOfInvalidTypeTest extends PubPackageResolutionTest
- with WithoutNullSafetyMixin {
+ with YieldOfInvalidTypeTestCases {}
+
+mixin YieldOfInvalidTypeTestCases on PubPackageResolutionTest {
test_none_asyncStar_dynamic_to_streamInt() async {
await assertErrorsInCode(
'''
@@ -448,5 +450,5 @@
}
@reflectiveTest
-class YieldOfInvalidTypeWithNullSafetyTest extends YieldOfInvalidTypeTest
- with WithNullSafetyMixin {}
+class YieldOfInvalidTypeWithoutNullSafetyTest extends PubPackageResolutionTest
+ with YieldOfInvalidTypeTestCases, WithoutNullSafetyMixin {}
diff --git a/pkg/compiler/lib/src/deferred_load/deferred_load.dart b/pkg/compiler/lib/src/deferred_load/deferred_load.dart
index 22d19c6..c531ba2 100644
--- a/pkg/compiler/lib/src/deferred_load/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load/deferred_load.dart
@@ -2,6 +2,270 @@
// 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.
+/// *Overview of deferred loading*
+///
+/// Deferred loading allows developers to specify deferred imports. These
+/// imports represent explicit asynchronous splits of the application that
+/// allows code to be delivered in pieces.
+///
+/// The initial download of an application will exclude code used only by
+/// deferred imports. As the application reaches a
+/// `deferred_import.loadLibrary()` instruction, it will download and initialize
+/// any code needed by that deferred import.
+///
+/// Very often separate deferred imports access common code. When that happens,
+/// the compiler places the shared code in separate files. At runtime, the
+/// application will only download shared code once when the first deferred
+/// import that needs that code gets loaded. To achieve this, the compiler
+/// generates _load lists_: a list of JavaScript files that need to be
+/// downloaded for every deferred import in the program.
+///
+/// Each generated JavaScript file has an initialzation within it. The files can
+/// be concatenated together in a bundle without affecting the initialization
+/// logic. This is used by customers to reduce the download latency when they
+/// know that multiple files will be loaded at once.
+///
+/// *The code splitting algorithm*
+///
+/// The goal of this library and the [DeferredLoadingTask] is to determine how
+/// to best split code in multiple files according to the principles described
+/// above.
+///
+/// We do so by partitioning code into output units ([OutputUnit]s in our
+/// implementation). The partitioning reflects how code is shared between
+/// different deferred imports. Each output unit is associated a set of deferred
+/// imports (an [ImportSet] in our implementation). These are the deferred
+/// imports that need the code that is stored in that output unit. Code that is
+/// needed by a single deferred import, will be associated with a set containing
+/// that deferred import only (a singleton set), but code that is shared by 10
+/// deferred imports will be associated with a set containing all of those
+/// imports instead. We determine whether code is shared based on how code is
+/// accessed in the program. An element is considered to be accessed by a
+/// deferred import if it is either loaded and invoked from that import or
+/// transitively accessed by an element that was invoked by that import.
+///
+/// In theory, there could be an exponential number of output units: one per
+/// subset of deferred imports in the program. In practice, large apps do have a
+/// large number of output units, but the result is not exponential. This is
+/// both because not all deferred imports have code in common and because many
+/// deferred imports end up having the same code in common.
+///
+/// *Main output unit*:
+///
+/// The main output unit contains any code accessed directly from main. Such
+/// code may be accessed by deferred imports too, but because it is accessed
+/// from the main entrypoint of the program, possibly synchronously, we do not
+/// split out the code or defer it. Our current implementation uses an empty
+/// import-set as a sentinel value to represent this output unit.
+///
+/// *Dependency graph*:
+///
+/// We use the element model to discover dependencies between elements.
+/// We distinguish two kinds of dependencies: deferred or direct (aka.
+/// non-deferred):
+///
+/// * Deferred dependencies are only used to discover root elements. Roots
+/// are elements immediately loaded and used from deferred import prefixes in
+/// a program.
+///
+/// * Direct dependencies are used to recursively update which output unit
+/// should be associated with an element.
+///
+/// *Algorithm Principle*:
+///
+/// Conceptually the algorithm consists of associating an element with an
+/// import-set. When we discover a root, we mark it and everything it can reach
+/// as being used by that import. Marking elements as used by that import
+/// consists of adding the import to the import-set of all those reachable
+/// elements.
+///
+/// An earlier version of this algorithm was implemented with this simple
+/// approach: we kept a map from entities to a [Set] of imports and updated the
+/// sets iteratively. However, as customer applications grew, we needed a more
+/// specialized and efficient implementation.
+///
+/// *ImportSet representation and related optimizations*:
+///
+/// The most important change to scale the algorithm was to use an efficient
+/// representation of entity to import-set associations. For large apps there
+/// are a lot of entities, and the simple representation of having a [Set] per
+/// entity was too expensive. We observed that many of such sets had the same
+/// imports (which makes sense given that many elements ended up together in the
+/// same output units). This led us to design the [ImportSet] abstraction: a
+/// representation of import-sets that guarantees that each import-set has a
+/// canonical representation. Memory-wise this was a big win: we now bounded
+/// the heap utilization to one [ImportSet] instance per unique import-set.
+///
+/// This representation is not perfect. Simple operations, like adding an import
+/// to an import-set, are now worse-case linear. So it was important to add a
+/// few optimizations in the algorithm in order to adapt to the new
+/// representation.
+///
+/// The principle of our optimizations is to make bulk updates. Rather than
+/// adding an import at a time for all reachable elements, we changed the
+/// algorithm to make updates in bulk in two ways:
+///
+/// * Batch unions: when possible add more than one import at once, and
+///
+/// * Update elements in segments: when an element and its reachable
+/// dependencies would change in the same way, update them all together.
+///
+/// To achieve these bulk updates, the algorithm uses a two tier algorithm:
+///
+/// * The top tier uses a worklist to track the start of a bulk update, either
+/// from a root (entities that dominate code used by a single deferred import)
+/// or from a merge point in the dependency graph (entities that dominate
+/// shared code between multiple imports).
+///
+/// * The second tier is where bulk updates are made, these don't use a
+/// worklist, but simply a DFS recursive traversal of the dependency graph.
+/// The DFS traversal stops at merge points and makes note of them by
+/// updating the top tier worklist.
+///
+///
+/// *Example*:
+///
+/// Consider this dependency graph (ignoring elements in the main output unit):
+///
+/// deferred import A: a1 ---> s1 ---> s2 -> s3
+/// ^ ^
+/// | |
+/// deferred import B: b1 -----+ |
+/// |
+/// deferred import C: c1 ---> c2 ---> c3
+///
+/// Here a1, b1, and c1 are roots, while s1 and s2 are merge points. The
+/// algorithm will compute a result with 5 deferred output units:
+//
+/// * unit {A}: contains a1
+/// * unit {B}: contains b1
+/// * unit {C}: contains c1, c2, and c3
+/// * unit {A, B}: contains s1
+/// * unit {A, B, C}: contains s2, and s3
+///
+/// After marking everything reachable from main as part of the main output
+/// unit, our algorithm will work as follows:
+///
+/// * Initially all deferred elements have no mapping.
+/// * We make note of work to do, initially to mark the root of each
+/// deferred import:
+/// * a1 with A, and recurse from there.
+/// * b1 with B, and recurse from there.
+/// * c1 with C, and recurse from there.
+/// * We update a1, s1, s2, s3 in bulk, from no mapping to {A}.
+/// * We update b1 from no mapping to {B}, and when we find s1 we notice
+/// that s1 is already associated with another import set {A}. This is a
+/// merge point that can't be updated in bulk, so we make
+/// note of additional work for later to mark s1 with {A, B}
+/// * We update in bulk c1, c2, c3 to {C}, and make a note to update s2 with
+/// {A, C} (another merge point).
+/// * We update s1 to {A, B}, and update the existing note to update s2, now
+/// with {A, B, C}
+/// * Finally we update s2 and s3 with {A, B, C} in bulk, without ever
+/// updating them to the intermediate state {A, C}.
+///
+/// *How bulk segment updates work?*
+///
+/// The principle of the bulk segment update is similar to memoizing the result
+/// of a union operation. We replace a union operation with a cached result if
+/// we can tell that the inputs to the operation are the same.
+///
+/// Our implementation doesn't use a cache table to memoize arbitrary unions.
+/// Instead it only memoizes one union at a time: it tries to reuse the result
+/// of a union applied to one entity, when updating the import-sets of its
+/// transitive dependencies.
+///
+/// Consider a modification of the example above where we add s4 and s5 as
+/// additional dependencies of s3. Conceptually, we are applying this sequence
+/// of union operations:
+///
+/// importSet[s2] = importSet[s2] UNION {B, C}
+/// importSet[s3] = importSet[s3] UNION {B, C}
+/// importSet[s4] = importSet[s4] UNION {B, C}
+/// importSet[s5] = importSet[s5] UNION {B, C}
+///
+/// When the algorithm is updating s2, it checks whether any of the entities
+/// reachable from s2 also have the same import-set as s2, and if so, we know
+/// that the union result is the same.
+///
+/// Our implementation uses the term `oldSet` to represent the first input of
+/// the memoized union operation, and `newSet` to represent the result:
+///
+/// oldSet = importSet[s2] // = A
+/// newSet = oldSet UNION {B, C} // = {A, B, C}
+///
+/// Then the updates are encoded as:
+///
+/// update(s2, oldSet, newSet);
+/// update(s3, oldSet, newSet);
+/// update(s4, oldSet, newSet);
+/// update(s5, oldSet, newSet);
+///
+/// where:
+///
+/// update(s5, oldSet, newSet) {
+/// var currentSet = importSet[s];
+/// if (currentSet == oldSet) {
+/// // Use the memoized result, whohoo!
+/// importSet[s] = newSet;
+/// } else {
+/// // Don't use the memoized result, instead use the worklist to later
+/// // update `s` with the appropriate union operation.
+/// }
+/// }
+///
+/// As a result of this, the update to the import set for s2, s3, s4 and s5
+/// becomes a single if-check and an assignment, but the union operation was
+/// only executed once.
+///
+/// *Constraints*:
+///
+/// By default our algorithm considers all deferred imports equally and
+/// potentially occurring at any time in the application lifetime. In practice,
+/// apps use deferred imports to layer the load of their application and, often,
+/// developers know how imports will be loaded over time.
+///
+/// Dart2js accepts a configuration file to specify constraints about deferred
+/// imports. There are many kinds of constraints that help developers encode how
+/// their applications work.
+///
+/// To model constraints, the deferred loading algorithm was changed to include
+/// _set transitions_: these are changes made to import-sets to effectively
+/// encode the constraints.
+///
+/// Consider, for example, a program with two deferred imports `A` and `B`. Our
+/// unconstrained algorithm will split the code in 3 files:
+///
+/// * code unique to `A` (represented by the import set `{A}`)
+///
+/// * code unique to `B` (represented by the import set `{B}`)
+///
+/// * code shared between `A and `B (represented by the import set `{A, B}`)
+///
+/// When an end-user loads the user journey corresponding to `A`, the code for
+/// `{A}` and `{A,B}` gets loaded. When they load the user journey corresponding
+/// to `B`, `{B}` and `{A, B}` gets loaded.
+///
+/// An ordering constraint saying that `B` always loads after `A` tells our
+/// algorithm that, even though there exists code that is unique to `A`, we
+/// could merge it together with the shared code between `A` and `B`, since the
+/// user never intends to load `B` first. The result would be to have two files
+/// instead:
+///
+/// * code unique to `B` (represented by the import set `{B}`)
+///
+/// * code unique to A and code shared between A and B (represented by the
+/// import set `{A, B}`)
+///
+///
+/// In this example, the set transition is to convert any set containing `{A}`
+/// into a set containing `{A, B}`.
+///
+// TODO(joshualitt): update doc above when main is represented by a set
+// containing an implict import corresponding to `main`.
+// TODO(sigmund): investigate different heuristics for how to select the next
+// work item (e.g. we might converge faster if we pick first the update that
+// contains a bigger delta.)
library deferred_load;
import 'dart:collection' show Queue;
@@ -662,81 +926,7 @@
/// Performs the deferred loading algorithm.
///
- /// The deferred loading algorithm maps elements and constants to an output
- /// unit. Each output unit is identified by a subset of deferred imports (an
- /// [ImportSet]), and they will contain the elements that are inherently used
- /// by all those deferred imports. An element is used by a deferred import if
- /// it is either loaded by that import or transitively accessed by an element
- /// that the import loads. An empty set represents the main output unit,
- /// which contains any elements that are accessed directly and are not
- /// deferred.
- ///
- /// The algorithm traverses the element model recursively looking for
- /// dependencies between elements. These dependencies may be deferred or
- /// non-deferred. Deferred dependencies are mainly used to discover the root
- /// elements that are loaded from deferred imports, while non-deferred
- /// dependencies are used to recursively associate more elements to output
- /// units.
- ///
- /// Naively, the algorithm traverses each root of a deferred import and marks
- /// everything it can reach as being used by that import. To reduce how many
- /// times we visit an element, we use an algorithm that works in segments: it
- /// marks elements with a subset of deferred imports at a time, until it
- /// detects a merge point where more deferred imports could be considered at
- /// once.
- ///
- /// For example, consider this dependency graph (ignoring elements in the main
- /// output unit):
- ///
- /// deferred import A: a1 ---> s1 ---> s2 -> s3
- /// ^ ^
- /// | |
- /// deferred import B: b1 -----+ |
- /// |
- /// deferred import C: c1 ---> c2 ---> c3
- ///
- /// The algorithm will compute a result with 5 deferred output units:
- //
- /// * unit {A}: contains a1
- /// * unit {B}: contains b1
- /// * unit {C}: contains c1, c2, and c3
- /// * unit {A, B}: contains s1
- /// * unit {A, B, C}: contains s2, and s3
- ///
- /// After marking everything reachable from main as part of the main output
- /// unit, our algorithm will work as follows:
- ///
- /// * Initially all deferred elements have no mapping.
- /// * We make note of work to do, initially to mark the root of each
- /// deferred import:
- /// * a1 with A, and recurse from there.
- /// * b1 with B, and recurse from there.
- /// * c1 with C, and recurse from there.
- /// * we update a1, s1, s2, s3 from no mapping to {A}
- /// * we update b1 from no mapping to {B}, and when we find s1 we notice
- /// that s1 is already associated with another import set {A}, so we make
- /// note of additional work for later to mark s1 with {A, B}
- /// * we update c1, c2, c3 to {C}, and make a note to update s2 with {A, C}
- /// * we update s1 to {A, B}, and update the existing note to update s2, now
- /// with {A, B, C}
- /// * finally we update s2 and s3 with {A, B, C} in one go, without ever
- /// updating them to the intermediate state {A, C}.
- ///
- /// The implementation below does atomic updates from one import-set to
- /// another. At first we add one deferred import at a time, but as the
- /// algorithm progesses it may update a small import-set with a larger
- /// import-set in one go. The key of this algorithm is to detect when sharing
- /// begins, so we can update those elements more efficently.
- ///
- /// To detect these merge points where sharing begins, the implementation
- /// below uses `a swap operation`: we first compare what the old import-set
- /// is, and if it matches our expectation, the swap is done and we recurse,
- /// otherwise a merge root was detected and we enqueue a new segment of
- /// updates for later.
- ///
- /// TODO(sigmund): investigate different heuristics for how to select the next
- /// work item (e.g. we might converge faster if we pick first the update that
- /// contains a bigger delta.)
+ /// See the top-level library comment for details.
OutputUnitData run(FunctionEntity main, KClosedWorld closedWorld) {
return metrics.time.measure(() => _run(main, closedWorld));
}
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index b8c01a5..2d8e462 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -367,7 +367,7 @@
) async {
switch (request.command) {
- /// Used by tests to validate available protocols (eg. DDS). There may be
+ /// Used by tests to validate available protocols (e.g. DDS). There may be
/// value in making this available to clients in future, but for now it's
/// internal.
case '_getSupportedProtocols':
@@ -546,11 +546,11 @@
),
],
supportsClipboardContext: true,
- // TODO(dantup): All of these...
- // supportsConditionalBreakpoints: true,
+ supportsConditionalBreakpoints: true,
supportsConfigurationDoneRequest: true,
supportsDelayedStackTraceLoading: true,
supportsEvaluateForHovers: true,
+ // TODO(dantup): All of these...
// supportsLogPoints: true,
// supportsRestartFrame: true,
supportsTerminateRequest: true,
@@ -560,6 +560,36 @@
sendEvent(InitializedEventBody());
}
+ /// Checks whether this library is from an external package.
+ ///
+ /// This is used to support debugging "Just My Code" so Pub packages can be
+ /// marked as not-debuggable.
+ ///
+ /// A library is considered local if the path is within the 'cwd' or
+ /// 'additionalProjectPaths' in the launch arguments. An editor should include
+ /// the paths of all open workspace folders in 'additionalProjectPaths' to
+ /// support this feature correctly.
+ bool isExternalPackageLibrary(Uri uri) {
+ if (!uri.isScheme('package')) {
+ return false;
+ }
+ final libraryPath = resolvePackageUri(uri);
+ if (libraryPath == null) {
+ return false;
+ }
+
+ // Always compare paths case-insensitively to avoid any issues where APIs
+ // may have returned different casing (e.g. Windows drive letters). It's
+ // almost certain a user wouldn't have a "local" package and an "external"
+ // package with paths differing only be case.
+ final libraryPathLower = libraryPath.toLowerCase();
+ return !projectPaths.any((projectPath) =>
+ path.isWithin(projectPath.toLowerCase(), libraryPathLower));
+ }
+
+ /// Checks whether this library is from the SDK.
+ bool isSdkLibrary(Uri uri) => uri.isScheme('dart');
+
/// Overridden by sub-classes to handle when the client sends a
/// `launchRequest` (a request to start running/debugging an app).
///
@@ -588,6 +618,20 @@
sendResponse();
}
+ /// Checks whether a library URI should be considered debuggable.
+ ///
+ /// Initial values are provided in the launch arguments, but may be updated
+ /// by the `updateDebugOptions` custom request.
+ bool libaryIsDebuggable(Uri uri) {
+ if (isSdkLibrary(uri)) {
+ return _isolateManager.debugSdkLibraries;
+ } else if (isExternalPackageLibrary(uri)) {
+ return _isolateManager.debugExternalPackageLibraries;
+ } else {
+ return true;
+ }
+ }
+
/// Handles the clients "next" ("step over") request for the thread in
/// [args.threadId].
@override
@@ -831,7 +875,7 @@
// should return all.
final limit = numFrames == 0 ? null : startFrame + numFrames;
final stack = await vmService?.getStack(thread.isolate.id!, limit: limit);
- final frames = stack?.frames;
+ final frames = stack?.asyncCausalFrames ?? stack?.frames;
if (stack != null && frames != null) {
// When the call stack is truncated, we always add [stackFrameBatchSize]
@@ -847,10 +891,19 @@
? frames.length + stackFrameBatchSize
: frames.length;
+ // Find the first async marker, because some functionality only works
+ // up until the first async bounday (e.g. rewind) since we're showing
+ // the user async frames which are out-of-sync with the real frames
+ // past that point.
+ final firstAsyncMarkerIndex = frames.indexWhere(
+ (frame) => frame.kind == vm.FrameKind.kAsyncSuspensionMarker,
+ );
+
Future<StackFrame> convert(int index, vm.Frame frame) async {
return _converter.convertVmToDapStackFrame(
thread,
frame,
+ firstAsyncMarkerIndex: firstAsyncMarkerIndex,
isTopFrame: startFrame == 0 && index == 0,
);
}
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli.dart b/pkg/dds/lib/src/dap/adapters/dart_cli.dart
index 9ad8e80..a44f520 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli.dart
@@ -21,7 +21,7 @@
/// The location of the vm-service-info file (if debugging).
///
- /// This may be provided by the user (eg. if attaching) or generated by the DA.
+ /// This may be provided by the user (e.g. if attaching) or generated by the DA.
File? _vmServiceInfoFile;
/// A watcher for [_vmServiceInfoFile] to detect when the VM writes the service
@@ -68,7 +68,7 @@
if (!isAttach) {
// Capture the PID from the VM Service so that we can terminate it when
// cleaning up. Terminating the process might not be enough as it could be
- // just a shell script (eg. pub on Windows) and may not pass the
+ // just a shell script (e.g. pub on Windows) and may not pass the
// signal on correctly.
// See: https://github.com/Dart-Code/Dart-Code/issues/907
final pid = vmInfo.pid;
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index 6861be1..e8d06fe 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -4,7 +4,6 @@
import 'dart:async';
-import 'package:path/path.dart' as path;
import 'package:vm_service/vm_service.dart' as vm;
import 'adapters/dart.dart';
@@ -56,6 +55,10 @@
/// isolates that appear after initial breakpoints were sent.
final Map<String, List<SourceBreakpoint>> _clientBreakpointsByUri = {};
+ /// Tracks client breakpoints by the ID assigned by the VM so we can look up
+ /// conditions/logpoints when hitting breakpoints.
+ final Map<String, SourceBreakpoint> _clientBreakpointsByVmId = {};
+
/// Tracks breakpoints created in the VM so they can be removed when the
/// editor sends new breakpoints (currently the editor just sends a new list
/// and not requests to add/remove).
@@ -272,6 +275,27 @@
ThreadInfo? threadForIsolate(vm.IsolateRef? isolate) =>
isolate?.id != null ? _threadsByIsolateId[isolate!.id!] : null;
+ /// Evaluates breakpoint condition [condition] and returns whether the result
+ /// is true (or non-zero for a numeric), sending any evaluation error to the
+ /// client.
+ Future<bool> _breakpointConditionEvaluatesTrue(
+ ThreadInfo thread,
+ String condition,
+ ) async {
+ final result =
+ await _evaluateAndPrintErrors(thread, condition, 'condition');
+ if (result == null) {
+ return false;
+ }
+
+ // Values we consider true for breakpoint conditions are boolean true,
+ // or non-zero numerics.
+ return (result.kind == vm.InstanceKind.kBool &&
+ result.valueAsString == 'true') ||
+ (result.kind == vm.InstanceKind.kInt && result.valueAsString != '0') ||
+ (result.kind == vm.InstanceKind.kDouble && result.valueAsString != '0');
+ }
+
/// Configures a new isolate, setting it's exception-pause mode, which
/// libraries are debuggable, and sending all breakpoints.
Future<void> _configureIsolate(vm.IsolateRef isolate) async {
@@ -282,6 +306,44 @@
], eagerError: true);
}
+ /// Evaluates an expression, returning the result if it is a [vm.InstanceRef]
+ /// and sending any error as an [OutputEvent].
+ Future<vm.InstanceRef?> _evaluateAndPrintErrors(
+ ThreadInfo thread,
+ String expression,
+ String type,
+ ) async {
+ try {
+ final result = await _adapter.vmService?.evaluateInFrame(
+ thread.isolate.id!,
+ 0,
+ expression,
+ disableBreakpoints: true,
+ );
+
+ if (result is vm.InstanceRef) {
+ return result;
+ } else if (result is vm.ErrorRef) {
+ final message = result.message ?? '<error ref>';
+ _adapter.sendOutput(
+ 'console',
+ 'Debugger failed to evaluate breakpoint $type "$expression": $message',
+ );
+ } else if (result is vm.Sentinel) {
+ final message = result.valueAsString ?? '<collected>';
+ _adapter.sendOutput(
+ 'console',
+ 'Debugger failed to evaluate breakpoint $type "$expression": $message',
+ );
+ }
+ } catch (e) {
+ _adapter.sendOutput(
+ 'console',
+ 'Debugger failed to evaluate breakpoint $type "$expression": $e',
+ );
+ }
+ }
+
void _handleExit(vm.Event event) {
final isolate = event.isolate!;
final isolateId = isolate.id!;
@@ -341,6 +403,20 @@
if (eventKind == vm.EventKind.kPauseBreakpoint &&
(event.pauseBreakpoints?.isNotEmpty ?? false)) {
reason = 'breakpoint';
+ // Look up the client breakpoints that correspond to the VM breakpoint(s)
+ // we hit. It's possible some of these may be missing because we could
+ // hit a breakpoint that was set before we were attached.
+ final breakpoints = event.pauseBreakpoints!
+ .map((bp) => _clientBreakpointsByVmId[bp.id!])
+ .toSet();
+
+ // Resume if there are no (non-logpoint) breakpoints, of any of the
+ // breakpoints don't have false conditions.
+ if (breakpoints.isEmpty ||
+ !await _shouldHitBreakpoint(thread, breakpoints)) {
+ await resumeThread(thread.threadId);
+ return;
+ }
} else if (eventKind == vm.EventKind.kPauseBreakpoint) {
reason = 'step';
} else if (eventKind == vm.EventKind.kPauseException) {
@@ -372,55 +448,6 @@
}
}
- /// Checks whether this library is from an external package.
- ///
- /// This is used to support debugging "Just My Code" so Pub packages can be
- /// marked as not-debuggable.
- ///
- /// A library is considered local if the path is within the 'cwd' or
- /// 'additionalProjectPaths' in the launch arguments. An editor should include
- /// the paths of all open workspace folders in 'additionalProjectPaths' to
- /// support this feature correctly.
- bool _isExternalPackageLibrary(vm.LibraryRef library) {
- final libraryUri = library.uri;
- if (libraryUri == null) {
- return false;
- }
- final uri = Uri.parse(libraryUri);
- if (!uri.isScheme('package')) {
- return false;
- }
- final libraryPath = _adapter.resolvePackageUri(uri);
- if (libraryPath == null) {
- return false;
- }
-
- // Always compare paths case-insensitively to avoid any issues where APIs
- // may have returned different casing (eg. Windows drive letters). It's
- // almost certain a user wouldn't have a "local" package and an "external"
- // package with paths differing only be case.
- final libraryPathLower = libraryPath.toLowerCase();
- return !_adapter.projectPaths.any((projectPath) =>
- path.isWithin(projectPath.toLowerCase(), libraryPathLower));
- }
-
- bool _isSdkLibrary(vm.LibraryRef library) =>
- library.uri?.startsWith('dart:') ?? false;
-
- /// Checks whether a library should be considered debuggable.
- ///
- /// Initial values are provided in the launch arguments, but may be updated
- /// by the `updateDebugOptions` custom request.
- bool _libaryIsDebuggable(vm.LibraryRef library) {
- if (_isSdkLibrary(library)) {
- return debugSdkLibraries;
- } else if (_isExternalPackageLibrary(library)) {
- return debugExternalPackageLibraries;
- } else {
- return true;
- }
- }
-
/// Sets breakpoints for an individual isolate.
///
/// If [uri] is provided, only breakpoints for that URI will be sent (used
@@ -456,6 +483,7 @@
isolateId, uri, bp.line,
column: bp.column);
existingBreakpointsForIsolateAndUri.add(vmBp);
+ _clientBreakpointsByVmId[vmBp.id!] = bp;
});
}
}
@@ -489,10 +517,40 @@
return;
}
await Future.wait(libraries.map((library) async {
- final isDebuggable = _libaryIsDebuggable(library);
+ final libraryUri = library.uri;
+ final isDebuggable = libraryUri != null
+ ? _adapter.libaryIsDebuggable(Uri.parse(libraryUri))
+ : false;
await service.setLibraryDebuggable(isolateId, library.id!, isDebuggable);
}));
}
+
+ /// Checks whether a breakpoint the VM paused at is one we should actually
+ /// remain at. That is, it either has no condition, or its condition evaluates
+ /// to something truthy.
+ Future<bool> _shouldHitBreakpoint(
+ ThreadInfo thread,
+ Set<SourceBreakpoint?> breakpoints,
+ ) async {
+ // If any were missing (they're null) or do not have a condition, we should
+ // hit the breakpoint.
+ final clientBreakpointsWithConditions =
+ breakpoints.where((bp) => bp?.condition?.isNotEmpty ?? false).toList();
+ if (breakpoints.length != clientBreakpointsWithConditions.length) {
+ return true;
+ }
+
+ // Otherwise, we need to evaluate all of the conditions and see if any are
+ // true, in which case we will also hit.
+ final conditions =
+ clientBreakpointsWithConditions.map((bp) => bp!.condition!).toSet();
+
+ final results = await Future.wait(conditions.map(
+ (condition) => _breakpointConditionEvaluatesTrue(thread, condition),
+ ));
+
+ return results.any((result) => result);
+ }
}
/// Holds state for a single Isolate/Thread.
diff --git a/pkg/dds/lib/src/dap/protocol_converter.dart b/pkg/dds/lib/src/dap/protocol_converter.dart
index 1243e5a..c22b2a4 100644
--- a/pkg/dds/lib/src/dap/protocol_converter.dart
+++ b/pkg/dds/lib/src/dap/protocol_converter.dart
@@ -357,6 +357,18 @@
}
}
+ // If a source would be considered not-debuggable (for example it's in the
+ // SDK and debugSdkLibraries=false) then we should also mark it as
+ // deemphasized so that the editor can jump up the stack to the first frame
+ // of debuggable code.
+ final isDebuggable = uri != null && _adapter.libaryIsDebuggable(uri);
+ final presentationHint = isDebuggable ? null : 'deemphasize';
+ final origin = uri != null && _adapter.isSdkLibrary(uri)
+ ? 'from the SDK'
+ : uri != null && _adapter.isExternalPackageLibrary(uri)
+ ? 'from external packages'
+ : null;
+
final source = canShowSource
? dap.Source(
name: uriIsPackage
@@ -366,8 +378,10 @@
: uri?.toString() ?? '<unknown source>',
path: sourcePath,
sourceReference: sourceReference,
- origin: null,
- adapterData: location.script)
+ origin: origin,
+ adapterData: location.script,
+ presentationHint: presentationHint,
+ )
: null;
// The VM only allows us to restart from frames that are not the top frame,
diff --git a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
index eadf81f..6ada4bc 100644
--- a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
+++ b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
@@ -331,6 +331,84 @@
client.stepIn(stop.threadId!),
], eagerError: true);
});
+ }, timeout: Timeout.none);
+
+ group('debug mode conditional breakpoints', () {
+ test('stops with condition evaluating to true', () async {
+ final client = dap.client;
+ final testFile = dap.createTestFile(simpleBreakpointProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ await client.hitBreakpoint(
+ testFile,
+ breakpointLine,
+ condition: '1 == 1',
+ );
+ });
+
+ test('does not stop with condition evaluating to false', () async {
+ final client = dap.client;
+ final testFile = dap.createTestFile(simpleBreakpointProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ await client.doNotHitBreakpoint(
+ testFile,
+ breakpointLine,
+ condition: '1 == 2',
+ );
+ });
+
+ test('stops with condition evaluating to non-zero', () async {
+ final client = dap.client;
+ final testFile = dap.createTestFile(simpleBreakpointProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ await client.hitBreakpoint(
+ testFile,
+ breakpointLine,
+ condition: '1 + 1',
+ );
+ });
+
+ test('does not stop with condition evaluating to zero', () async {
+ final client = dap.client;
+ final testFile = dap.createTestFile(simpleBreakpointProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ await client.doNotHitBreakpoint(
+ testFile,
+ breakpointLine,
+ condition: '1 - 1',
+ );
+ });
+
+ test('reports evaluation errors for conditions', () async {
+ final client = dap.client;
+ final testFile = dap.createTestFile(simpleBreakpointProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ final outputEventsFuture = client.outputEvents.toList();
+
+ await client.doNotHitBreakpoint(
+ testFile,
+ breakpointLine,
+ condition: "1 + 'a'",
+ );
+
+ final outputEvents = await outputEventsFuture;
+ final outputMessages = outputEvents.map((e) => e.output);
+
+ final hasPrefix = startsWith(
+ 'Debugger failed to evaluate breakpoint condition "1 + \'a\'": '
+ 'evaluateInFrame: (113) Expression compilation error');
+ final hasDescriptiveMessage = contains(
+ "A value of type 'String' can't be assigned to a variable of type 'num'");
+
+ expect(
+ outputMessages,
+ containsAll([allOf(hasPrefix, hasDescriptiveMessage)]),
+ );
+ });
// These tests can be slow due to starting up the external server process.
}, timeout: Timeout.none);
}
diff --git a/pkg/dds/test/dap/integration/debug_stack_test.dart b/pkg/dds/test/dap/integration/debug_stack_test.dart
new file mode 100644
index 0000000..2a348e8
--- /dev/null
+++ b/pkg/dds/test/dap/integration/debug_stack_test.dart
@@ -0,0 +1,162 @@
+// Copyright (c) 2021, 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.
+
+import 'package:test/test.dart';
+
+import 'test_client.dart';
+import 'test_scripts.dart';
+import 'test_support.dart';
+
+main() {
+ late DapTestSession dap;
+ setUp(() async {
+ dap = await DapTestSession.setUp();
+ });
+ tearDown(() => dap.tearDown());
+
+ group('debug mode stack trace', () {
+ test('includes expected names and async boundaries', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile(simpleAsyncProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ final stop = await client.hitBreakpoint(testFile, breakpointLine);
+ final stack = await client.getValidStack(
+ stop.threadId!,
+ startFrame: 0,
+ numFrames: 8,
+ );
+
+ expect(
+ stack.stackFrames.map((f) => f.name),
+ equals([
+ 'four',
+ 'three',
+ '<asynchronous gap>',
+ 'two',
+ '<asynchronous gap>',
+ 'one',
+ '<asynchronous gap>',
+ 'main',
+ ]),
+ );
+
+ // Ensure async gaps have their presentationHint set to 'label'.
+ expect(
+ stack.stackFrames.map((f) => f.presentationHint),
+ equals([
+ null,
+ null,
+ 'label',
+ null,
+ 'label',
+ null,
+ 'label',
+ null,
+ ]),
+ );
+ });
+
+ test('only sets canRestart where VM can rewind', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile(simpleAsyncProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ final stop = await client.hitBreakpoint(testFile, breakpointLine);
+ final stack = await client.getValidStack(
+ stop.threadId!,
+ startFrame: 0,
+ numFrames: 8,
+ );
+
+ expect(
+ stack.stackFrames.map((f) => f.canRestart ?? false),
+ equals([
+ // Top frame cannot be rewound to:
+ // https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#resume
+ isFalse,
+ // Other frames can
+ isTrue,
+ // Until after an async boundary
+ isFalse,
+ isFalse,
+ isFalse,
+ isFalse,
+ isFalse,
+ isFalse,
+ ]),
+ );
+ });
+
+ test('deemphasizes SDK frames when debugSdk=false', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile(sdkStackFrameProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ final stop = await client.hitBreakpoint(
+ testFile,
+ breakpointLine,
+ launch: () => client.launch(testFile.path, debugSdkLibraries: false),
+ );
+ final stack = await client.getValidStack(
+ stop.threadId!,
+ startFrame: 0,
+ numFrames: 100,
+ );
+
+ // Get all frames that are SDK Frames
+ final sdkFrames = stack.stackFrames
+ .where((frame) => frame.source?.name?.startsWith('dart:') ?? false)
+ .toList();
+ // Ensure we got some frames for the test to be valid.
+ expect(sdkFrames, isNotEmpty);
+
+ for (final sdkFrame in sdkFrames) {
+ expect(
+ sdkFrame.source?.presentationHint,
+ equals('deemphasize'),
+ reason: '${sdkFrame.source!.name} should be deemphasized',
+ );
+ }
+ });
+
+ test('does not deemphasize SDK frames when debugSdk=true', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile(sdkStackFrameProgram);
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ final stop = await client.hitBreakpoint(
+ testFile,
+ breakpointLine,
+ launch: () => client.launch(testFile.path, debugSdkLibraries: true),
+ );
+ final stack = await client.getValidStack(
+ stop.threadId!,
+ startFrame: 0,
+ numFrames: 100,
+ );
+
+ // Get all frames that are SDK Frames
+ final sdkFrames = stack.stackFrames
+ .where((frame) => frame.source?.name?.startsWith('dart:') ?? false)
+ .toList();
+ // Ensure we got some frames for the test to be valid.
+ expect(sdkFrames, isNotEmpty);
+
+ for (final sdkFrame in sdkFrames) {
+ expect(
+ sdkFrame.source?.presentationHint,
+ isNot(equals('deemphasize')),
+ reason: '${sdkFrame.source!.name} should not be deemphasized',
+ );
+ expect(
+ sdkFrame.source?.origin,
+ equals('from the SDK'),
+ reason: '${sdkFrame.source!.name} should be labelled as SDK code',
+ );
+ }
+ });
+ // These tests can be slow due to starting up the external server process.
+ }, timeout: Timeout.none);
+}
diff --git a/pkg/dds/test/dap/integration/debug_test.dart b/pkg/dds/test/dap/integration/debug_test.dart
index 1986253..18b544c 100644
--- a/pkg/dds/test/dap/integration/debug_test.dart
+++ b/pkg/dds/test/dap/integration/debug_test.dart
@@ -5,7 +5,6 @@
import 'dart:io';
import 'package:dds/src/dap/protocol_generated.dart';
-import 'package:pedantic/pedantic.dart';
import 'package:test/test.dart';
import 'test_client.dart';
@@ -61,7 +60,6 @@
// request and capture the args.
RunInTerminalRequestArguments? runInTerminalArgs;
Process? proc;
- var processExited = false;
dap.client.handleRequest(
'runInTerminal',
(args) async {
@@ -78,7 +76,6 @@
runArgs.args.skip(1).toList(),
workingDirectory: runArgs.cwd,
);
- unawaited(proc!.exitCode.then((_) => processExited = true));
return RunInTerminalResponseBody(processId: proc!.pid);
},
@@ -98,7 +95,7 @@
containsAllInOrder([Platform.resolvedExecutable, testFile.path]),
);
expect(proc!.pid, isPositive);
- expect(processExited, isTrue);
+ expect(proc!.exitCode, completes);
});
test('provides a list of threads', () async {
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index b7c9ecd..1299701 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -391,6 +391,7 @@
Future<StoppedEventBody> hitBreakpoint(
File file,
int line, {
+ String? condition,
Future<Response> Function()? launch,
}) async {
final stop = expectStop('breakpoint', file: file, line: line);
@@ -400,7 +401,7 @@
sendRequest(
SetBreakpointsArguments(
source: Source(path: file.path),
- breakpoints: [SourceBreakpoint(line: line)],
+ breakpoints: [SourceBreakpoint(line: line, condition: condition)],
),
),
launch?.call() ?? this.launch(file.path),
@@ -434,6 +435,30 @@
return stop;
}
+ /// Sets a breakpoint at [line] in [file] and expects _not_ to hit it after
+ /// running the script (instead the script is expected to terminate).
+ ///
+ /// Launch options can be customised by passing a custom [launch] function that
+ /// will be used instead of calling `launch(file.path)`.
+ Future<void> doNotHitBreakpoint(
+ File file,
+ int line, {
+ String? condition,
+ Future<Response> Function()? launch,
+ }) async {
+ await Future.wait([
+ event('terminated'),
+ initialize(),
+ sendRequest(
+ SetBreakpointsArguments(
+ source: Source(path: file.path),
+ breakpoints: [SourceBreakpoint(line: line, condition: condition)],
+ ),
+ ),
+ launch?.call() ?? this.launch(file.path),
+ ], eagerError: true);
+ }
+
/// Returns whether DDS is available for the VM Service the debug adapter
/// is connected to.
Future<bool> get ddsAvailable async {
diff --git a/pkg/dds/test/dap/integration/test_scripts.dart b/pkg/dds/test/dap/integration/test_scripts.dart
index 0a5a492..5582f34 100644
--- a/pkg/dds/test/dap/integration/test_scripts.dart
+++ b/pkg/dds/test/dap/integration/test_scripts.dart
@@ -7,6 +7,43 @@
void main(List<String> args) {}
''';
+/// A simple async Dart script that when stopped at the line of '// BREAKPOINT'
+/// will contain SDK frames in the call stack.
+const sdkStackFrameProgram = r'''
+ void main() {
+ [0].where((i) {
+ return i == 0; // BREAKPOINT
+ }).toList();
+ }
+''';
+
+/// A simple async Dart script that when stopped at the line of '// BREAKPOINT'
+/// will contain multiple stack frames across some async boundaries.
+const simpleAsyncProgram = r'''
+ import 'dart:async';
+
+ Future<void> main() async {
+ await one();
+ }
+
+ Future<void> one() async {
+ await two();
+ }
+
+ Future<void> two() async {
+ await three();
+ }
+
+ Future<void> three() async {
+ await Future.delayed(const Duration(microseconds: 1));
+ four();
+ }
+
+ void four() {
+ print('!'); // BREAKPOINT
+ }
+''';
+
/// A simple Dart script that should run with no errors and contains a comment
/// marker '// BREAKPOINT' for use in tests that require stopping at a breakpoint
/// but require no other context.
diff --git a/pkg/dds/tool/dap/codegen.dart b/pkg/dds/tool/dap/codegen.dart
index edd064b..7dee5b3 100644
--- a/pkg/dds/tool/dap/codegen.dart
+++ b/pkg/dds/tool/dap/codegen.dart
@@ -482,7 +482,7 @@
} else if (type.isUnion) {
final types = type.unionTypes;
- // Write a check against each type, eg.:
+ // Write a check against each type, e.g.:
// x is y ? new Either.tx(x) : (...)
for (var i = 0; i < types.length; i++) {
final isLast = i == types.length - 1;
diff --git a/pkg/nnbd_migration/lib/instrumentation.dart b/pkg/nnbd_migration/lib/instrumentation.dart
index fb1afee..c2700c1 100644
--- a/pkg/nnbd_migration/lib/instrumentation.dart
+++ b/pkg/nnbd_migration/lib/instrumentation.dart
@@ -240,6 +240,7 @@
enum EdgeOriginKind {
alreadyMigratedType,
alwaysNullableType,
+ angularAnnotation,
argumentErrorCheckNotNull,
callTearOff,
compoundAssignment,
diff --git a/pkg/nnbd_migration/lib/src/edge_origin.dart b/pkg/nnbd_migration/lib/src/edge_origin.dart
index 2c2643f..ea9e4b1 100644
--- a/pkg/nnbd_migration/lib/src/edge_origin.dart
+++ b/pkg/nnbd_migration/lib/src/edge_origin.dart
@@ -64,6 +64,19 @@
EdgeOriginKind get kind => EdgeOriginKind.alwaysNullableType;
}
+/// Edge origin resulting from the presence of an Angular annotation such as
+/// `@Optional()`, `@ViewChild(...)`, or `@ContentChild(...)`.
+class AngularAnnotationOrigin extends EdgeOrigin {
+ AngularAnnotationOrigin(Source? source, AstNode node) : super(source, node);
+
+ @override
+ String get description =>
+ "annotated with an Angular annotation indicating nullability";
+
+ @override
+ EdgeOriginKind get kind => EdgeOriginKind.angularAnnotation;
+}
+
/// Edge origin resulting from the presence of a call to
/// `ArgumentError.checkNotNull`.
///
diff --git a/pkg/nnbd_migration/lib/src/node_builder.dart b/pkg/nnbd_migration/lib/src/node_builder.dart
index b50bed1..05cb79d 100644
--- a/pkg/nnbd_migration/lib/src/node_builder.dart
+++ b/pkg/nnbd_migration/lib/src/node_builder.dart
@@ -465,9 +465,13 @@
_variables!.recordDecoratedElementType(
declaredElement.variable, decoratedType.returnType);
} else {
- _variables!.recordDecoratedElementType(
- declaredElement.variable, decoratedType.positionalParameters![0],
+ var type = decoratedType.positionalParameters![0];
+ _variables!.recordDecoratedElementType(declaredElement.variable, type,
soft: true);
+ if (_hasAngularChildAnnotation(node.metadata)) {
+ _graph.makeNullable(
+ type!.node!, AngularAnnotationOrigin(source, node));
+ }
}
}
return null;
@@ -668,6 +672,12 @@
_variables!.recordDecoratedElementType(declaredElement, type);
variable.initializer?.accept(this);
}
+ var parent = node.parent;
+ if (parent is FieldDeclaration) {
+ if (_hasAngularChildAnnotation(parent.metadata)) {
+ _graph.makeNullable(type!.node!, AngularAnnotationOrigin(source, node));
+ }
+ }
return null;
}
@@ -794,6 +804,15 @@
_handleNullabilityHint(node, decoratedType);
}
_variables!.recordDecoratedElementType(declaredElement, decoratedType);
+ for (var annotation in node.metadata) {
+ var element = annotation.element;
+ if (element is ConstructorElement &&
+ element.enclosingElement.name == 'Optional' &&
+ _isAngularUri(element.librarySource.uri)) {
+ _graph.makeNullable(
+ decoratedType!.node!, AngularAnnotationOrigin(source, node));
+ }
+ }
if (declaredElement.isNamed) {
_namedParameters![declaredElement.name] = decoratedType;
} else {
@@ -884,6 +903,35 @@
.recordDecoratedDirectSupertypes(declaredElement, decoratedSupertypes);
}
+ /// Determines if the given [metadata] contains a reference to one of the
+ /// Angular annotations `ViewChild` or `ContentChild`, either of which implies
+ /// nullability of the underlying property.
+ bool _hasAngularChildAnnotation(NodeList<Annotation> metadata) {
+ for (var annotation in metadata) {
+ var element = annotation.element;
+ if (element is ConstructorElement) {
+ var name = element.enclosingElement.name;
+ if ((name == 'ViewChild' || name == 'ContentChild') &&
+ _isAngularUri(element.librarySource.uri)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /// Determines whether the given [uri] comes from the Angular package.
+ bool _isAngularUri(Uri uri) {
+ if (uri.scheme != 'package') return false;
+ var packageName = uri.pathSegments[0];
+ if (packageName == 'angular') return true;
+ if (packageName == 'third_party.dart_src.angular.angular') {
+ // This name is used for angular development internally at Google.
+ return true;
+ }
+ return false;
+ }
+
T _pushNullabilityNodeTarget<T>(
NullabilityNodeTarget target, T Function() fn) {
NullabilityNodeTarget? previousTarget = _target;
diff --git a/pkg/nnbd_migration/test/abstract_context.dart b/pkg/nnbd_migration/test/abstract_context.dart
index 4e7b83e..88ee434 100644
--- a/pkg/nnbd_migration/test/abstract_context.dart
+++ b/pkg/nnbd_migration/test/abstract_context.dart
@@ -45,6 +45,33 @@
String get testsPath => '$homePath/tests';
+ /// Makes a mock version of the Angular package available for unit testing.
+ ///
+ /// If optional argument [internalUris] is `true`, the mock Angular package
+ /// will be located in a package called `third_party.dart_src.angular.angular`
+ /// (as it is in Google3), and `package:angular` will simply re-export it;
+ /// this allows the test to reflect usage in internal sources.
+ void addAngularPackage({bool internalUris = false}) {
+ addPackageFile(
+ internalUris ? 'third_party.dart_src.angular.angular' : 'angular',
+ 'angular.dart', '''
+class ContentChild {
+ const ContentChild(Object selector, {Object? read});
+}
+class Optional {
+ const Optional();
+}
+class ViewChild {
+ const ViewChild(Object selector, {Object? read});
+}
+''');
+ if (internalUris) {
+ addPackageFile('angular', 'angular.dart', '''
+export 'package:third_party.dart_src.angular.angular/angular.dart';
+''');
+ }
+ }
+
void addBuiltValuePackage() {
addPackageFile('built_value', 'built_value.dart', '''
abstract class Built<V extends Built<V, B>, B extends Builder<V, B>> {}
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index cc1bc01..5307de5 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -356,6 +356,181 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_angular_contentChild_field() async {
+ addAngularPackage();
+ var content = '''
+import 'dart:html';
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ // Initialize this.bar in the constructor just so the migration tool doesn't
+ // decide to make it nullable due to the lack of initializer.
+ MyComponent(this.bar);
+
+ @ContentChild('foo')
+ Element bar;
+}
+''';
+ var expected = '''
+import 'dart:html';
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ // Initialize this.bar in the constructor just so the migration tool doesn't
+ // decide to make it nullable due to the lack of initializer.
+ MyComponent(this.bar);
+
+ @ContentChild('foo')
+ Element? bar;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_angular_optional_constructor_param() async {
+ addAngularPackage();
+ var content = '''
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ MyComponent(@Optional() String foo);
+}
+''';
+ var expected = '''
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ MyComponent(@Optional() String? foo);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_angular_optional_constructor_param_field_formal() async {
+ addAngularPackage();
+ var content = '''
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ String foo;
+ MyComponent(@Optional() this.foo);
+}
+''';
+ var expected = '''
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ String? foo;
+ MyComponent(@Optional() this.foo);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_angular_optional_constructor_param_internal() async {
+ addAngularPackage(internalUris: true);
+ var content = '''
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ MyComponent(@Optional() String foo);
+}
+''';
+ var expected = '''
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ MyComponent(@Optional() String? foo);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_angular_viewChild_field() async {
+ addAngularPackage();
+ var content = '''
+import 'dart:html';
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ // Initialize this.bar in the constructor just so the migration tool doesn't
+ // decide to make it nullable due to the lack of initializer.
+ MyComponent(this.bar);
+
+ @ViewChild('foo')
+ Element bar;
+}
+''';
+ var expected = '''
+import 'dart:html';
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ // Initialize this.bar in the constructor just so the migration tool doesn't
+ // decide to make it nullable due to the lack of initializer.
+ MyComponent(this.bar);
+
+ @ViewChild('foo')
+ Element? bar;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_angular_viewChild_field_internal() async {
+ addAngularPackage(internalUris: true);
+ var content = '''
+import 'dart:html';
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ // Initialize this.bar in the constructor just so the migration tool doesn't
+ // decide to make it nullable due to the lack of initializer.
+ MyComponent(this.bar);
+
+ @ViewChild('foo')
+ Element bar;
+}
+''';
+ var expected = '''
+import 'dart:html';
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ // Initialize this.bar in the constructor just so the migration tool doesn't
+ // decide to make it nullable due to the lack of initializer.
+ MyComponent(this.bar);
+
+ @ViewChild('foo')
+ Element? bar;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_angular_viewChild_setter() async {
+ addAngularPackage();
+ var content = '''
+import 'dart:html';
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ @ViewChild('foo')
+ set bar(Element element) {}
+}
+''';
+ var expected = '''
+import 'dart:html';
+import 'package:angular/angular.dart';
+
+class MyComponent {
+ @ViewChild('foo')
+ set bar(Element? element) {}
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_argumentError_checkNotNull_implies_non_null_intent() async {
var content = '''
void f(int i) {
diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn
index 3016678..dd25e81 100644
--- a/runtime/BUILD.gn
+++ b/runtime/BUILD.gn
@@ -212,7 +212,10 @@
if (is_fuchsia) {
# safe-stack does not work with the interpreter.
- cflags += [ "-fno-sanitize=safe-stack" ]
+ cflags += [
+ "-fno-sanitize=safe-stack",
+ "-Wno-deprecated-copy",
+ ]
}
}
}
diff --git a/runtime/lib/double.cc b/runtime/lib/double.cc
index bc7c33b..53e38dc 100644
--- a/runtime/lib/double.cc
+++ b/runtime/lib/double.cc
@@ -101,17 +101,6 @@
return Smi::New(((uval >> 32) ^ (uval)) & kSmiMax);
}
-DEFINE_NATIVE_ENTRY(Double_trunc_div, 0, 2) {
- double left = Double::CheckedHandle(zone, arguments->NativeArgAt(0)).value();
- GET_NON_NULL_NATIVE_ARGUMENT(Double, right_object, arguments->NativeArgAt(1));
- double right = right_object.value();
- if (FLAG_trace_intrinsified_natives) {
- OS::PrintErr("Double_trunc_div %f ~/ %f\n", left, right);
- }
- return DoubleToInteger(trunc(left / right),
- "Result of truncating division is Infinity or NaN");
-}
-
DEFINE_NATIVE_ENTRY(Double_modulo, 0, 2) {
double left = Double::CheckedHandle(zone, arguments->NativeArgAt(0)).value();
GET_NON_NULL_NATIVE_ARGUMENT(Double, right_object, arguments->NativeArgAt(1));
diff --git a/runtime/vm/allocation.h b/runtime/vm/allocation.h
index fc4be72..fd1bdfa 100644
--- a/runtime/vm/allocation.h
+++ b/runtime/vm/allocation.h
@@ -75,4 +75,8 @@
} // namespace dart
+// Prevent use of `new (zone) DoesNotExtendZoneAllocated()`, which places the
+// DoesNotExtendZoneAllocated on top of the Zone.
+void* operator new(size_t size, dart::Zone* zone) = delete;
+
#endif // RUNTIME_VM_ALLOCATION_H_
diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h
index e9710df6..c8f50e2 100644
--- a/runtime/vm/bootstrap_natives.h
+++ b/runtime/vm/bootstrap_natives.h
@@ -89,7 +89,6 @@
V(Double_sub, 2) \
V(Double_mul, 2) \
V(Double_div, 2) \
- V(Double_trunc_div, 2) \
V(Double_remainder, 2) \
V(Double_modulo, 2) \
V(Double_greaterThanFromInteger, 2) \
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 527fa42..d2bf1c4 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -357,7 +357,7 @@
bool HierarchyInfo::CanUseSubtypeRangeCheckFor(const AbstractType& type) {
ASSERT(type.IsFinalized());
- if (!type.IsInstantiated() || !type.IsType() || type.IsDartFunctionType()) {
+ if (!type.IsInstantiated() || !type.IsType()) {
return false;
}
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index c239df3..160abbe 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -15774,12 +15774,15 @@
return count;
}
-void ICData::WriteSentinel(const Array& data, intptr_t test_entry_length) {
+void ICData::WriteSentinel(const Array& data,
+ intptr_t test_entry_length,
+ const Object& back_ref) {
ASSERT(!data.IsNull());
RELEASE_ASSERT(smi_illegal_cid().Value() == kIllegalCid);
- for (intptr_t i = 1; i <= test_entry_length; i++) {
+ for (intptr_t i = 2; i <= test_entry_length; i++) {
data.SetAt(data.Length() - i, smi_illegal_cid());
}
+ data.SetAt(data.Length() - 1, back_ref);
}
#if defined(DEBUG)
@@ -15825,6 +15828,11 @@
for (intptr_t i = start; i < end; i++) {
data.SetAt(i, smi_illegal_cid());
}
+ // The last slot in the last entry of the [ICData::entries_] is a back-ref to
+ // the [ICData] itself.
+ if (index == (len - 1)) {
+ data.SetAt(end - 1, *this);
+ }
}
void ICData::ClearCountAt(intptr_t index,
@@ -15985,7 +15993,7 @@
// Grow the array and write the new final sentinel into place.
const intptr_t new_len = data.Length() + TestEntryLength();
data = Array::Grow(data, new_len, Heap::kOld);
- WriteSentinel(data, TestEntryLength());
+ WriteSentinel(data, TestEntryLength(), *this);
return data.ptr();
}
@@ -16113,7 +16121,8 @@
data = entries();
const intptr_t entry_length = TestEntryLength();
intptr_t data_pos = index * TestEntryLength();
- for (intptr_t i = 0; i < entry_length; i++) {
+ const intptr_t kBackRefLen = (index == (Length() - 1)) ? 1 : 0;
+ for (intptr_t i = 0; i < entry_length - kBackRefLen; i++) {
if (data.At(data_pos++) != smi_illegal_cid().ptr()) {
return false;
}
@@ -16349,7 +16358,7 @@
pos += result.TestEntryLength();
}
- WriteSentinel(data, result.TestEntryLength());
+ WriteSentinel(data, result.TestEntryLength(), result);
result.set_entries(data);
ASSERT(result.NumberOfChecksIs(aggregate.length()));
return result.ptr();
@@ -16419,7 +16428,8 @@
// IC data array must be null terminated (sentinel entry).
const intptr_t len = TestEntryLengthFor(num_args_tested, tracking_exactness);
const Array& array = Array::Handle(Array::New(len, Heap::kOld));
- WriteSentinel(array, len);
+ // Only empty [ICData]s are allowed to have a non-ICData backref.
+ WriteSentinel(array, len, /*back_ref=*/smi_illegal_cid());
array.MakeImmutable();
return array.ptr();
}
@@ -16548,7 +16558,7 @@
#if !defined(DART_PRECOMPILED_RUNTIME)
array.SetAt(CountIndexFor(num_args_tested), Object::smi_zero());
#endif
- WriteSentinel(array, entry_len);
+ WriteSentinel(array, entry_len, result);
result.set_entries(array);
@@ -16632,6 +16642,26 @@
}
#endif
+ICDataPtr ICData::ICDataOfEntriesArray(const Array& array) {
+ const auto& back_ref = Object::Handle(array.At(array.Length() - 1));
+ if (back_ref.ptr() == smi_illegal_cid().ptr()) {
+ // The ICData must be empty.
+#if defined(DEBUG)
+ const int kMaxTestEntryLen = TestEntryLengthFor(2, true);
+ ASSERT(array.Length() <= kMaxTestEntryLen);
+ for (intptr_t i = 0; i < array.Length(); ++i) {
+ ASSERT(array.At(i) == Object::sentinel().ptr());
+ }
+#endif
+ return ICData::null();
+ }
+ const auto& ic_data = ICData::Cast(back_ref);
+#if defined(DEBUG)
+ ic_data.IsSentinelAt(ic_data.Length() - 1);
+#endif
+ return ic_data.ptr();
+}
+
const char* WeakSerializationReference::ToCString() const {
return Object::Handle(target()).ToCString();
}
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 0778e23..112efdd 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2314,6 +2314,12 @@
// Generates a new ICData with descriptor and data array copied (deep clone).
static ICDataPtr Clone(const ICData& from);
+ // Gets the [ICData] from the [ICData::entries_] array (which stores a back
+ // ref).
+ //
+ // May return `null` if the [ICData] is empty.
+ static ICDataPtr ICDataOfEntriesArray(const Array& array);
+
static intptr_t TestEntryLengthFor(intptr_t num_args,
bool tracking_exactness);
@@ -2473,7 +2479,9 @@
RebindRule rebind_rule,
const AbstractType& receiver_type);
- static void WriteSentinel(const Array& data, intptr_t test_entry_length);
+ static void WriteSentinel(const Array& data,
+ intptr_t test_entry_length,
+ const Object& back_ref);
// A cache of VM heap allocated preinitialized empty ic data entry arrays.
static ArrayPtr cached_icdata_arrays_[kCachedICDataArrayCount];
diff --git a/runtime/vm/os_thread.h b/runtime/vm/os_thread.h
index dca5003..7601cdc 100644
--- a/runtime/vm/os_thread.h
+++ b/runtime/vm/os_thread.h
@@ -62,7 +62,6 @@
friend class MutexLocker;
friend class SafepointMutexLocker;
friend class OSThreadIterator;
- friend class TimelineEventBlockIterator;
friend class TimelineEventRecorder;
friend class PageSpace;
friend void Dart_TestMutex();
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index d2d7f4d..a545c0e 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -1832,26 +1832,6 @@
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
-#if !defined(DART_PRECOMPILED_RUNTIME)
-static ICDataPtr FindICDataForInstanceCall(Zone* zone,
- const Code& code,
- uword pc) {
- uword pc_offset = pc - code.PayloadStart();
- const PcDescriptors& descriptors =
- PcDescriptors::Handle(zone, code.pc_descriptors());
- PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kIcCall);
- intptr_t deopt_id = -1;
- while (iter.MoveNext()) {
- if (iter.PcOffset() == pc_offset) {
- deopt_id = iter.DeoptId();
- break;
- }
- }
- ASSERT(deopt_id != -1);
- return Function::Handle(zone, code.function()).FindICData(deopt_id);
-}
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
-
#if defined(DART_PRECOMPILED_RUNTIME)
void PatchableCallHandler::DoMonomorphicMissAOT(
const Object& data,
@@ -1917,22 +1897,53 @@
void PatchableCallHandler::DoMonomorphicMissJIT(
const Object& data,
const Function& target_function) {
- const ICData& ic_data = ICData::Handle(
- zone_,
- FindICDataForInstanceCall(zone_, caller_code_, caller_frame_->pc()));
- RELEASE_ASSERT(!ic_data.IsNull());
+ // Monomorphic calls use the ICData::entries() as their data.
+ const auto& ic_data_entries = Array::Cast(data);
+ // Any non-empty ICData::entries() has a backref to it's ICData.
+ const auto& ic_data =
+ ICData::Handle(zone_, ICData::ICDataOfEntriesArray(ic_data_entries));
+
+ const classid_t current_cid = receiver().GetClassId();
+ const classid_t old_cid = ic_data.GetReceiverClassIdAt(0);
+ const bool same_receiver = current_cid == old_cid;
+
+ // The target didn't change, so we can stay inside monomorphic state.
+ if (same_receiver) {
+ // We got a miss because the old target code got disabled.
+ // Notice the reverse is not true: If the old code got disabled, the call
+ // might still have a different receiver then last time and possibly a
+ // different target.
+ ASSERT(miss_handler_ == MissHandler::kFixCallersTargetMonomorphic ||
+ !IsolateGroup::Current()->ContainsOnlyOneIsolate());
+
+ // No need to update ICData - it's already up-to-date.
+
+ if (FLAG_trace_ic) {
+ OS::PrintErr("Instance call at %" Px
+ " updating code (old code was disabled)\n",
+ caller_frame_->pc());
+ }
+
+ // We stay in monomorphic state, patch the code object and keep the same
+ // data (old ICData entries array).
+ const auto& code = Code::Handle(zone_, target_function.EnsureHasCode());
+ CodePatcher::PatchInstanceCallAt(caller_frame_->pc(), caller_code_, data,
+ code);
+ ReturnJIT(code, data, target_function);
+ return;
+ }
ASSERT(ic_data.NumArgsTested() == 1);
const Code& stub = ic_data.is_tracking_exactness()
? StubCode::OneArgCheckInlineCacheWithExactnessCheck()
: StubCode::OneArgCheckInlineCache();
- CodePatcher::PatchInstanceCallAt(caller_frame_->pc(), caller_code_, ic_data,
- stub);
if (FLAG_trace_ic) {
OS::PrintErr("Instance call at %" Px
- " switching to polymorphic dispatch, %s\n",
+ " switching monomorphic to polymorphic dispatch, %s\n",
caller_frame_->pc(), ic_data.ToCString());
}
+ CodePatcher::PatchInstanceCallAt(caller_frame_->pc(), caller_code_, ic_data,
+ stub);
ASSERT(caller_arguments_.length() == 1);
UpdateICDataWithTarget(ic_data, target_function);
@@ -2231,14 +2242,13 @@
}
#else
case kArrayCid: {
- // ICData three-element array: Smi(receiver CID), Smi(count),
- // Function(target). It is the Array from ICData::entries_.
- const auto& ic_data = ICData::Handle(
- zone_,
- FindICDataForInstanceCall(zone_, caller_code_, caller_frame_->pc()));
- RELEASE_ASSERT(!ic_data.IsNull());
- name_ = ic_data.target_name();
+ // Monomorphic calls use the ICData::entries() as their data.
+ const auto& ic_data_entries = Array::Cast(data);
+ // Any non-empty ICData::entries() has a backref to it's ICData.
+ const auto& ic_data =
+ ICData::Handle(zone_, ICData::ICDataOfEntriesArray(ic_data_entries));
args_descriptor_ = ic_data.arguments_descriptor();
+ name_ = ic_data.target_name();
break;
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/thread_interrupter.h b/runtime/vm/thread_interrupter.h
index 954a174..c5d770e 100644
--- a/runtime/vm/thread_interrupter.h
+++ b/runtime/vm/thread_interrupter.h
@@ -120,9 +120,9 @@
intptr_t old_value = sample_buffer_lock_.load(std::memory_order_relaxed);
intptr_t new_value;
do {
- if (old_value > 0) {
+ while (old_value > 0) {
+ // Spin waiting for outstanding SIGPROFs to complete.
old_value = sample_buffer_lock_.load(std::memory_order_relaxed);
- continue; // Spin waiting for outstanding SIGPROFs to complete.
}
new_value = old_value - 1;
} while (!sample_buffer_lock_.compare_exchange_weak(
diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
index e57fa3b..de13805 100644
--- a/runtime/vm/timeline.cc
+++ b/runtime/vm/timeline.cc
@@ -1516,44 +1516,6 @@
#endif
}
-TimelineEventBlockIterator::TimelineEventBlockIterator(
- TimelineEventRecorder* recorder)
- : current_(NULL), recorder_(NULL) {
- Reset(recorder);
-}
-
-TimelineEventBlockIterator::~TimelineEventBlockIterator() {
- Reset(NULL);
-}
-
-void TimelineEventBlockIterator::Reset(TimelineEventRecorder* recorder) {
- // Clear current.
- current_ = NULL;
- if (recorder_ != NULL) {
- // Unlock old recorder.
- recorder_->lock_.Unlock();
- }
- recorder_ = recorder;
- if (recorder_ == NULL) {
- return;
- }
- // Lock new recorder.
- recorder_->lock_.Lock();
- // Queue up first block.
- current_ = recorder_->GetHeadBlockLocked();
-}
-
-bool TimelineEventBlockIterator::HasNext() const {
- return current_ != NULL;
-}
-
-TimelineEventBlock* TimelineEventBlockIterator::Next() {
- ASSERT(current_ != NULL);
- TimelineEventBlock* r = current_;
- current_ = current_->next();
- return r;
-}
-
void DartTimelineEventHelpers::ReportTaskEvent(Thread* thread,
TimelineEvent* event,
int64_t id,
diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h
index 69302aa..b6d1908 100644
--- a/runtime/vm/timeline.h
+++ b/runtime/vm/timeline.h
@@ -757,7 +757,6 @@
int64_t time_high_micros_;
friend class TimelineEvent;
- friend class TimelineEventBlockIterator;
friend class TimelineStream;
friend class TimelineTestHelper;
friend class Timeline;
@@ -887,25 +886,6 @@
friend class TimelineTestHelper;
};
-// An iterator for blocks.
-class TimelineEventBlockIterator {
- public:
- explicit TimelineEventBlockIterator(TimelineEventRecorder* recorder);
- ~TimelineEventBlockIterator();
-
- void Reset(TimelineEventRecorder* recorder);
-
- // Returns false when there are no more blocks.
- bool HasNext() const;
-
- // Returns the next block and moves forward.
- TimelineEventBlock* Next();
-
- private:
- TimelineEventBlock* current_;
- TimelineEventRecorder* recorder_;
-};
-
// The TimelineEventPlatformRecorder records timeline events to a platform
// specific destination. It's implementation is in the timeline_{linux,...}.cc
// files.
diff --git a/runtime/vm/type_testing_stubs.cc b/runtime/vm/type_testing_stubs.cc
index 81f4d21..48c073e 100644
--- a/runtime/vm/type_testing_stubs.cc
+++ b/runtime/vm/type_testing_stubs.cc
@@ -322,13 +322,6 @@
__ BranchIfNotSmi(TypeTestABI::kInstanceReg, &non_smi_value);
__ Ret();
__ Bind(&non_smi_value);
- } else if (type.IsDartFunctionType()) {
- compiler::Label continue_checking;
- __ CompareImmediate(TTSInternalRegs::kScratchReg, kClosureCid);
- __ BranchIf(NOT_EQUAL, &continue_checking);
- __ Ret();
- __ Bind(&continue_checking);
-
} else if (type.IsObjectType()) {
ASSERT(type.IsNonNullable() &&
IsolateGroup::Current()->use_strict_null_safety_checks());
diff --git a/runtime/vm/type_testing_stubs_test.cc b/runtime/vm/type_testing_stubs_test.cc
index cf4fe9f..e036dfa 100644
--- a/runtime/vm/type_testing_stubs_test.cc
+++ b/runtime/vm/type_testing_stubs_test.cc
@@ -1133,6 +1133,54 @@
RunTTSTest(dst_type, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
}
+// Check that we generate correct TTS for type Function (the non-FunctionType
+// version).
+ISOLATE_UNIT_TEST_CASE(TTS_Function) {
+ const char* kScript =
+ R"(
+ class A<T> {}
+
+ createF() => (){};
+ createG() => () => 3;
+ createH() => (int x, String y, {int z : 0}) => x + z;
+
+ createAInt() => A<int>();
+ createAFunction() => A<Function>();
+ )";
+
+ const auto& root_library = Library::Handle(LoadTestScript(kScript));
+ const auto& obj_f = Object::Handle(Invoke(root_library, "createF"));
+ const auto& obj_g = Object::Handle(Invoke(root_library, "createG"));
+ const auto& obj_h = Object::Handle(Invoke(root_library, "createH"));
+ const auto& obj_null = Instance::Handle();
+
+ const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
+ const auto& type_function = Type::Handle(Type::DartFunctionType());
+
+ RunTTSTest(type_function, {obj_f, tav_null, tav_null});
+ RunTTSTest(type_function, {obj_g, tav_null, tav_null});
+ RunTTSTest(type_function, {obj_h, tav_null, tav_null});
+ if (!thread->isolate_group()->use_strict_null_safety_checks()) {
+ RunTTSTest(type_function, {obj_null, tav_null, tav_null});
+ } else {
+ RunTTSTest(type_function, Failure({obj_null, tav_null, tav_null}));
+ }
+
+ const auto& class_a = Class::Handle(GetClass(root_library, "A"));
+ const auto& obj_a_int = Object::Handle(Invoke(root_library, "createAInt"));
+ const auto& obj_a_function =
+ Object::Handle(Invoke(root_library, "createAFunction"));
+
+ auto& tav_function = TypeArguments::Handle(TypeArguments::New(1));
+ tav_function.SetTypeAt(0, type_function);
+ CanonicalizeTAV(&tav_function);
+ auto& type_a_function = Type::Handle(Type::New(class_a, tav_function));
+ FinalizeAndCanonicalize(&type_a_function);
+
+ RunTTSTest(type_a_function, {obj_a_function, tav_null, tav_null});
+ RunTTSTest(type_a_function, Failure({obj_a_int, tav_null, tav_null}));
+}
+
ISOLATE_UNIT_TEST_CASE(TTS_Partial) {
const char* kScript =
R"(
diff --git a/runtime/vm/zone.cc b/runtime/vm/zone.cc
index 744c8a5..5fa1efc 100644
--- a/runtime/vm/zone.cc
+++ b/runtime/vm/zone.cc
@@ -163,8 +163,7 @@
// is created within a new thread or ApiNativeScope when calculating high
// watermarks or memory consumption.
Zone::Zone()
- : canary_(kCanary),
- position_(reinterpret_cast<uword>(&buffer_)),
+ : position_(reinterpret_cast<uword>(&buffer_)),
limit_(position_ + kInitialChunkSize),
head_(NULL),
large_segments_(NULL),
@@ -179,7 +178,6 @@
}
Zone::~Zone() {
- ASSERT(canary_ == kCanary);
if (FLAG_trace_zones) {
DumpZoneSizes();
}
diff --git a/runtime/vm/zone.h b/runtime/vm/zone.h
index e4a0030..b30f446 100644
--- a/runtime/vm/zone.h
+++ b/runtime/vm/zone.h
@@ -141,10 +141,6 @@
template <class ElementType>
static inline void CheckLength(intptr_t len);
- // Guard against `new (zone) DoesNotExtendZoneAllocated()`.
- static constexpr uint64_t kCanary = 0x656e6f7a74726164ull; // "dartzone"
- uint64_t canary_;
-
// The free region in the current (head) segment or the initial buffer is
// represented as the half-open interval [position, limit). The 'position'
// variable is guaranteed to be aligned as dictated by kAlignment.
diff --git a/sdk/lib/_internal/vm/lib/double.dart b/sdk/lib/_internal/vm/lib/double.dart
index cc518e5..ab3b88d 100644
--- a/sdk/lib/_internal/vm/lib/double.dart
+++ b/sdk/lib/_internal/vm/lib/double.dart
@@ -49,12 +49,9 @@
double _mul(double other) native "Double_mul";
int operator ~/(num other) {
- return _trunc_div(other.toDouble());
+ return (this / other.toDouble()).truncate();
}
- @pragma("vm:non-nullable-result-type")
- int _trunc_div(double other) native "Double_trunc_div";
-
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", _Double)
@pragma("vm:never-inline")
@@ -142,7 +139,7 @@
}
int _truncDivFromInteger(int other) {
- return new _Double.fromInteger(other)._trunc_div(this);
+ return (new _Double.fromInteger(other) / this).truncate();
}
double _moduloFromInteger(int other) {
diff --git a/tests/language/constructor/reference_test.dart b/tests/language/constructor/reference_test.dart
index 0630a86..36ca27d 100644
--- a/tests/language/constructor/reference_test.dart
+++ b/tests/language/constructor/reference_test.dart
@@ -98,6 +98,8 @@
// ^^^^^
// [analyzer] SYNTACTIC_ERROR.EXPERIMENT_NOT_ENABLED
// [cfe] This requires the 'constructor-tearoffs' language feature to be enabled.
+ // ^^^
+ // [analyzer] COMPILE_TIME_ERROR.UNDEFINED_METHOD
// ^
// [cfe] Getter not found: 'bar'.
Foo.bar<int>();
diff --git a/tests/language/type_object/explicit_instantiated_type_literal_test.dart b/tests/language/type_object/explicit_instantiated_type_literal_test.dart
index 6b3b07f..5d036e6 100644
--- a/tests/language/type_object/explicit_instantiated_type_literal_test.dart
+++ b/tests/language/type_object/explicit_instantiated_type_literal_test.dart
@@ -45,9 +45,7 @@
Expect.identical(C<int>, prefix.C<int>);
(<T extends num>() {
- Expect.notIdentical(C<T>, C<T>);
Expect.equals(C<T>, C<T>);
- Expect.notIdentical(prefix.C<T>, prefix.C<T>);
Expect.equals(prefix.C<T>, prefix.C<T>);
}<int>());
}
diff --git a/tests/language_2/constructor/reference_test.dart b/tests/language_2/constructor/reference_test.dart
index 6e3dac9..6dff1ad 100644
--- a/tests/language_2/constructor/reference_test.dart
+++ b/tests/language_2/constructor/reference_test.dart
@@ -100,6 +100,8 @@
// ^^^^^
// [analyzer] SYNTACTIC_ERROR.EXPERIMENT_NOT_ENABLED
// [cfe] This requires the 'constructor-tearoffs' language feature to be enabled.
+ // ^^^
+ // [analyzer] COMPILE_TIME_ERROR.UNDEFINED_METHOD
// ^
// [cfe] Getter not found: 'bar'.
Foo.bar<int>();
diff --git a/tools/VERSION b/tools/VERSION
index 369f442..39cf8998 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 1
+PRERELEASE 2
PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/dom/scripts/dartdomgenerator.py b/tools/dom/scripts/dartdomgenerator.py
index 944d36a..a392020 100755
--- a/tools/dom/scripts/dartdomgenerator.py
+++ b/tools/dom/scripts/dartdomgenerator.py
@@ -95,7 +95,8 @@
dart2js_output_dir,
update_dom_metadata=False,
logging_level=logging.WARNING,
- dart_js_interop=False):
+ dart_js_interop=False,
+ generate_static_extensions=False):
print('\n ----- Accessing DOM using %s -----\n' %
('dart:js' if dart_js_interop else 'C++'))
@@ -167,8 +168,9 @@
backend_options = GeneratorOptions(template_loader, webkit_database,
type_registry, renamer, metadata,
dart_js_interop)
+
backend_factory = lambda interface:\
- Dart2JSBackend(interface, backend_options, logging_level)
+ Dart2JSBackend(interface, backend_options, logging_level, generate_static_extensions)
dart_output_dir = os.path.join(dart2js_output_dir, 'dart')
dart_libraries = DartLibraries(HTML_LIBRARY_NAMES, template_loader,
@@ -306,6 +308,12 @@
action='store_true',
default=False,
help='Do not generate the sdk/lib/js/cached_patches.dart file')
+ parser.add_option(
+ '--generate-static-extensions',
+ dest='generate_static_extensions',
+ action='store_true',
+ default=False,
+ help='Generate static extension members for dart:html classes')
(options, args) = parser.parse_args()
@@ -337,7 +345,8 @@
GenerateFromDatabase(database, dart2js_output_dir,
options.update_dom_metadata, logging_level,
- options.dart_js_interop)
+ options.dart_js_interop,
+ options.generate_static_extensions)
file_generation_start_time = time.time()
diff --git a/tools/dom/scripts/htmldartgenerator.py b/tools/dom/scripts/htmldartgenerator.py
index 7891aa3..22890c9 100644
--- a/tools/dom/scripts/htmldartgenerator.py
+++ b/tools/dom/scripts/htmldartgenerator.py
@@ -535,7 +535,7 @@
return mixins
def AddConstructors(self, constructors, factory_name,
- factory_constructor_name):
+ factory_constructor_name, emmiter):
""" Adds all of the constructors.
Arguments:
constructors - List of the constructors to be added.
@@ -543,13 +543,15 @@
factory_constructor_name - The name of the constructor on the
factory_name to call (calls an autogenerated FactoryProvider
if unspecified)
+ emmiter - Emmiter used to emit constructors when generating classes
+ using the static extension pattern.
"""
for constructor_info in constructors:
self._AddConstructor(constructor_info, factory_name,
- factory_constructor_name)
+ factory_constructor_name, emmiter)
def _AddConstructor(self, constructor_info, factory_name,
- factory_constructor_name):
+ factory_constructor_name, emmiter):
# Hack to ignore the constructor used by JavaScript.
if ((self._interface.id == 'HTMLImageElement' or
self._interface.id == 'Blob' or
@@ -563,6 +565,8 @@
metadata = self._metadata.GetFormattedMetadata(
self._library_name, self._interface, self._interface.id, ' ')
+ target_emitter = emmiter if emmiter else self._members_emitter
+
if not factory_constructor_name:
factory_constructor_name = '_create'
factory_parameters = constructor_info.ParametersAsArgumentList()
@@ -586,7 +590,7 @@
factory_name, factory_constructor_name, factory_parameters,
constructor_info)
if not has_optional:
- self._members_emitter.Emit(
+ target_emitter.Emit(
'\n $(METADATA)'
'factory $CTOR($PARAMS) => '
'$FACTORY_CALL;\n',
@@ -595,7 +599,7 @@
FACTORY_CALL=factory_call,
METADATA=metadata)
else:
- inits = self._members_emitter.Emit(
+ inits = target_emitter.Emit(
'\n $(METADATA)'
'factory $CONSTRUCTOR($PARAMS) {\n'
' $CONSTRUCTOR e = $FACTORY_CALL;\n'
@@ -664,7 +668,7 @@
FACTORY_NAME=qualified_name,
FACTORY_PARAMS=args)
self.EmitStaticFactoryOverload(constructor_info, name,
- arguments)
+ arguments, emmiter)
def IsOptional(signature_index, argument):
return self.IsConstructorArgumentOptional(argument)
@@ -678,13 +682,12 @@
METADATA=metadata,
PARAMS=constructor_info.ParametersAsDeclaration(InputType))
- overload_emitter = self._members_emitter
overload_declaration = entry_declaration
self._GenerateOverloadDispatcher(constructor_info,
constructor_info.idl_args, False,
overload_declaration, GenerateCall,
- IsOptional, overload_emitter)
+ IsOptional, target_emitter)
def _AddFutureifiedOperation(self, info, html_name):
"""Given a API function that uses callbacks, convert it to using Futures.
@@ -813,12 +816,13 @@
FUTURE_GENERIC=future_generic,
ORIGINAL_FUNCTION=html_name)
- def EmitHelpers(self, base_class):
- if not self._members_emitter:
+ def EmitHelpers(self, base_class, emmiter):
+ if (not self._members_emitter) and (not emmiter):
return
if self._interface.id not in custom_html_constructors:
- self._members_emitter.Emit(
+ target_emmiter = emmiter if emmiter else self._members_emitter
+ target_emmiter.Emit(
' // To suppress missing implicit constructor warnings.\n'
' factory $CLASSNAME._() { '
'throw new UnsupportedError("Not supported"); }\n',
diff --git a/tools/dom/scripts/systemhtml.py b/tools/dom/scripts/systemhtml.py
index 5749cd9..c8ff6b3 100644
--- a/tools/dom/scripts/systemhtml.py
+++ b/tools/dom/scripts/systemhtml.py
@@ -16,6 +16,14 @@
_logger = logging.getLogger('systemhtml')
+
+def CanUseStaticExtensions(interface, should):
+ if not should:
+ return False
+ static_extension_interfaces = [] # Classes to be migrated
+ return interface in static_extension_interfaces
+
+
HTML_LIBRARY_NAMES = [
'html', 'indexed_db', 'svg', 'web_audio', 'web_gl', 'web_sql'
]
@@ -809,24 +817,34 @@
NULLASSERT='!')
stream_getter_signatures_emitter = None
element_stream_getters_emitter = None
+ class_members_emitter = None
if type(implementation_members_emitter) == tuple:
# We add event stream getters for both Element and ElementList, so in
# impl_Element.darttemplate, we have two additional "holes" for emitters
# to fill in, with small variations. These store these specialized
# emitters.
- assert len(implementation_members_emitter) == 3
- stream_getter_signatures_emitter = \
- implementation_members_emitter[0]
- element_stream_getters_emitter = implementation_members_emitter[1]
- implementation_members_emitter = \
- implementation_members_emitter[2]
+ if (len(implementation_members_emitter) == 3):
+ stream_getter_signatures_emitter = \
+ implementation_members_emitter[0]
+ element_stream_getters_emitter = implementation_members_emitter[
+ 1]
+ implementation_members_emitter = \
+ implementation_members_emitter[2]
+
+ # We add special emmiters for classes migrated to static type extensions
+ elif (len(implementation_members_emitter) == 2):
+ class_members_emitter = \
+ implementation_members_emitter[0]
+ implementation_members_emitter = \
+ implementation_members_emitter[1]
self._backend.StartInterface(implementation_members_emitter)
- self._backend.EmitHelpers(base_class)
+ self._backend.EmitHelpers(base_class, class_members_emitter)
self._event_generator.EmitStreamProviders(
self._interface, self._backend.CustomJSMembers(),
implementation_members_emitter, self._library_name)
self._backend.AddConstructors(constructors, factory_provider,
- factory_constructor_name)
+ factory_constructor_name,
+ class_members_emitter)
isElement = False
for parent in self._database.Hierarchy(self._interface):
@@ -1243,9 +1261,14 @@
interface.
"""
- def __init__(self, interface, options, logging_level=logging.WARNING):
+ def __init__(self,
+ interface,
+ options,
+ logging_level=logging.WARNING,
+ generate_static_extensions=False):
super(Dart2JSBackend, self).__init__(interface, options, False, _logger)
+ self._generate_static_extensions = generate_static_extensions
self._database = options.database
self._template_loader = options.templates
self._type_registry = options.type_registry
@@ -1253,6 +1276,7 @@
self._metadata = options.metadata
self._interface_type_info = self._type_registry.TypeInfo(
self._interface.id)
+ self._interface_name = self._interface_type_info.interface_name()
self._current_secondary_parent = None
self._library_name = self._renamer.GetLibraryName(self._interface)
# Global constants for all WebGLRenderingContextBase, WebGL2RenderingContextBase, WebGLDrawBuffers
@@ -1289,9 +1313,15 @@
# TODO(terry): There are no mutable maplikes yet.
template_file_content = self._template_loader.Load(
'dart2js_maplike_impl.darttemplate')
+
else:
- template_file_content = self._template_loader.Load(
- 'dart2js_impl.darttemplate')
+ if CanUseStaticExtensions(self._interface_name,
+ self._generate_static_extensions):
+ template_file_content = self._template_loader.Load(
+ 'dart2js_static_extension_impl.darttemplate')
+ else:
+ template_file_content = self._template_loader.Load(
+ 'dart2js_impl.darttemplate')
return template_file_content
def StartInterface(self, members_emitter):
@@ -1349,7 +1379,8 @@
def IsConstructorArgumentOptional(self, argument):
return argument.optional
- def EmitStaticFactoryOverload(self, constructor_info, name, arguments):
+ def EmitStaticFactoryOverload(self, constructor_info, name, arguments,
+ emmiter):
if self._interface_type_info.has_generated_interface():
# Use dart_type name, we're generating.
interface_name = self._interface_type_info.interface_name()
@@ -1361,7 +1392,7 @@
arguments = constructor_info.ParametersAsArgumentList(index)
if arguments:
arguments = ', ' + arguments
- self._members_emitter.Emit(
+ (emmiter if (emmiter != None) else self._members_emitter).Emit(
" static $INTERFACE_NAME $NAME($PARAMETERS) => "
"JS('$INTERFACE_NAME', 'new $CTOR_NAME($PLACEHOLDERS)'$ARGUMENTS);\n",
INTERFACE_NAME=interface_name,
@@ -1598,7 +1629,8 @@
if attribute.type.nullable:
promiseType += '?'
- template = '\n $RENAME$(ANNOTATIONS)$TYPE get $NAME => $PROMISE_CALL(JS("$TYPE_DESC", "#.$NAME", this));\n'
+ template = '\n $RENAME$(ANNOTATIONS)$TYPE get $NAME => '\
+ '$PROMISE_CALL(JS("$TYPE_DESC", "#.$NAME", this));\n'
self._members_emitter.Emit(
template,
@@ -1676,10 +1708,17 @@
\n
\n $STATIC $NONNULLTYPE get $HTML_NAME => _$HTML_NAME$NULLASSERT;"""
else:
- template = """\n $RENAME
- \n $METADATA
- \n $STATIC $TYPE get $HTML_NAME native;
- \n"""
+ if CanUseStaticExtensions(self._interface_name,
+ self._generate_static_extensions):
+ template = """\n $RENAME
+ \n $METADATA
+ \n $STATIC $TYPE get $HTML_NAME => js_util.getProperty(this, '$JSNAME');
+ \n"""
+ else:
+ template = """\n $RENAME
+ \n $METADATA
+ \n $STATIC $TYPE get $HTML_NAME native;
+ \n"""
self._members_emitter.Emit(template,
RENAME=rename if rename else '',
METADATA=metadata if metadata else '',
@@ -1687,7 +1726,8 @@
STATIC='static' if attr.is_static else '',
TYPE=return_type,
NULLASSERT='!',
- NONNULLTYPE=non_null_return_type)
+ NONNULLTYPE=non_null_return_type,
+ JSNAME=rename if rename else html_name)
def _AddRenamingSetter(self, attr, html_name, rename):
conversion = self._InputConversion(attr.type.id, attr.id)
@@ -1705,14 +1745,23 @@
if self._IsACompatibilityConflict(self._interface.id, attr):
# Force non-nullable if it's a manual conflict.
nullable_type = False
- self._members_emitter.Emit(
- '\n $RENAME'
- '\n $STATIC set $HTML_NAME($TYPE value) native;'
- '\n',
- RENAME=rename if rename else '',
- HTML_NAME=html_name,
- STATIC='static ' if attr.is_static else '',
- TYPE=self.SecureOutputType(attr.type.id, nullable=nullable_type))
+ if CanUseStaticExtensions(self._interface_name,
+ self._generate_static_extensions):
+ template = """\n $RENAME
+ \n $STATIC set $HTML_NAME($TYPE value)
+ => js_util.setProperty(this, '$JSNAME', value);
+ \n"""
+ else:
+ template = """\n $RENAME
+ \n $STATIC set $HTML_NAME($TYPE value) native;
+ \n"""
+ self._members_emitter.Emit(template,
+ RENAME=rename if rename else '',
+ HTML_NAME=html_name,
+ STATIC='static ' if attr.is_static else '',
+ TYPE=self.SecureOutputType(
+ attr.type.id, nullable=nullable_type),
+ JSNAME=rename if rename else html_name)
def _AddConvertingGetter(self, attr, html_name, conversion):
# dynamic should not be marked with ?
@@ -1950,19 +1999,25 @@
else:
self._members_emitter.Emit(
'\n'
+ ' $RENAME$METADATA$MODIFIERS$TYPE $NAME($PARAMS) => '\
+ 'js_util.callMethod(this, \'$JSNAME\', [$ARGS]);\n'
+ if CanUseStaticExtensions(self._interface_name, self._generate_static_extensions) else
' $RENAME$METADATA$MODIFIERS$TYPE $NAME($PARAMS) native;\n',
RENAME=self._RenamingAnnotation(info.declared_name, html_name),
- METADATA=self._Metadata(info.type_name, info.declared_name,
- self.SecureOutputType(info.type_name,
- nullable=info.type_nullable),
- info.type_nullable),
+ METADATA=self._Metadata(
+ info.type_name, info.declared_name,
+ self.SecureOutputType(info.type_name,
+ nullable=info.type_nullable),
+ info.type_nullable),
MODIFIERS='static ' if info.IsStatic() else '',
TYPE=self.SecureOutputType(resultType,
can_narrow_type=True,
nullable=info.type_nullable),
NAME=html_name,
PARAMS=info.ParametersAsDeclaration(self._NarrowInputType,
- force_optional))
+ force_optional),
+ ARGS=info.ParametersAsArgumentList(),
+ JSNAME=info.declared_name if info.declared_name != html_name else html_name)
def _AddOperationWithConversions(self, info, html_name):
# Assert all operations have same return type.
@@ -2029,7 +2084,8 @@
)
self._members_emitter.Emit(
' $RENAME$METADATA$MODIFIERS$TYPE$TARGET($PARAMS) =>\n'
- ' promiseToFuture(JS("", "#.$JSNAME($HASH_STR)", this$CALLING_PARAMS));\n',
+ ' promiseToFuture(JS("", "#.$JSNAME($HASH_STR)"'\
+ ', this$CALLING_PARAMS));\n',
RENAME=self._RenamingAnnotation(info.declared_name, target),
METADATA=self._Metadata(info.type_name, info.declared_name,
None, info.type_nullable),
diff --git a/tools/dom/templates/dart2js_static_extension_impl.darttemplate b/tools/dom/templates/dart2js_static_extension_impl.darttemplate
new file mode 100644
index 0000000..e2b3c61
--- /dev/null
+++ b/tools/dom/templates/dart2js_static_extension_impl.darttemplate
@@ -0,0 +1,11 @@
+// Copyright (c) 2012, 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.
+
+part of $LIBRARYNAME;
+
+$(ANNOTATIONS)@sealed$(NATIVESPEC)$(CLASS_MODIFIERS)class $CLASSNAME$EXTENDS$MIXINS$IMPLEMENTS { $!CLASSMEMBERS }
+
+extension $(CLASSNAME)Extension on $CLASSNAME {
+$!EXTENSIONMEMBERS
+}