diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index 56e4a75f4d37..db027ac33476 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -5766,17 +5766,27 @@ class ResolverVisitor extends ScopedVisitor { } /** - * Returns true if this method is `Future.then`. + * Returns true if this method is `Future.then` or an override thereof. * * If so we will apply special typing rules in strong mode, to handle the * implicit union of `S | Future` */ bool isFutureThen(Element element) { - return element is MethodElement && - element.name == 'then' && - element.enclosingElement.type.isDartAsyncFuture; + // If we are a method named then + if (element is MethodElement && element.name == 'then') { + DartType type = element.enclosingElement.type; + // On Future or a subtype, then we're good. + return (type.isDartAsyncFuture || isSubtypeOfFuture(type)); + } + return false; } + /** + * Returns true if this type is any subtype of the built in Future type. + */ + bool isSubtypeOfFuture(DartType type) => + typeSystem.isSubtypeOf(type, typeProvider.futureDynamicType); + /** * Given a downward inference type [fnType], and the declared * [typeParameterList] for a function expression, determines if we can enable @@ -6733,6 +6743,12 @@ class ResolverVisitor extends ScopedVisitor { @override Object visitInstanceCreationExpression(InstanceCreationExpression node) { TypeName classTypeName = node.constructorName.type; + // TODO(leafp): Currently, we may re-infer types here, since we + // sometimes resolve multiple times. We should really check that we + // have not already inferred something. However, the obvious ways to + // check this don't work, since we may have been instantiated + // to bounds in an earlier phase, and we *do* want to do inference + // in that case. if (classTypeName.typeArguments == null) { // Given a union of context types ` T0 | T1 | ... | Tn`, find the first // valid instantiation `new C`, if it exists. diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart index b3484a9669f1..628c340c7848 100644 --- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart +++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart @@ -1947,7 +1947,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { DartType returnContext = InferenceContext.getContext(node); DartType returnType; if (returnContext is FutureUnionType) { - returnType = fnType.returnType.isDartAsyncFuture + returnType = _resolver.isSubtypeOfFuture(fnType.returnType) ? returnContext.futureOfType : returnContext.type; } else { @@ -1974,7 +1974,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { // S or Future in a conditional. if (!argReturnType.isObject && !argReturnType.isDynamic) { DartType paramReturnType = fnType.typeFormals[0].type; - if (argReturnType.isDartAsyncFuture) { + if (_resolver.isSubtypeOfFuture(argReturnType)) { // Given an argument of (T) -> Future, instantiate with paramReturnType = _typeProvider.futureType.instantiate([paramReturnType]); @@ -1986,13 +1986,11 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { ..shareParameters(firstParamType.parameters) ..returnType = paramReturnType; function.type = new FunctionTypeImpl(function); - // Use this as the expected 1st parameter type. paramTypes[0] = function.type; } } } - return ts.inferGenericFunctionCall( _typeProvider, fnType, paramTypes, argTypes, returnType); } @@ -2016,6 +2014,13 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { return; } + // TODO(leafp): Currently, we may re-infer types here, since we + // sometimes resolve multiple times. We should really check that we + // have not already inferred something. However, the obvious ways to + // check this don't work, since we may have been instantiated + // to bounds in an earlier phase, and we *do* want to do inference + // in that case. + // Get back to the uninstantiated generic constructor. // TODO(jmesserly): should we store this earlier in resolution? // Or look it up, instead of jumping backwards through the Member? @@ -2076,7 +2081,6 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { } computedType = _computeReturnTypeOfFunction(body, computedType); - functionElement.returnType = computedType; _recordPropagatedTypeOfFunction(functionElement, node.body); _recordStaticType(node, functionElement.type); diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart index c32e5c52b17a..9a4a5cf8264f 100644 --- a/pkg/analyzer/lib/src/generated/type_system.dart +++ b/pkg/analyzer/lib/src/generated/type_system.dart @@ -1666,8 +1666,8 @@ class FutureUnionType extends TypeImpl { throw new UnsupportedError('Future unions are not used in typedefs'); /** - * Creates a union of `T | Future`, unless `T` is already a future-union, - * in which case it simply returns `T` + * Creates a union of `flatten(T) | Future`, unless `T` is + * already a future-union, in which case it simply returns `T` */ static DartType from( DartType type, TypeProvider provider, TypeSystem system) { diff --git a/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart b/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart index bca50680f91b..bd2c1fbf0380 100644 --- a/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart +++ b/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart @@ -207,6 +207,36 @@ class AstInferredTypeTest extends AbstractResynthesizeTest super.test_constructors_inferFromArguments_redirectingFactory(); } + @override + @failingTest + void test_futureThen() { + super.test_futureThen(); + } + + @override + @failingTest + void test_futureThen_conditional() { + super.test_futureThen_conditional(); + } + + @override + @failingTest + void test_futureThen_upwards() { + super.test_futureThen_upwards(); + } + + @override + @failingTest + void test_futureUnion_asyncConditional() { + super.test_futureUnion_asyncConditional(); + } + + @override + @failingTest + void test_futureUnion_downwards() { + super.test_futureUnion_downwards(); + } + @override @failingTest void test_genericMethods_inferJSBuiltin() { diff --git a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart index 6a8608d34c1b..5405293e7a44 100644 --- a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart +++ b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart @@ -1520,29 +1520,79 @@ int get y => null; } void test_futureThen() { - checkFile(''' + String build({String declared, String downwards, String upwards}) => ''' import 'dart:async'; -Future f; -Future t1 = f.then((_) async => await new Future.value(3)); -Future t2 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return await new Future.value(3);}); -Future t3 = f.then((_) async => 3); -Future t4 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return 3;}); -Future t5 = f.then((_) => new Future.value(3)); -Future t6 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) {return new Future.value(3);}); -Future t7 = f.then((_) async => new Future.value(3)); -Future t8 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return new Future.value(3);}); -'''); +class MyFuture implements Future { + MyFuture() {} + MyFuture.value(T x) {} + dynamic noSuchMethod(invocation); + MyFuture/**/ then/**/(dynamic f(T x), {Function onError}) => null; +} + +void main() { + $declared f; + $downwards t1 = f.then((_) async => await new $upwards.value(3)); + $downwards t2 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async { + return await new $upwards.value(3);}); + $downwards t3 = f.then((_) async => 3); + $downwards t4 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async { + return 3;}); + $downwards t5 = f.then((_) => new $upwards.value(3)); + $downwards t6 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) {return new $upwards.value(3);}); + $downwards t7 = f.then((_) async => new $upwards.value(3)); + $downwards t8 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async { + return new $upwards.value(3);}); +} +'''; + + checkFile( + build(declared: "MyFuture", downwards: "Future", upwards: "Future")); + checkFile( + build(declared: "MyFuture", downwards: "Future", upwards: "MyFuture")); + checkFile( + build(declared: "MyFuture", downwards: "MyFuture", upwards: "Future")); + checkFile(build( + declared: "MyFuture", downwards: "MyFuture", upwards: "MyFuture")); + checkFile( + build(declared: "Future", downwards: "Future", upwards: "MyFuture")); + checkFile( + build(declared: "Future", downwards: "Future", upwards: "Future")); } void test_futureThen_conditional() { - checkFile(''' + String build({String declared, String downwards, String upwards}) => ''' import 'dart:async'; -Future f; -Future t1 = f.then((x) async => x ? 2 : await new Future.value(3)); -Future t2 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(x) async {return await x ? 2 : new Future.value(3);}); -Future t5 = f.then((x) => x ? 2 : new Future.value(3)); -Future t6 = f.then((x) {return x ? 2 : new Future.value(3);}); -'''); +class MyFuture implements Future { + MyFuture() {} + MyFuture.value(T x) {} + dynamic noSuchMethod(invocation); + MyFuture/**/ then/**/(dynamic f(T x), {Function onError}) => null; +} + +void main() { + $declared f; + $downwards t1 = f.then(/*info:INFERRED_TYPE_CLOSURE*/ + (x) async => x ? 2 : await new $upwards.value(3)); + $downwards t2 = f.then(/*info:INFERRED_TYPE_CLOSURE,info:INFERRED_TYPE_CLOSURE*/(x) async { // TODO(leafp): Why the duplicate here? + return await x ? 2 : new $upwards.value(3);}); + $downwards t5 = f.then(/*info:INFERRED_TYPE_CLOSURE*/ + (x) => x ? 2 : new $upwards.value(3)); + $downwards t6 = f.then(/*info:INFERRED_TYPE_CLOSURE*/ + (x) {return x ? 2 : new $upwards.value(3);}); +} +'''; + checkFile( + build(declared: "MyFuture", downwards: "Future", upwards: "Future")); + checkFile( + build(declared: "MyFuture", downwards: "Future", upwards: "MyFuture")); + checkFile( + build(declared: "MyFuture", downwards: "MyFuture", upwards: "Future")); + checkFile(build( + declared: "MyFuture", downwards: "MyFuture", upwards: "MyFuture")); + checkFile( + build(declared: "Future", downwards: "Future", upwards: "MyFuture")); + checkFile( + build(declared: "Future", downwards: "Future", upwards: "Future")); } void test_futureThen_downwardsMethodTarget() { @@ -1561,18 +1611,32 @@ main() { void test_futureThen_upwards() { // Regression test for https://github.com/dart-lang/sdk/issues/27088. - checkFile(r''' + String build({String declared, String downwards, String upwards}) => ''' import 'dart:async'; -main() { +class MyFuture implements Future { + MyFuture() {} + MyFuture.value(T x) {} + dynamic noSuchMethod(invocation); + MyFuture/**/ then/**/(dynamic f(T x), {Function onError}) => null; +} + +void main() { var f = foo().then((_) => 2.3); - Future f2 = /*error:INVALID_ASSIGNMENT*/f; + $downwards f2 = /*error:INVALID_ASSIGNMENT*/f; // The unnecessary cast is to illustrate that we inferred for // the generic type args, even though we had a return type context. - Future f3 = /*info:UNNECESSARY_CAST*/foo().then((_) => 2.3) as Future; + $downwards f3 = /*info:UNNECESSARY_CAST*/foo().then( + (_) => 2.3) as $upwards; } -Future foo() async => 1; - '''); +$declared foo() => new $declared.value(1); + '''; + checkFile( + build(declared: "MyFuture", downwards: "Future", upwards: "Future")); + checkFile(build( + declared: "MyFuture", downwards: "MyFuture", upwards: "MyFuture")); + checkFile( + build(declared: "Future", downwards: "Future", upwards: "Future")); } void test_futureThen_upwardsFromBlock() { @@ -1590,31 +1654,68 @@ main() { } void test_futureUnion_asyncConditional() { - checkFile(''' + String build({String declared, String downwards, String upwards}) => ''' import 'dart:async'; - -Future g1(bool x) async { return x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42); } -Future g2(bool x) async => x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42); - -Future g3(bool x) async { - var y = x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42); +class MyFuture implements Future { + MyFuture() {} + MyFuture.value(T x) {} + dynamic noSuchMethod(invocation); + MyFuture/**/ then/**/(dynamic f(T x), {Function onError}) => null; +} + +$downwards g1(bool x) async { + return x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new $upwards.value(42); } +$downwards g2(bool x) async => + x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new $upwards.value(42); +$downwards g3(bool x) async { + var y = x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new $upwards.value(42); return y; } - '''); + '''; + checkFile(build(downwards: "Future", upwards: "Future")); + checkFile(build(downwards: "Future", upwards: "MyFuture")); } void test_futureUnion_downwards() { - checkFile(''' + String build({String declared, String downwards, String upwards}) { + // TODO(leafp): The use of matchTypes in visitInstanceCreationExpression + // in the resolver visitor isn't powerful enough to catch this for the + // subclass. See the TODO there. + var allocInfo = + (upwards == "Future") ? "/*info:INFERRED_TYPE_ALLOCATION*/" : ""; + return ''' import 'dart:async'; -Future f; +class MyFuture implements Future { + MyFuture() {} + MyFuture.value([T x]) {} + dynamic noSuchMethod(invocation); + MyFuture/**/ then/**/(dynamic f(T x), {Function onError}) => null; +} + +$declared f; // Instantiates Future -Future t1 = f.then((_) => /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/'hi')); +$downwards t1 = f.then((_) => + ${allocInfo}new $upwards.value( + /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/'hi')); // Instantiates List -Future> t2 = f.then((_) => /*info:INFERRED_TYPE_LITERAL*/[3]); -Future> g2() async { return /*info:INFERRED_TYPE_LITERAL*/[3]; } -Future> g3() async { return /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(/*info:INFERRED_TYPE_LITERAL*/[3]); } -'''); +$downwards> t2 = f.then((_) => /*info:INFERRED_TYPE_LITERAL*/[3]); +$downwards> g2() async { return /*info:INFERRED_TYPE_LITERAL*/[3]; } +$downwards> g3() async { + return /*info:INFERRED_TYPE_ALLOCATION*/new $upwards.value( + /*info:INFERRED_TYPE_LITERAL*/[3]); } +'''; + } + + ; + checkFile( + build(declared: "MyFuture", downwards: "Future", upwards: "Future")); + checkFile( + build(declared: "MyFuture", downwards: "Future", upwards: "MyFuture")); + checkFile( + build(declared: "Future", downwards: "Future", upwards: "Future")); + checkFile( + build(declared: "Future", downwards: "Future", upwards: "MyFuture")); } void test_genericMethods_basicDownwardInference() { @@ -2644,18 +2745,6 @@ main() { expect(fns[9].type.toString(), '() → Stream'); } - void test_inferReturnOfStatementLambda() { - // Regression test for https://github.com/dart-lang/sdk/issues/26139 - checkFile(r''' -List strings() { - var stuff = [].expand(/*info:INFERRED_TYPE_CLOSURE*/(i) { - return []; - }); - return stuff.toList(); -} - '''); - } - void test_inferred_nonstatic_field_depends_on_static_field_complex() { var mainUnit = checkFile(''' class C { @@ -3038,6 +3127,18 @@ class C { expect(f.type.toString(), '(bool) → int'); } + void test_inferReturnOfStatementLambda() { + // Regression test for https://github.com/dart-lang/sdk/issues/26139 + checkFile(r''' +List strings() { + var stuff = [].expand(/*info:INFERRED_TYPE_CLOSURE*/(i) { + return []; + }); + return stuff.toList(); +} + '''); + } + void test_inferStaticsTransitively() { addFile( '''