Fix bug in assignability check for return expressions of async bodies

Change-Id: Iaab2466ac1c9766861e72e9224a3206da6c42d45
Reviewed-on: https://dart-review.googlesource.com/42743
Commit-Queue: Dmitry Stefantsov <dmitryas@google.com>
Reviewed-by: Kevin Millikin <kmillikin@google.com>
This commit is contained in:
Dmitry Stefantsov
2018-02-22 13:15:14 +00:00
committed by commit-bot@chromium.org
parent 8881ec77ec
commit 22447b9516
5 changed files with 133 additions and 11 deletions
@@ -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<T>. We check both branches for FutureOr here: both T
// and Future<T>.
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);
@@ -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<dynamic> is
// accepted as a return expression of a method with an async body and the
// declared return type Future<int>.
import 'dart:async';
class A {
dynamic foo() => null;
}
class B {
A a;
Future<dynamic> bar() async => a.foo();
}
class C {
B b = B();
Future<int> baz() async => b.bar();
}
main() {}
@@ -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<dynamic> 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<core::int> async
return this.{self::C::b}.bar();
}
static method main() → dynamic {}
@@ -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<dynamic>
;
}
class C extends core::Object {
field self::B b;
synthetic constructor •() → void
;
method baz() → asy::Future<core::int>
;
}
static method main() → dynamic
;
@@ -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<dynamic> 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<core::int> async
return this.{self::C::b}.{self::B::bar}() as{TypeError} asy::FutureOr<core::int>;
}
static method main() → dynamic {}