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:
committed by
commit-bot@chromium.org
parent
8881ec77ec
commit
22447b9516
@@ -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 {}
|
||||
Reference in New Issue
Block a user