Migration: properly handle `dynamic` in code declared `external`.
Fixes #47307.
Bug: https://github.com/dart-lang/sdk/issues/47307
Change-Id: Iad6fa8a413e81cbd8902c223fa722c7da6850aa6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/214980
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/instrumentation.dart b/pkg/nnbd_migration/lib/instrumentation.dart
index c2700c1..1038d97 100644
--- a/pkg/nnbd_migration/lib/instrumentation.dart
+++ b/pkg/nnbd_migration/lib/instrumentation.dart
@@ -249,6 +249,7 @@
dynamicAssignment,
enumValue,
expressionChecks,
+ externalDynamic,
fieldFormalParameter,
fieldNotInitialized,
forEachVariable,
diff --git a/pkg/nnbd_migration/lib/src/edge_origin.dart b/pkg/nnbd_migration/lib/src/edge_origin.dart
index b12f9ec..58ffe89 100644
--- a/pkg/nnbd_migration/lib/src/edge_origin.dart
+++ b/pkg/nnbd_migration/lib/src/edge_origin.dart
@@ -194,6 +194,18 @@
EdgeOriginKind get kind => EdgeOriginKind.enumValue;
}
+/// Edge origin resulting from an explicit or implicit `dynamic` type annotation
+/// appearing in an external declaration.
+class ExternalDynamicOrigin extends EdgeOrigin {
+ ExternalDynamicOrigin(Source? source, AstNode node) : super(source, node);
+
+ @override
+ String get description => 'dynamic type in external declaration';
+
+ @override
+ EdgeOriginKind get kind => EdgeOriginKind.externalDynamic;
+}
+
/// Edge origin resulting from the relationship between a field formal parameter
/// and the corresponding field.
class FieldFormalParameterOrigin extends EdgeOrigin {
diff --git a/pkg/nnbd_migration/lib/src/node_builder.dart b/pkg/nnbd_migration/lib/src/node_builder.dart
index 6673352..653d36b 100644
--- a/pkg/nnbd_migration/lib/src/node_builder.dart
+++ b/pkg/nnbd_migration/lib/src/node_builder.dart
@@ -67,6 +67,10 @@
final TypeProvider _typeProvider;
+ /// Indicates whether the declaration currently being visited is marked
+ /// `external`.
+ bool _visitingExternalDeclaration = false;
+
NodeBuilder(this._variables, this.source, this.listener, this._graph,
this._typeProvider, this._getLineInfo,
{this.instrumentation});
@@ -196,7 +200,8 @@
node.parameters,
node.initializers,
node.body,
- node.redirectedConstructor);
+ node.redirectedConstructor,
+ isExternal: node.externalKeyword != null);
return null;
}
@@ -340,14 +345,16 @@
node.functionExpression.parameters,
null,
node.functionExpression.body,
- null);
+ null,
+ isExternal: node.externalKeyword != null);
return null;
}
@override
DecoratedType? visitFunctionExpression(FunctionExpression node) {
_handleExecutableDeclaration(node, node.declaredElement!, null, null,
- node.typeParameters, node.parameters, null, node.body, null);
+ node.typeParameters, node.parameters, null, node.body, null,
+ isExternal: false);
return null;
}
@@ -456,7 +463,8 @@
node.parameters,
null,
node.body,
- null);
+ null,
+ isExternal: node.externalKeyword != null);
if (declaredElement is PropertyAccessorElement) {
// Store a decorated type for the synthetic field so that in case we try
// to access it later we won't crash (this could happen due to errors in
@@ -537,6 +545,10 @@
var nullabilityNode = NullabilityNode.forTypeAnnotation(target);
var decoratedType = DecoratedType(type, nullabilityNode);
_variables!.recordDecoratedTypeAnnotation(source, node, decoratedType);
+ if (_visitingExternalDeclaration) {
+ _graph.makeNullableUnion(
+ nullabilityNode, ExternalDynamicOrigin(source, node));
+ }
return decoratedType;
}
var typeArguments = const <DecoratedType>[];
@@ -708,49 +720,62 @@
FormalParameterList? parameters,
NodeList<ConstructorInitializer>? initializers,
FunctionBody body,
- ConstructorName? redirectedConstructor) {
+ ConstructorName? redirectedConstructor,
+ {required bool isExternal}) {
metadata?.accept(this);
- var functionType = declaredElement.type;
- DecoratedType? decoratedReturnType;
- var target = NullabilityNodeTarget.element(declaredElement, _getLineInfo);
- if (returnType != null) {
- _pushNullabilityNodeTarget(target.returnType(), () {
- decoratedReturnType = returnType.accept(this);
- });
- } else if (declaredElement is ConstructorElement) {
- // Constructors have no explicit return type annotation, so use the
- // implicit return type.
- decoratedReturnType = _createDecoratedTypeForClass(
- declaredElement.enclosingElement, parameters!.parent);
- instrumentation?.implicitReturnType(source, node, decoratedReturnType);
- } else {
- // Inferred return type.
- decoratedReturnType = DecoratedType.forImplicitType(
- _typeProvider, functionType.returnType, _graph, target);
- instrumentation?.implicitReturnType(source, node, decoratedReturnType);
- }
- var previousPositionalParameters = _positionalParameters;
- var previousNamedParameters = _namedParameters;
- _positionalParameters = [];
- _namedParameters = {};
- DecoratedType decoratedFunctionType;
+ var previouslyVisitingExternalDeclaration = _visitingExternalDeclaration;
try {
- typeParameters?.accept(this);
- _pushNullabilityNodeTarget(target, () => parameters?.accept(this));
- redirectedConstructor?.accept(this);
- initializers?.accept(this);
- decoratedFunctionType = DecoratedType(functionType, _graph.never,
- returnType: decoratedReturnType,
- positionalParameters: _positionalParameters,
- namedParameters: _namedParameters);
- body.accept(this);
+ if (isExternal) {
+ _visitingExternalDeclaration = true;
+ }
+ var functionType = declaredElement.type;
+ DecoratedType? decoratedReturnType;
+ var target = NullabilityNodeTarget.element(declaredElement, _getLineInfo);
+ if (returnType != null) {
+ _pushNullabilityNodeTarget(target.returnType(), () {
+ decoratedReturnType = returnType.accept(this);
+ });
+ } else if (declaredElement is ConstructorElement) {
+ // Constructors have no explicit return type annotation, so use the
+ // implicit return type.
+ decoratedReturnType = _createDecoratedTypeForClass(
+ declaredElement.enclosingElement, parameters!.parent);
+ instrumentation?.implicitReturnType(source, node, decoratedReturnType);
+ } else {
+ // Inferred return type.
+ decoratedReturnType = DecoratedType.forImplicitType(
+ _typeProvider, functionType.returnType, _graph, target);
+ instrumentation?.implicitReturnType(source, node, decoratedReturnType);
+ if (isExternal && functionType.returnType.isDynamic) {
+ _graph.makeNullableUnion(
+ decoratedReturnType.node!, ExternalDynamicOrigin(source, node));
+ }
+ }
+ var previousPositionalParameters = _positionalParameters;
+ var previousNamedParameters = _namedParameters;
+ _positionalParameters = [];
+ _namedParameters = {};
+ DecoratedType decoratedFunctionType;
+ try {
+ typeParameters?.accept(this);
+ _pushNullabilityNodeTarget(target, () => parameters?.accept(this));
+ redirectedConstructor?.accept(this);
+ initializers?.accept(this);
+ decoratedFunctionType = DecoratedType(functionType, _graph.never,
+ returnType: decoratedReturnType,
+ positionalParameters: _positionalParameters,
+ namedParameters: _namedParameters);
+ body.accept(this);
+ } finally {
+ _positionalParameters = previousPositionalParameters;
+ _namedParameters = previousNamedParameters;
+ }
+ _variables!
+ .recordDecoratedElementType(declaredElement, decoratedFunctionType);
+ return decoratedFunctionType;
} finally {
- _positionalParameters = previousPositionalParameters;
- _namedParameters = previousNamedParameters;
+ _visitingExternalDeclaration = previouslyVisitingExternalDeclaration;
}
- _variables!
- .recordDecoratedElementType(declaredElement, decoratedFunctionType);
- return decoratedFunctionType;
}
DecoratedType? _handleFormalParameter(
@@ -768,6 +793,10 @@
} else {
decoratedType = DecoratedType.forImplicitType(
_typeProvider, declaredElement.type, _graph, target);
+ if (_visitingExternalDeclaration) {
+ _graph.makeNullableUnion(
+ decoratedType.node!, ExternalDynamicOrigin(source, node));
+ }
instrumentation?.implicitType(source, node, decoratedType);
}
} else {
@@ -776,6 +805,10 @@
decoratedReturnType = DecoratedType.forImplicitType(_typeProvider,
DynamicTypeImpl.instance, _graph, target.returnType());
instrumentation?.implicitReturnType(source, node, decoratedReturnType);
+ if (_visitingExternalDeclaration) {
+ _graph.makeNullableUnion(
+ decoratedReturnType.node!, ExternalDynamicOrigin(source, node));
+ }
} else {
decoratedReturnType = type.accept(this);
}
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 7e35d77..a13964d 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -3310,6 +3310,114 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_external_constructor() async {
+ var content = '''
+class C {
+ external C(dynamic Function(dynamic) callback);
+ static Object g(Object Function(Object) callback) => C(callback);
+}
+''';
+ var expected = '''
+class C {
+ external C(dynamic Function(dynamic) callback);
+ static Object g(Object Function(Object?) callback) => C(callback);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_external_function() async {
+ var content = '''
+external dynamic f();
+Object g() => f();
+''';
+ var expected = '''
+external dynamic f();
+Object? g() => f();
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_external_function_implicit_return() async {
+ var content = '''
+external f();
+Object g() => f();
+''';
+ var expected = '''
+external f();
+Object? g() => f();
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_external_function_implicit_variance() async {
+ var content = '''
+external void f(callback(x));
+void g(Object Function(Object) callback) => f(callback);
+''';
+ var expected = '''
+external void f(callback(x));
+void g(Object Function(Object?) callback) => f(callback);
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_external_function_implicit_variance_complex() async {
+ var content = '''
+external void f(callback(x()));
+void g(Object Function(Object Function()) callback) => f(callback);
+''';
+ var expected = '''
+external void f(callback(x()));
+void g(Object Function(Object? Function()) callback) => f(callback);
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_external_function_variance() async {
+ var content = '''
+external void f(dynamic Function(dynamic) callback);
+void g(Object Function(Object) callback) => f(callback);
+''';
+ var expected = '''
+external void f(dynamic Function(dynamic) callback);
+void g(Object Function(Object?) callback) => f(callback);
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_external_method() async {
+ var content = '''
+class C {
+ external dynamic f();
+ Object g() => f();
+}
+''';
+ var expected = '''
+class C {
+ external dynamic f();
+ Object? g() => f();
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_external_method_implicit() async {
+ var content = '''
+class C {
+ external f();
+ Object g() => f();
+}
+''';
+ var expected = '''
+class C {
+ external f();
+ Object? g() => f();
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_field_final_uninitalized_used() async {
var content = '''
class C {