Version 2.16.0-71.0.dev
Merge commit 'e729bdab37405781645549eec30f71ebe05f84ee' into 'dev'
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index e2c1264..0fc0c32 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -84,6 +84,9 @@
}
@override
+ DecoratedType? _getCallMethodType(DecoratedType type) => null;
+
+ @override
DecoratedType _getTypeParameterTypeBound(DecoratedType type) {
return bounds[(type.type as TypeParameterType).element] ??
(throw StateError('Unknown bound for $type'));
@@ -241,8 +244,10 @@
/// assigned to so far inside it. Otherwise `null`.
Set<Element>? _elementsWrittenToInLocalFunction;
+ final LibraryElement _library;
+
EdgeBuilder(this.typeProvider, this._typeSystem, this._variables, this._graph,
- this.source, this.listener, this._decoratedClassHierarchy,
+ this.source, this.listener, this._decoratedClassHierarchy, this._library,
{this.instrumentation})
: _inheritanceManager = InheritanceManager3(),
_whereOrNullTransformer =
@@ -2365,6 +2370,20 @@
typeArguments: [type]);
@override
+ DecoratedType? _getCallMethodType(DecoratedType type) {
+ var typeType = type.type;
+ if (typeType is InterfaceType) {
+ var callMethod = typeType.lookUpMethod2('call', _library);
+ if (callMethod != null) {
+ return _variables!
+ .decoratedElementType(callMethod.declaration)
+ .substitute(type.asSubstitution);
+ }
+ }
+ return null;
+ }
+
+ @override
DecoratedType? _getTypeParameterTypeBound(DecoratedType type) {
// TODO(paulberry): once we've wired up flow analysis, return promoted
// bounds if applicable.
@@ -3528,6 +3547,19 @@
source: source, destination: destination, hard: hard);
return;
}
+ if (destinationType is FunctionType) {
+ var callMethodType = _getCallMethodType(source);
+ if (callMethodType != null) {
+ // Handle implicit `.call` coercion
+ _checkAssignment(origin, edgeTarget,
+ source: callMethodType,
+ destination: destination,
+ hard: false,
+ checkable: false,
+ sourceIsFunctionLiteral: sourceIsFunctionLiteral);
+ return;
+ }
+ }
// A side cast. This may be an explicit side cast, or illegal code. There
// is no nullability we can infer here.
assert(
@@ -3798,6 +3830,11 @@
EdgeOrigin origin, FixReasonTarget edgeTarget,
{bool hard = false, bool checkable = true});
+ /// If [type] represents a class containing a `call` method, returns the
+ /// decorated type of the `call` method, with appropriate substitutions.
+ /// Otherwise returns `null`.
+ DecoratedType? _getCallMethodType(DecoratedType type);
+
/// Given a [type] representing a type parameter, retrieves the type's bound.
DecoratedType? _getTypeParameterTypeBound(DecoratedType type);
}
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index 44f4d37..0caee26 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -672,6 +672,31 @@
return null;
}
+ bool _isSubtypeOrCoercible(DartType type, DartType context) {
+ var fixBuilder = _fixBuilder!;
+ var typeSystem = fixBuilder._typeSystem;
+ if (typeSystem.isSubtypeOf(type, context)) {
+ return true;
+ }
+ if (context is FunctionType && type is InterfaceType) {
+ var callMethod =
+ type.lookUpMethod2('call', fixBuilder.unit!.declaredElement!.library);
+ if (callMethod != null) {
+ var variables = fixBuilder._variables!;
+ var callMethodType = variables.toFinalType(
+ variables.decoratedElementType(callMethod.declaration));
+ if (callMethod is MethodMember) {
+ callMethodType =
+ callMethod.substitution.substituteType(callMethodType);
+ }
+ if (typeSystem.isSubtypeOf(callMethodType, context)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
DartType _modifyRValueType(Expression node, DartType type,
{DartType? context}) {
if (node is MethodInvocation) {
@@ -697,7 +722,7 @@
var ancestor = _findNullabilityContextAncestor(node);
context ??=
InferenceContext.getContext(ancestor) ?? DynamicTypeImpl.instance;
- if (!_fixBuilder!._typeSystem.isSubtypeOf(type, context)) {
+ if (!_isSubtypeOrCoercible(type, context)) {
var transformationInfo =
_fixBuilder!._whereOrNullTransformer.tryTransformOrElseArgument(node);
if (transformationInfo != null) {
diff --git a/pkg/nnbd_migration/lib/src/nullability_migration_impl.dart b/pkg/nnbd_migration/lib/src/nullability_migration_impl.dart
index 867f72a..a9d155e 100644
--- a/pkg/nnbd_migration/lib/src/nullability_migration_impl.dart
+++ b/pkg/nnbd_migration/lib/src/nullability_migration_impl.dart
@@ -236,6 +236,7 @@
unit.declaredElement!.source,
_permissive! ? listener : null,
_decoratedClassHierarchy,
+ result.libraryElement,
instrumentation: _instrumentation));
} finally {
DecoratedTypeParameterBounds.current = null;
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 286aa4e..182d33a 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -877,6 +877,135 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_call_tearoff() async {
+ var content = '''
+class C {
+ void call() {}
+}
+void Function() f(C c) => c;
+''';
+ var expected = '''
+class C {
+ void call() {}
+}
+void Function() f(C c) => c;
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_call_tearoff_already_migrated() async {
+ var content = '''
+import 'already_migrated.dart';
+void Function() f(C c) => c;
+''';
+ var alreadyMigrated = '''
+// @dart=2.12
+class C {
+ void call() {}
+}
+''';
+ var expected = '''
+import 'already_migrated.dart';
+void Function() f(C c) => c;
+''';
+ await _checkSingleFileChanges(content, expected, migratedInput: {
+ '$projectPath/lib/already_migrated.dart': alreadyMigrated
+ });
+ }
+
+ Future<void>
+ test_call_tearoff_already_migrated_propagate_nullability() async {
+ var content = '''
+import 'already_migrated.dart';
+Map<int, String> Function() f(C c) => c;
+''';
+ var alreadyMigrated = '''
+// @dart=2.12
+class C {
+ Map<int, String?> call() => {};
+}
+''';
+ var expected = '''
+import 'already_migrated.dart';
+Map<int, String?> Function() f(C c) => c;
+''';
+ await _checkSingleFileChanges(content, expected, migratedInput: {
+ '$projectPath/lib/already_migrated.dart': alreadyMigrated
+ });
+ }
+
+ Future<void> test_call_tearoff_already_migrated_with_substitution() async {
+ var content = '''
+import 'already_migrated.dart';
+Map<int, String> Function() f(C<String/*?*/> c) => c;
+''';
+ var alreadyMigrated = '''
+// @dart=2.12
+class C<T> {
+ Map<int, T> call() => {};
+}
+''';
+ var expected = '''
+import 'already_migrated.dart';
+Map<int, String?> Function() f(C<String?> c) => c;
+''';
+ await _checkSingleFileChanges(content, expected, migratedInput: {
+ '$projectPath/lib/already_migrated.dart': alreadyMigrated
+ });
+ }
+
+ Future<void> test_call_tearoff_inherited() async {
+ var content = '''
+class B {
+ void call() {}
+}
+class C extends B {}
+void Function() f(C c) => c;
+''';
+ var expected = '''
+class B {
+ void call() {}
+}
+class C extends B {}
+void Function() f(C c) => c;
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_call_tearoff_inherited_propagate_nullability() async {
+ var content = '''
+class B {
+ Map<int, String> call() => {1: null};
+}
+class C extends B {}
+Map<int, String> Function() f(C c) => c;
+''';
+ var expected = '''
+class B {
+ Map<int, String?> call() => {1: null};
+}
+class C extends B {}
+Map<int, String?> Function() f(C c) => c;
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_call_tearoff_propagate_nullability() async {
+ var content = '''
+class C {
+ Map<int, String> call() => {1: null};
+}
+Map<int, String> Function() f(C c) => c;
+''';
+ var expected = '''
+class C {
+ Map<int, String?> call() => {1: null};
+}
+Map<int, String?> Function() f(C c) => c;
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_catch_simple() async {
var content = '''
void f() {
@@ -4938,6 +5067,18 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_implicit_tearoff_type_arguments() async {
+ var content = '''
+T f<T>(T t) => t;
+int Function(int) g() => f;
+''';
+ var expected = '''
+T f<T>(T t) => t;
+int Function(int) g() => f;
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_implicit_type_parameter_bound_nullable() async {
var content = '''
class C<T> {
@@ -5183,6 +5324,16 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_int_double_coercion() async {
+ var content = '''
+double f() => 0;
+''';
+ var expected = '''
+double f() => 0;
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_is_promotion_implies_non_nullable() async {
var content = '''
bool f(Object o) => o is int && o.isEven;
diff --git a/pkg/nnbd_migration/test/migration_visitor_test_base.dart b/pkg/nnbd_migration/test/migration_visitor_test_base.dart
index 9a4294b..85448d0 100644
--- a/pkg/nnbd_migration/test/migration_visitor_test_base.dart
+++ b/pkg/nnbd_migration/test/migration_visitor_test_base.dart
@@ -147,8 +147,15 @@
Future<CompilationUnit> analyze(String code) async {
var unit = await super.analyze(code);
decoratedClassHierarchy = DecoratedClassHierarchy(variables, graph);
- unit.accept(EdgeBuilder(typeProvider, typeSystem, variables, graph,
- testSource, null, decoratedClassHierarchy));
+ unit.accept(EdgeBuilder(
+ typeProvider,
+ typeSystem,
+ variables,
+ graph,
+ testSource,
+ null,
+ decoratedClassHierarchy,
+ unit.declaredElement!.library));
return unit;
}
}
diff --git a/tools/VERSION b/tools/VERSION
index ae9a195..9dfdd80 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 16
PATCH 0
-PRERELEASE 70
+PRERELEASE 71
PRERELEASE_PATCH 0
\ No newline at end of file