fix #25944, improve Future.then inference

also fixes #25322, return values of async functions are T | F<T>

R=leafp@google.com, vsm@google.com

Review URL: https://codereview.chromium.org/2208953002 .
This commit is contained in:
John Messerly 2016-08-09 16:04:26 -07:00
parent bf085120f6
commit 1c7aca0d6d
7 changed files with 319 additions and 99 deletions

View file

@ -4697,8 +4697,6 @@ class InferenceContext {
* A stack of return types for all of the enclosing
* functions and methods.
*/
// TODO(leafp) Handle the implicit union type for Futures
// https://github.com/dart-lang/sdk/issues/25322
final List<DartType> _returnStack = <DartType>[];
InferenceContext._(this._errorReporter, TypeProvider typeProvider,
@ -4727,9 +4725,12 @@ class InferenceContext {
if (_returnStack.isEmpty) {
return;
}
DartType inferred = _inferredReturn.last;
inferred = _typeSystem.getLeastUpperBound(_typeProvider, type, inferred);
_inferredReturn[_inferredReturn.length - 1] = inferred;
DartType context = _returnStack.last;
if (context is! FutureUnionType) {
DartType inferred = _inferredReturn.last;
inferred = _typeSystem.getLeastUpperBound(_typeProvider, type, inferred);
_inferredReturn[_inferredReturn.length - 1] = inferred;
}
}
/**
@ -4766,8 +4767,7 @@ class InferenceContext {
* Push a block function body's return type onto the return stack.
*/
void pushReturnContext(BlockFunctionBody node) {
DartType returnType = getType(node);
_returnStack.add(returnType);
_returnStack.add(getContext(node));
_inferredReturn.add(BottomTypeImpl.instance);
}
@ -4908,19 +4908,50 @@ class InferenceContext {
node?.setProperty(_typeProperty, null);
}
/**
* Look for a single contextual type attached to [node], and returns the type
* if found, otherwise null.
*
* If [node] has a contextual union type like `T | Future<T>` this will
* simplify it to only return `T`. If the caller can handle a union type,
* [getContext] should be used instead.
*/
static DartType getType(AstNode node) {
DartType t = getContext(node);
if (t is FutureUnionType) {
return t.type;
}
return t;
}
/**
* Look for contextual type information attached to [node]. Returns
* the type if found, otherwise null.
*
* If [node] has a contextual union type like `T | Future<T>` this will be
* returned. You can use [getType] if you prefer to only get the `T`.
*/
static DartType getType(AstNode node) => node?.getProperty(_typeProperty);
static DartType getContext(AstNode node) => node?.getProperty(_typeProperty);
/**
* Like [getContext] but expands a union type into a list of types.
*/
static Iterable<DartType> getTypes(AstNode node) {
DartType t = getContext(node);
if (t == null) {
return DartType.EMPTY_LIST;
}
if (t is FutureUnionType) {
return t.types;
}
return <DartType>[t];
}
/**
* Attach contextual type information [type] to [node] for use during
* inference.
*/
static void setType(AstNode node, DartType type) {
// TODO(jmesserly): this sets the type even when it's dynamic.
// Can we skip that?
node?.setProperty(_typeProperty, type);
}
@ -4929,7 +4960,7 @@ class InferenceContext {
* inference.
*/
static void setTypeFromNode(AstNode innerNode, AstNode outerNode) {
setType(innerNode, getType(outerNode));
setType(innerNode, getContext(outerNode));
}
}
@ -6013,13 +6044,11 @@ class ResolverVisitor extends ScopedVisitor {
@override
Object visitAwaitExpression(AwaitExpression node) {
// TODO(leafp): Handle the implicit union type here
// https://github.com/dart-lang/sdk/issues/25322
DartType contextType = InferenceContext.getType(node);
DartType contextType = InferenceContext.getContext(node);
if (contextType != null) {
InterfaceType futureT = typeProvider.futureType
.instantiate([contextType.flattenFutures(typeSystem)]);
InferenceContext.setType(node.expression, futureT);
var futureUnion =
FutureUnionType.from(contextType, typeProvider, typeSystem);
InferenceContext.setType(node.expression, futureUnion);
}
return super.visitAwaitExpression(node);
}
@ -6073,7 +6102,7 @@ class ResolverVisitor extends ScopedVisitor {
if (operatorType == TokenType.QUESTION_QUESTION) {
// Set the right side, either from the context, or using the information
// from the left side if it is more precise.
DartType contextType = InferenceContext.getType(node);
DartType contextType = InferenceContext.getContext(node);
DartType leftType = leftOperand?.staticType;
if (contextType == null || contextType.isDynamic) {
contextType = leftType;
@ -6551,8 +6580,24 @@ class ResolverVisitor extends ScopedVisitor {
matchFunctionTypeParameters(node.typeParameters, functionType);
if (functionType is FunctionType) {
_inferFormalParameterList(node.parameters, functionType);
DartType returnType =
_computeReturnOrYieldType(functionType.returnType);
DartType returnType;
if (_isFutureThenLambda(node)) {
var futureThenType =
InferenceContext.getContext(node.parent) as FunctionType;
// Pretend the return type of Future<T>.then<S> first parameter is
//
// T -> (S | Future<S>)
//
// We can't represent this in Dart so we populate it here during
// inference.
returnType = FutureUnionType.from(
futureThenType.returnType, typeProvider, typeSystem);
} else {
returnType = _computeReturnOrYieldType(functionType.returnType);
}
InferenceContext.setType(node.body, returnType);
}
}
@ -6668,30 +6713,37 @@ class ResolverVisitor extends ScopedVisitor {
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
TypeName classTypeName = node.constructorName.type;
if (classTypeName.typeArguments == null) {
DartType contextType = InferenceContext.getType(node);
if (contextType is InterfaceType &&
contextType.typeArguments != null &&
contextType.typeArguments.length > 0) {
// TODO(jmesserly): for generic methods we use the
// StrongTypeSystemImpl.inferGenericFunctionCall, which appears to
// be a tad more powerful than matchTypes.
//
// For example it can infer this case:
//
// class E<S, T> extends A<C<S>, T> { ... }
// A<C<int>, String> a0 = /*infer<int, String>*/new E("hello");
//
// See _inferArgumentTypesFromContext in this file for use of it.
List<DartType> targs =
inferenceContext.matchTypes(classTypeName.type, contextType);
if (targs != null && targs.any((t) => !t.isDynamic)) {
ClassElement classElement = classTypeName.type.element;
InterfaceType rawType = classElement.type;
InterfaceType fullType =
rawType.substitute2(targs, rawType.typeArguments);
// The element resolver uses the type on the constructor name, so
// infer it first
typeAnalyzer.inferConstructorName(node.constructorName, fullType);
// Given a union of context types ` T0 | T1 | ... | Tn`, find the first
// valid instantiation `new C<Ti>`, if it exists.
// TODO(jmesserly): if we support union types for real, `new C<Ti | Tj>`
// will become a valid possibility. Right now the only allowed union is
// `T | Future<T>` so we can take a simple approach.
for (var contextType in InferenceContext.getTypes(node)) {
if (contextType is InterfaceType &&
contextType.typeArguments != null &&
contextType.typeArguments.isNotEmpty) {
// TODO(jmesserly): for generic methods we use the
// StrongTypeSystemImpl.inferGenericFunctionCall, which appears to
// be a tad more powerful than matchTypes.
//
// For example it can infer this case:
//
// class E<S, T> extends A<C<S>, T> { ... }
// A<C<int>, String> a0 = /*infer<int, String>*/new E("hello");
//
// See _inferArgumentTypesFromContext in this file for use of it.
List<DartType> targs =
inferenceContext.matchTypes(classTypeName.type, contextType);
if (targs != null && targs.any((t) => !t.isDynamic)) {
ClassElement classElement = classTypeName.type.element;
InterfaceType rawType = classElement.type;
InterfaceType fullType =
rawType.substitute2(targs, rawType.typeArguments);
// The element resolver uses the type on the constructor name, so
// infer it first
typeAnalyzer.inferConstructorName(node.constructorName, fullType);
break;
}
}
}
}
@ -6805,7 +6857,7 @@ class ResolverVisitor extends ScopedVisitor {
@override
Object visitNamedExpression(NamedExpression node) {
InferenceContext.setType(node.expression, InferenceContext.getType(node));
InferenceContext.setTypeFromNode(node.expression, node);
return super.visitNamedExpression(node);
}
@ -6819,7 +6871,7 @@ class ResolverVisitor extends ScopedVisitor {
@override
Object visitParenthesizedExpression(ParenthesizedExpression node) {
InferenceContext.setType(node.expression, InferenceContext.getType(node));
InferenceContext.setTypeFromNode(node.expression, node);
return super.visitParenthesizedExpression(node);
}
@ -6937,7 +6989,7 @@ class ResolverVisitor extends ScopedVisitor {
@override
Object visitVariableDeclaration(VariableDeclaration node) {
InferenceContext.setType(node.initializer, InferenceContext.getType(node));
InferenceContext.setTypeFromNode(node.initializer, node);
super.visitVariableDeclaration(node);
VariableElement element = node.element;
if (element.initializer != null && node.initializer != null) {
@ -7081,21 +7133,22 @@ class ResolverVisitor extends ScopedVisitor {
if (!isGenerator && !isAsynchronous) {
return declaredType;
}
if (isGenerator) {
if (declaredType is! InterfaceType) {
return null;
if (declaredType is InterfaceType) {
if (isGenerator) {
// If it's sync* we expect Iterable<T>
// If it's async* we expect Stream<T>
InterfaceType rawType = isAsynchronous
? typeProvider.streamDynamicType
: typeProvider.iterableDynamicType;
// Match the types to instantiate the type arguments if possible
List<DartType> typeArgs =
inferenceContext.matchTypes(rawType, declaredType);
return (typeArgs?.length == 1) ? typeArgs[0] : null;
}
// If it's synchronous, we expect Iterable<T>, otherwise Stream<T>
InterfaceType rawType = isAsynchronous
? typeProvider.streamDynamicType
: typeProvider.iterableDynamicType;
// Match the types to instantiate the type arguments if possible
List<DartType> typeArgs =
inferenceContext.matchTypes(rawType, declaredType);
return (typeArgs?.length == 1) ? typeArgs[0] : null;
// async functions expect `Future<T> | T`
return new FutureUnionType(declaredType, typeProvider, typeSystem);
}
// Must be asynchronous to reach here, so strip off any layers of Future
return declaredType.flattenFutures(typeSystem);
return declaredType;
}
/**
@ -7339,6 +7392,19 @@ class ResolverVisitor extends ScopedVisitor {
return false;
}
/**
* Returns true if this expression is being passed to `Future.then`.
*
* If so we will apply special typing rules in strong mode, to handle the
* implicit union of `S | Future<S>`
*/
bool _isFutureThenLambda(FunctionExpression node) {
Element element = node.staticParameterElement?.enclosingElement;
return element is MethodElement &&
element.name == 'then' &&
element.enclosingElement.type.isDartAsyncFuture;
}
/**
* Return `true` if the given variable is accessed within a closure in the given
* [AstNode] and also mutated somewhere in variable scope. This information is only

View file

@ -504,7 +504,13 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
// * BlockFunctionBody, if we inferred a type from yield/return.
// * we also normalize bottom to dynamic here.
if (_strongMode && (computedType.isBottom || computedType.isDynamic)) {
computedType = InferenceContext.getType(body) ?? _dynamicType;
DartType contextType = InferenceContext.getContext(body);
if (contextType is FutureUnionType) {
// TODO(jmesserly): can we do something better here?
computedType = body.isAsynchronous ? contextType.type : _dynamicType;
} else {
computedType = contextType ?? _dynamicType;
}
recordInference = !computedType.isDynamic;
}
@ -1970,8 +1976,17 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
}
}
return ts.inferGenericFunctionCall(_typeProvider, fnType, paramTypes,
argTypes, InferenceContext.getType(node));
DartType returnContext = InferenceContext.getContext(node);
DartType returnType;
if (returnContext is FutureUnionType) {
returnType = fnType.returnType.isDartAsyncFuture
? returnContext.futureOfType
: returnContext.type;
} else {
returnType = returnContext as DartType;
}
return ts.inferGenericFunctionCall(
_typeProvider, fnType, paramTypes, argTypes, returnType);
}
return null;
}

View file

@ -15,7 +15,9 @@ import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/engine.dart'
show AnalysisContext, AnalysisOptionsImpl;
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/generated/utilities_dart.dart' show ParameterKind;
import 'package:analyzer/src/generated/utilities_general.dart'
show JenkinsSmiHash;
typedef bool _GuardedSubtypeChecker<T>(T t1, T t2, Set<Element> visited);
@ -282,23 +284,6 @@ class StrongTypeSystemImpl extends TypeSystem {
var inferringTypeSystem =
new _StrongInferenceTypeSystem(typeProvider, this, fnType.typeFormals);
// Special case inference for Future.then.
//
// We don't have union types, so Future<T>.then<S> is typed to take a
// callback `T -> S`. However, the lambda might actually return a
// Future<S>. So we handle that special case here.
if (argumentTypes.isNotEmpty && argumentTypes[0] is FunctionType) {
Element element = fnType?.element;
bool isFutureThen = element is MethodElement &&
element.name == 'then' &&
element.enclosingElement.type.isDartAsyncFuture;
if (isFutureThen) {
// Ignore return context. We'll let the onValue function's return type
// drive inference.
returnContextType = null;
}
}
if (returnContextType != null) {
inferringTypeSystem.isSubtypeOf(fnType.returnType, returnContextType);
}
@ -751,8 +736,8 @@ class StrongTypeSystemImpl extends TypeSystem {
return true;
}
// The types are void, dynamic, bottom, interface types, function types
// and type parameters. We proceed by eliminating these different classes
// The types are void, dynamic, bottom, interface types, function types,
// and type parameters. We proceed by eliminating these different classes
// from consideration.
// Trivially true.
@ -1340,6 +1325,28 @@ class TypeSystemImpl extends TypeSystem {
}
/// Tracks upper and lower type bounds for a set of type parameters.
///
/// This class is used by calling [isSubtypeOf]. When it encounters one of
/// the type parameters it is inferring, it will record the constraint, and
/// optimistically assume the constraint will be satisfied.
///
/// For example if we are inferring type parameter A, and we ask if
/// `A <: num`, this will record that A must be a subytpe of `num`. It also
/// handles cases when A appears as part of the structure of another type, for
/// example `Iterable<A> <: Iterable<num>` would infer the same constraint
/// (due to covariant generic types) as would `() -> A <: () -> num`. In
/// contrast `(A) -> void <: (num) -> void`.
///
/// Once the lower/upper bounds are determined, [_infer] should be called to
/// finish the inference. It will instantiate a generic function type with the
/// inferred types for each type parameter.
///
/// It can also optionally compute a partial solution, in case some of the type
/// parameters could not be inferred (because the constraints cannot be
/// satisfied), or bail on the inference when this happens.
///
/// As currently designed, an instance of this class should only be used to
/// infer a single call and discarded immediately afterwards.
class _StrongInferenceTypeSystem extends StrongTypeSystemImpl {
final TypeProvider _typeProvider;
@ -1578,6 +1585,100 @@ class _TypeParameterVariance {
}
}
/**
* A special union type of `Future<T> | T` used for Strong Mode inference.
*/
class FutureUnionType extends TypeImpl {
// TODO(jmesserly): a Set would be better.
//
// For now we know `Future<T> | T` is the only valid use, so we can rely on
// the order, which simplifies some things.
//
// This will need clean up before this can function as a real union type.
final List<DartType> _types;
/**
* Creates a union of `Future< flatten(T) > | flatten(T)`.
*/
factory FutureUnionType(
DartType type, TypeProvider provider, TypeSystem system) {
type = type.flattenFutures(system);
// The order of these types is important: T could be a type variable, so
// we want to try and match `Future<T>` before we try and match `T`.
return new FutureUnionType._([
provider.futureType.instantiate([type]),
type
]);
}
FutureUnionType._(this._types) : super(null, null);
DartType get futureOfType => _types[0];
DartType get type => _types[1];
Iterable<DartType> get types => _types;
@override
void appendTo(StringBuffer buffer) {
buffer.write('(');
for (int i = 0; i < _types.length; i++) {
if (i != 0) {
buffer.write(' | ');
}
(_types[i] as TypeImpl).appendTo(buffer);
}
buffer.write(')');
}
@override
int get hashCode {
int hash = 0;
for (var t in types) {
hash = JenkinsSmiHash.combine(hash, t.hashCode);
}
return JenkinsSmiHash.finish(hash);
}
@override
bool operator ==(Object obj) {
if (obj is FutureUnionType) {
if (identical(obj, this)) return true;
return types.length == obj.types.length &&
types.toSet().containsAll(obj.types);
}
return false;
}
@override
bool isMoreSpecificThan(DartType type,
[bool withDynamic = false, Set<Element> visitedElements]) =>
throw new UnsupportedError(
'Future unions are not part of the Dart 1 type system');
@override
TypeImpl pruned(List<FunctionTypeAliasElement> prune) =>
throw new UnsupportedError('Future unions are not substituted');
@override
DartType substitute2(List<DartType> args, List<DartType> params,
[List<FunctionTypeAliasElement> prune]) =>
throw new UnsupportedError('Future unions are not used in typedefs');
/**
* Creates a union of `T | Future<T>`, unless `T` is already a future-union,
* in which case it simply returns `T`
*/
static DartType from(
DartType type, TypeProvider provider, TypeSystem system) {
if (type is FutureUnionType) {
return type;
}
return new FutureUnionType(type, provider, system);
}
}
bool _isBottom(DartType t, {bool dynamicIsBottom: false}) {
return (t.isDynamic && dynamicIsBottom) || t.isBottom;
}

View file

@ -121,9 +121,7 @@ class StrongModeDownwardsInferenceTest extends ResolverTestCase {
check("f2", _isFutureOfDynamic);
check("f3", _isFutureOfInt);
// This should be int when we handle the implicit Future<T> | T union
// https://github.com/dart-lang/sdk/issues/25322
check("f4", _isFutureOfDynamic);
check("f4", _isFutureOfInt);
check("f5", _isFutureOfInt);
check("g0", _isFutureOfDynamic);
@ -131,9 +129,7 @@ class StrongModeDownwardsInferenceTest extends ResolverTestCase {
check("g2", _isFutureOfDynamic);
check("g3", _isFutureOfInt);
// This should be int when we handle the implicit Future<T> | T union
// https://github.com/dart-lang/sdk/issues/25322
check("g4", _isFutureOfDynamic);
check("g4", _isFutureOfInt);
check("g5", _isFutureOfInt);
}
@ -181,9 +177,7 @@ class StrongModeDownwardsInferenceTest extends ResolverTestCase {
check("f2", _isFutureOfDynamic);
check("f3", _isFutureOfInt);
// This should be int when we handle the implicit Future<T> | T union
// https://github.com/dart-lang/sdk/issues/25322
check("f4", _isFutureOfDynamic);
check("f4", _isFutureOfInt);
check("f5", _isFutureOfInt);
check("g0", _isFutureOfDynamic);
@ -191,9 +185,7 @@ class StrongModeDownwardsInferenceTest extends ResolverTestCase {
check("g2", _isFutureOfDynamic);
check("g3", _isFutureOfInt);
// This should be int when we handle the implicit Future<T> | T union
// https://github.com/dart-lang/sdk/issues/25322
check("g4", _isFutureOfDynamic);
check("g4", _isFutureOfInt);
check("g5", _isFutureOfInt);
}

View file

@ -29,7 +29,7 @@ class Future<T> {
static Future<List/*<T>*/> wait/*<T>*/(
Iterable<Future/*<T>*/> futures) => null;
Future/*<R>*/ then/*<R>*/(/*=R*/ onValue(T value)) => null;
Future/*<R>*/ then/*<R>*/(onValue(T value)) => null;
}
abstract class Completer<T> {

View file

@ -900,7 +900,7 @@ void main() {
import 'dart:async';
Future test() async {
dynamic d;
List<int> l0 = /*warning:DOWN_CAST_COMPOSITE should be pass*/await /*pass should be info:INFERRED_TYPE_LITERAL*/[d];
List<int> l0 = await /*info:INFERRED_TYPE_LITERAL*/[/*info:DYNAMIC_CAST*/d];
List<int> l1 = await /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(/*info:INFERRED_TYPE_LITERAL*/[/*info:DYNAMIC_CAST*/d]);
}
''');
@ -1523,7 +1523,53 @@ int get y => null;
checkFile('''
import 'dart:async';
Future f;
Future<int> t1 = f.then((_) => new Future<int>.value(42));
Future<int> t1 = f.then((_) async => await new Future<int>.value(3));
Future<int> t2 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return await new Future<int>.value(3);});
Future<int> t3 = f.then((_) async => 3);
Future<int> t4 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return 3;});
Future<int> t5 = f.then((_) => new Future<int>.value(3));
Future<int> t6 = f.then((_) {return new Future<int>.value(3);});
Future<int> t7 = f.then((_) async => new Future<int>.value(3));
Future<int> t8 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return new Future<int>.value(3);});
''');
}
void test_futureThen_conditional() {
checkFile('''
import 'dart:async';
Future<bool> f;
Future<int> t1 = f.then((x) async => x ? 2 : await new Future<int>.value(3));
Future<int> t2 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(x) async {return await x ? 2 : new Future<int>.value(3);});
Future<int> t5 = f.then((x) => x ? 2 : new Future<int>.value(3));
Future<int> t6 = f.then((x) {return x ? 2 : new Future<int>.value(3);});
''');
}
void test_futureUnion_asyncConditional() {
checkFile('''
import 'dart:async';
Future<int> g1(bool x) async { return x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42); }
Future<int> g2(bool x) async => x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42);
Future<int> g3(bool x) async {
var y = x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42);
return y;
}
''');
}
void test_futureUnion_downwards() {
checkFile('''
import 'dart:async';
Future f;
// Instantiates Future<int>
Future<int> t1 = f.then((_) => /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/'hi'));
// Instantiates List<int>
Future<List<int>> t2 = f.then((_) => /*info:INFERRED_TYPE_LITERAL*/[3]);
Future<List<int>> g2() async { return /*info:INFERRED_TYPE_LITERAL*/[3]; }
Future<List<int>> g3() async { return /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(/*info:INFERRED_TYPE_LITERAL*/[3]); }
''');
}

View file

@ -433,7 +433,7 @@ abstract class Future<T> {
* with a `test` parameter, instead of handling both value and error in a
* single [then] call.
*/
Future/*<S>*/ then/*<S>*/(/*=S*/ onValue(T value), { Function onError });
Future/*<S>*/ then/*<S>*/(onValue(T value), { Function onError });
/**
* Handles errors emitted by this [Future].