diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart index db9d9f4fb96..d76eb4f6011 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart @@ -231,16 +231,9 @@ class ClosureContext { : returnOrYieldContext; if (expectedType != null) { expectedType = greatestClosure(inferrer.coreTypes, expectedType); - DartType expectedTypeToCheck = expectedType; - if (!inferrer.isAssignable(expectedType, type) && isAsync) { - DartType unfuturedExpectedType = - inferrer.typeSchemaEnvironment.unfutureType(expectedType); - if (inferrer.isAssignable(unfuturedExpectedType, type)) { - expectedTypeToCheck = unfuturedExpectedType; - } - } if (inferrer.ensureAssignable( - expectedTypeToCheck, type, expression, fileOffset) != + expectedType, type, expression, fileOffset, + isReturnFromAsync: isAsync) != null) { type = expectedType; } @@ -452,8 +445,25 @@ abstract class TypeInferrerImpl extends TypeInferrer { /// Checks whether [actualType] can be assigned to [expectedType], and inserts /// an implicit downcast if appropriate. Expression ensureAssignable(DartType expectedType, DartType actualType, - Expression expression, int fileOffset) { + Expression expression, int fileOffset, + {bool isReturnFromAsync = false}) { assert(expectedType == null || isKnown(expectedType)); + + DartType initialExpectedType = expectedType; + if (isReturnFromAsync && !isAssignable(expectedType, actualType)) { + // If the body of the function is async, the expected return type has the + // shape FutureOr. We check both branches for FutureOr here: both T + // and Future. + DartType unfuturedExpectedType = + typeSchemaEnvironment.unfutureType(expectedType); + DartType futuredExpectedType = wrapFutureType(unfuturedExpectedType); + if (isAssignable(unfuturedExpectedType, actualType)) { + expectedType = unfuturedExpectedType; + } else if (isAssignable(futuredExpectedType, actualType)) { + expectedType = futuredExpectedType; + } + } + // We don't need to insert assignability checks when doing top level type // inference since top level type inference only cares about the type that // is inferred (the kernel code is discarded). @@ -506,7 +516,7 @@ abstract class TypeInferrerImpl extends TypeInferrer { } else { // Insert an implicit downcast. var parent = expression.parent; - var typeCheck = new AsExpression(expression, expectedType) + var typeCheck = new AsExpression(expression, initialExpectedType) ..isTypeError = true ..fileOffset = fileOffset; parent?.replaceChild(expression, typeCheck); diff --git a/pkg/front_end/testcases/future_or_test.dart b/pkg/front_end/testcases/future_or_test.dart new file mode 100644 index 00000000000..375589df6c5 --- /dev/null +++ b/pkg/front_end/testcases/future_or_test.dart @@ -0,0 +1,27 @@ +// Copyright (c) 2018, 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. + +// The test checks that an expression with static type Future is +// accepted as a return expression of a method with an async body and the +// declared return type Future. + +import 'dart:async'; + +class A { + dynamic foo() => null; +} + +class B { + A a; + + Future bar() async => a.foo(); +} + +class C { + B b = B(); + + Future baz() async => b.bar(); +} + +main() {} diff --git a/pkg/front_end/testcases/future_or_test.dart.direct.expect b/pkg/front_end/testcases/future_or_test.dart.direct.expect new file mode 100644 index 00000000000..ea64edd23d8 --- /dev/null +++ b/pkg/front_end/testcases/future_or_test.dart.direct.expect @@ -0,0 +1,29 @@ +library; +import self as self; +import "dart:core" as core; +import "dart:async" as asy; + +class A extends core::Object { + synthetic constructor •() → void + : super core::Object::•() + ; + method foo() → dynamic + return null; +} +class B extends core::Object { + field self::A a = null; + synthetic constructor •() → void + : super core::Object::•() + ; + method bar() → asy::Future async + return this.{self::B::a}.foo(); +} +class C extends core::Object { + field self::B b = new self::B::•(); + synthetic constructor •() → void + : super core::Object::•() + ; + method baz() → asy::Future async + return this.{self::C::b}.bar(); +} +static method main() → dynamic {} diff --git a/pkg/front_end/testcases/future_or_test.dart.outline.expect b/pkg/front_end/testcases/future_or_test.dart.outline.expect new file mode 100644 index 00000000000..e69e132ed0f --- /dev/null +++ b/pkg/front_end/testcases/future_or_test.dart.outline.expect @@ -0,0 +1,27 @@ +library; +import self as self; +import "dart:core" as core; +import "dart:async" as asy; + +class A extends core::Object { + synthetic constructor •() → void + ; + method foo() → dynamic + ; +} +class B extends core::Object { + field self::A a; + synthetic constructor •() → void + ; + method bar() → asy::Future + ; +} +class C extends core::Object { + field self::B b; + synthetic constructor •() → void + ; + method baz() → asy::Future + ; +} +static method main() → dynamic + ; diff --git a/pkg/front_end/testcases/future_or_test.dart.strong.expect b/pkg/front_end/testcases/future_or_test.dart.strong.expect new file mode 100644 index 00000000000..253d160f24a --- /dev/null +++ b/pkg/front_end/testcases/future_or_test.dart.strong.expect @@ -0,0 +1,29 @@ +library; +import self as self; +import "dart:core" as core; +import "dart:async" as asy; + +class A extends core::Object { + synthetic constructor •() → void + : super core::Object::•() + ; + method foo() → dynamic + return null; +} +class B extends core::Object { + field self::A a = null; + synthetic constructor •() → void + : super core::Object::•() + ; + method bar() → asy::Future async + return this.{self::B::a}.{self::A::foo}(); +} +class C extends core::Object { + field self::B b = new self::B::•(); + synthetic constructor •() → void + : super core::Object::•() + ; + method baz() → asy::Future async + return this.{self::C::b}.{self::B::bar}() as{TypeError} asy::FutureOr; +} +static method main() → dynamic {}