Skip to content

Commit 1c7aca0

Browse files
author
John Messerly
committed
fix #25944, improve Future.then inference
also fixes #25322, return values of async functions are T | F<T> [email protected], [email protected] Review URL: https://codereview.chromium.org/2208953002 .
1 parent bf08512 commit 1c7aca0

File tree

7 files changed

+319
-99
lines changed

7 files changed

+319
-99
lines changed

pkg/analyzer/lib/src/generated/resolver.dart

Lines changed: 126 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4697,8 +4697,6 @@ class InferenceContext {
46974697
* A stack of return types for all of the enclosing
46984698
* functions and methods.
46994699
*/
4700-
// TODO(leafp) Handle the implicit union type for Futures
4701-
// https://github.com/dart-lang/sdk/issues/25322
47024700
final List<DartType> _returnStack = <DartType>[];
47034701

47044702
InferenceContext._(this._errorReporter, TypeProvider typeProvider,
@@ -4727,9 +4725,12 @@ class InferenceContext {
47274725
if (_returnStack.isEmpty) {
47284726
return;
47294727
}
4730-
DartType inferred = _inferredReturn.last;
4731-
inferred = _typeSystem.getLeastUpperBound(_typeProvider, type, inferred);
4732-
_inferredReturn[_inferredReturn.length - 1] = inferred;
4728+
DartType context = _returnStack.last;
4729+
if (context is! FutureUnionType) {
4730+
DartType inferred = _inferredReturn.last;
4731+
inferred = _typeSystem.getLeastUpperBound(_typeProvider, type, inferred);
4732+
_inferredReturn[_inferredReturn.length - 1] = inferred;
4733+
}
47334734
}
47344735

47354736
/**
@@ -4766,8 +4767,7 @@ class InferenceContext {
47664767
* Push a block function body's return type onto the return stack.
47674768
*/
47684769
void pushReturnContext(BlockFunctionBody node) {
4769-
DartType returnType = getType(node);
4770-
_returnStack.add(returnType);
4770+
_returnStack.add(getContext(node));
47714771
_inferredReturn.add(BottomTypeImpl.instance);
47724772
}
47734773

@@ -4908,19 +4908,50 @@ class InferenceContext {
49084908
node?.setProperty(_typeProperty, null);
49094909
}
49104910

4911+
/**
4912+
* Look for a single contextual type attached to [node], and returns the type
4913+
* if found, otherwise null.
4914+
*
4915+
* If [node] has a contextual union type like `T | Future<T>` this will
4916+
* simplify it to only return `T`. If the caller can handle a union type,
4917+
* [getContext] should be used instead.
4918+
*/
4919+
static DartType getType(AstNode node) {
4920+
DartType t = getContext(node);
4921+
if (t is FutureUnionType) {
4922+
return t.type;
4923+
}
4924+
return t;
4925+
}
4926+
49114927
/**
49124928
* Look for contextual type information attached to [node]. Returns
49134929
* the type if found, otherwise null.
4930+
*
4931+
* If [node] has a contextual union type like `T | Future<T>` this will be
4932+
* returned. You can use [getType] if you prefer to only get the `T`.
49144933
*/
4915-
static DartType getType(AstNode node) => node?.getProperty(_typeProperty);
4934+
static DartType getContext(AstNode node) => node?.getProperty(_typeProperty);
4935+
4936+
/**
4937+
* Like [getContext] but expands a union type into a list of types.
4938+
*/
4939+
static Iterable<DartType> getTypes(AstNode node) {
4940+
DartType t = getContext(node);
4941+
if (t == null) {
4942+
return DartType.EMPTY_LIST;
4943+
}
4944+
if (t is FutureUnionType) {
4945+
return t.types;
4946+
}
4947+
return <DartType>[t];
4948+
}
49164949

49174950
/**
49184951
* Attach contextual type information [type] to [node] for use during
49194952
* inference.
49204953
*/
49214954
static void setType(AstNode node, DartType type) {
4922-
// TODO(jmesserly): this sets the type even when it's dynamic.
4923-
// Can we skip that?
49244955
node?.setProperty(_typeProperty, type);
49254956
}
49264957

@@ -4929,7 +4960,7 @@ class InferenceContext {
49294960
* inference.
49304961
*/
49314962
static void setTypeFromNode(AstNode innerNode, AstNode outerNode) {
4932-
setType(innerNode, getType(outerNode));
4963+
setType(innerNode, getContext(outerNode));
49334964
}
49344965
}
49354966

@@ -6013,13 +6044,11 @@ class ResolverVisitor extends ScopedVisitor {
60136044

60146045
@override
60156046
Object visitAwaitExpression(AwaitExpression node) {
6016-
// TODO(leafp): Handle the implicit union type here
6017-
// https://github.com/dart-lang/sdk/issues/25322
6018-
DartType contextType = InferenceContext.getType(node);
6047+
DartType contextType = InferenceContext.getContext(node);
60196048
if (contextType != null) {
6020-
InterfaceType futureT = typeProvider.futureType
6021-
.instantiate([contextType.flattenFutures(typeSystem)]);
6022-
InferenceContext.setType(node.expression, futureT);
6049+
var futureUnion =
6050+
FutureUnionType.from(contextType, typeProvider, typeSystem);
6051+
InferenceContext.setType(node.expression, futureUnion);
60236052
}
60246053
return super.visitAwaitExpression(node);
60256054
}
@@ -6073,7 +6102,7 @@ class ResolverVisitor extends ScopedVisitor {
60736102
if (operatorType == TokenType.QUESTION_QUESTION) {
60746103
// Set the right side, either from the context, or using the information
60756104
// from the left side if it is more precise.
6076-
DartType contextType = InferenceContext.getType(node);
6105+
DartType contextType = InferenceContext.getContext(node);
60776106
DartType leftType = leftOperand?.staticType;
60786107
if (contextType == null || contextType.isDynamic) {
60796108
contextType = leftType;
@@ -6551,8 +6580,24 @@ class ResolverVisitor extends ScopedVisitor {
65516580
matchFunctionTypeParameters(node.typeParameters, functionType);
65526581
if (functionType is FunctionType) {
65536582
_inferFormalParameterList(node.parameters, functionType);
6554-
DartType returnType =
6555-
_computeReturnOrYieldType(functionType.returnType);
6583+
6584+
DartType returnType;
6585+
if (_isFutureThenLambda(node)) {
6586+
var futureThenType =
6587+
InferenceContext.getContext(node.parent) as FunctionType;
6588+
6589+
// Pretend the return type of Future<T>.then<S> first parameter is
6590+
//
6591+
// T -> (S | Future<S>)
6592+
//
6593+
// We can't represent this in Dart so we populate it here during
6594+
// inference.
6595+
returnType = FutureUnionType.from(
6596+
futureThenType.returnType, typeProvider, typeSystem);
6597+
} else {
6598+
returnType = _computeReturnOrYieldType(functionType.returnType);
6599+
}
6600+
65566601
InferenceContext.setType(node.body, returnType);
65576602
}
65586603
}
@@ -6668,30 +6713,37 @@ class ResolverVisitor extends ScopedVisitor {
66686713
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
66696714
TypeName classTypeName = node.constructorName.type;
66706715
if (classTypeName.typeArguments == null) {
6671-
DartType contextType = InferenceContext.getType(node);
6672-
if (contextType is InterfaceType &&
6673-
contextType.typeArguments != null &&
6674-
contextType.typeArguments.length > 0) {
6675-
// TODO(jmesserly): for generic methods we use the
6676-
// StrongTypeSystemImpl.inferGenericFunctionCall, which appears to
6677-
// be a tad more powerful than matchTypes.
6678-
//
6679-
// For example it can infer this case:
6680-
//
6681-
// class E<S, T> extends A<C<S>, T> { ... }
6682-
// A<C<int>, String> a0 = /*infer<int, String>*/new E("hello");
6683-
//
6684-
// See _inferArgumentTypesFromContext in this file for use of it.
6685-
List<DartType> targs =
6686-
inferenceContext.matchTypes(classTypeName.type, contextType);
6687-
if (targs != null && targs.any((t) => !t.isDynamic)) {
6688-
ClassElement classElement = classTypeName.type.element;
6689-
InterfaceType rawType = classElement.type;
6690-
InterfaceType fullType =
6691-
rawType.substitute2(targs, rawType.typeArguments);
6692-
// The element resolver uses the type on the constructor name, so
6693-
// infer it first
6694-
typeAnalyzer.inferConstructorName(node.constructorName, fullType);
6716+
// Given a union of context types ` T0 | T1 | ... | Tn`, find the first
6717+
// valid instantiation `new C<Ti>`, if it exists.
6718+
// TODO(jmesserly): if we support union types for real, `new C<Ti | Tj>`
6719+
// will become a valid possibility. Right now the only allowed union is
6720+
// `T | Future<T>` so we can take a simple approach.
6721+
for (var contextType in InferenceContext.getTypes(node)) {
6722+
if (contextType is InterfaceType &&
6723+
contextType.typeArguments != null &&
6724+
contextType.typeArguments.isNotEmpty) {
6725+
// TODO(jmesserly): for generic methods we use the
6726+
// StrongTypeSystemImpl.inferGenericFunctionCall, which appears to
6727+
// be a tad more powerful than matchTypes.
6728+
//
6729+
// For example it can infer this case:
6730+
//
6731+
// class E<S, T> extends A<C<S>, T> { ... }
6732+
// A<C<int>, String> a0 = /*infer<int, String>*/new E("hello");
6733+
//
6734+
// See _inferArgumentTypesFromContext in this file for use of it.
6735+
List<DartType> targs =
6736+
inferenceContext.matchTypes(classTypeName.type, contextType);
6737+
if (targs != null && targs.any((t) => !t.isDynamic)) {
6738+
ClassElement classElement = classTypeName.type.element;
6739+
InterfaceType rawType = classElement.type;
6740+
InterfaceType fullType =
6741+
rawType.substitute2(targs, rawType.typeArguments);
6742+
// The element resolver uses the type on the constructor name, so
6743+
// infer it first
6744+
typeAnalyzer.inferConstructorName(node.constructorName, fullType);
6745+
break;
6746+
}
66956747
}
66966748
}
66976749
}
@@ -6805,7 +6857,7 @@ class ResolverVisitor extends ScopedVisitor {
68056857

68066858
@override
68076859
Object visitNamedExpression(NamedExpression node) {
6808-
InferenceContext.setType(node.expression, InferenceContext.getType(node));
6860+
InferenceContext.setTypeFromNode(node.expression, node);
68096861
return super.visitNamedExpression(node);
68106862
}
68116863

@@ -6819,7 +6871,7 @@ class ResolverVisitor extends ScopedVisitor {
68196871

68206872
@override
68216873
Object visitParenthesizedExpression(ParenthesizedExpression node) {
6822-
InferenceContext.setType(node.expression, InferenceContext.getType(node));
6874+
InferenceContext.setTypeFromNode(node.expression, node);
68236875
return super.visitParenthesizedExpression(node);
68246876
}
68256877

@@ -6937,7 +6989,7 @@ class ResolverVisitor extends ScopedVisitor {
69376989

69386990
@override
69396991
Object visitVariableDeclaration(VariableDeclaration node) {
6940-
InferenceContext.setType(node.initializer, InferenceContext.getType(node));
6992+
InferenceContext.setTypeFromNode(node.initializer, node);
69416993
super.visitVariableDeclaration(node);
69426994
VariableElement element = node.element;
69436995
if (element.initializer != null && node.initializer != null) {
@@ -7081,21 +7133,22 @@ class ResolverVisitor extends ScopedVisitor {
70817133
if (!isGenerator && !isAsynchronous) {
70827134
return declaredType;
70837135
}
7084-
if (isGenerator) {
7085-
if (declaredType is! InterfaceType) {
7086-
return null;
7136+
if (declaredType is InterfaceType) {
7137+
if (isGenerator) {
7138+
// If it's sync* we expect Iterable<T>
7139+
// If it's async* we expect Stream<T>
7140+
InterfaceType rawType = isAsynchronous
7141+
? typeProvider.streamDynamicType
7142+
: typeProvider.iterableDynamicType;
7143+
// Match the types to instantiate the type arguments if possible
7144+
List<DartType> typeArgs =
7145+
inferenceContext.matchTypes(rawType, declaredType);
7146+
return (typeArgs?.length == 1) ? typeArgs[0] : null;
70877147
}
7088-
// If it's synchronous, we expect Iterable<T>, otherwise Stream<T>
7089-
InterfaceType rawType = isAsynchronous
7090-
? typeProvider.streamDynamicType
7091-
: typeProvider.iterableDynamicType;
7092-
// Match the types to instantiate the type arguments if possible
7093-
List<DartType> typeArgs =
7094-
inferenceContext.matchTypes(rawType, declaredType);
7095-
return (typeArgs?.length == 1) ? typeArgs[0] : null;
7148+
// async functions expect `Future<T> | T`
7149+
return new FutureUnionType(declaredType, typeProvider, typeSystem);
70967150
}
7097-
// Must be asynchronous to reach here, so strip off any layers of Future
7098-
return declaredType.flattenFutures(typeSystem);
7151+
return declaredType;
70997152
}
71007153

71017154
/**
@@ -7339,6 +7392,19 @@ class ResolverVisitor extends ScopedVisitor {
73397392
return false;
73407393
}
73417394

7395+
/**
7396+
* Returns true if this expression is being passed to `Future.then`.
7397+
*
7398+
* If so we will apply special typing rules in strong mode, to handle the
7399+
* implicit union of `S | Future<S>`
7400+
*/
7401+
bool _isFutureThenLambda(FunctionExpression node) {
7402+
Element element = node.staticParameterElement?.enclosingElement;
7403+
return element is MethodElement &&
7404+
element.name == 'then' &&
7405+
element.enclosingElement.type.isDartAsyncFuture;
7406+
}
7407+
73427408
/**
73437409
* Return `true` if the given variable is accessed within a closure in the given
73447410
* [AstNode] and also mutated somewhere in variable scope. This information is only

pkg/analyzer/lib/src/generated/static_type_analyzer.dart

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,13 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
504504
// * BlockFunctionBody, if we inferred a type from yield/return.
505505
// * we also normalize bottom to dynamic here.
506506
if (_strongMode && (computedType.isBottom || computedType.isDynamic)) {
507-
computedType = InferenceContext.getType(body) ?? _dynamicType;
507+
DartType contextType = InferenceContext.getContext(body);
508+
if (contextType is FutureUnionType) {
509+
// TODO(jmesserly): can we do something better here?
510+
computedType = body.isAsynchronous ? contextType.type : _dynamicType;
511+
} else {
512+
computedType = contextType ?? _dynamicType;
513+
}
508514
recordInference = !computedType.isDynamic;
509515
}
510516

@@ -1970,8 +1976,17 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
19701976
}
19711977
}
19721978

1973-
return ts.inferGenericFunctionCall(_typeProvider, fnType, paramTypes,
1974-
argTypes, InferenceContext.getType(node));
1979+
DartType returnContext = InferenceContext.getContext(node);
1980+
DartType returnType;
1981+
if (returnContext is FutureUnionType) {
1982+
returnType = fnType.returnType.isDartAsyncFuture
1983+
? returnContext.futureOfType
1984+
: returnContext.type;
1985+
} else {
1986+
returnType = returnContext as DartType;
1987+
}
1988+
return ts.inferGenericFunctionCall(
1989+
_typeProvider, fnType, paramTypes, argTypes, returnType);
19751990
}
19761991
return null;
19771992
}

0 commit comments

Comments
 (0)