Chromium Code Reviews| Index: pkg/analyzer/lib/src/generated/resolver.dart |
| diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart |
| index afa7ccec98ee9c48746663cc1c1a9893c0b0b541..b432e0dbb5de86b584e4e12355c5cf2b2c585399 100644 |
| --- a/pkg/analyzer/lib/src/generated/resolver.dart |
| +++ b/pkg/analyzer/lib/src/generated/resolver.dart |
| @@ -4689,9 +4689,7 @@ 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>[]; |
| + final List<TypeContext> _returnStack = <TypeContext>[]; |
| InferenceContext._(this._errorReporter, TypeProvider typeProvider, |
| this._typeSystem, this._inferenceHints) |
| @@ -4706,7 +4704,7 @@ class InferenceContext { |
| * functions returning Future<T> and for generator functions |
| * returning Stream<T> or Iterable<T>, this is T. |
| */ |
| - DartType get returnContext => |
| + TypeContext get returnContext => |
| _returnStack.isNotEmpty ? _returnStack.last : null; |
| /** |
| @@ -4719,10 +4717,10 @@ class InferenceContext { |
| if (_returnStack.isEmpty) { |
| return; |
| } |
| - DartType context = _returnStack.last; |
| - if (context == null || context.isDynamic) { |
| + TypeContext context = _returnStack.last; |
| + if (context == null || context is DartType && context.isDynamic) { |
| DartType inferred = _inferredReturn.last; |
| - inferred = _typeSystem.getLeastUpperBound(_typeProvider, type, inferred); |
| + inferred = _typeSystem.getLeastUpperBound(_typeProvider, inferred, type); |
| _inferredReturn[_inferredReturn.length - 1] = inferred; |
| } |
| } |
| @@ -4762,8 +4760,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(getType(node)); |
| _inferredReturn.add(BottomTypeImpl.instance); |
| } |
| @@ -4905,16 +4902,46 @@ class InferenceContext { |
| } |
| /** |
| + * 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, |
| + * [getType] should be used instead. |
| + */ |
| + static DartType getNonFutureType(AstNode node) { |
|
Leaf
2016/08/05 22:32:35
I don't love this name. It seems to be that it wo
Jennifer Messerly
2016/08/08 21:59:26
Great idea! Yeah I didn't like those names either.
|
| + TypeContext t = getType(node); |
| + if (t is FutureUnionTypeContext) { |
| + return t.type; |
| + } |
| + return t; |
| + } |
| + |
| + /** |
| * Look for contextual type information attached to [node]. Returns |
| * the type if found, otherwise null. |
| */ |
| - static DartType getType(AstNode node) => node?.getProperty(_typeProperty); |
| + static TypeContext getType(AstNode node) => node?.getProperty(_typeProperty); |
| + |
| + /** |
| + * Like [getType] but expands a union type into a list of types. |
| + */ |
| + static Iterable<DartType> getTypes(AstNode node) { |
| + TypeContext t = getType(node); |
| + if (t == null) { |
| + return DartType.EMPTY_LIST; |
| + } |
| + if (t is FutureUnionTypeContext) { |
| + return t.types; |
| + } |
| + return <DartType>[t]; |
| + } |
| /** |
| * Attach contextual type information [type] to [node] for use during |
| * inference. |
| */ |
| - static void setType(AstNode node, DartType type) { |
| + static void setType(AstNode node, TypeContext type) { |
| node?.setProperty(_typeProperty, type); |
| } |
| @@ -5931,7 +5958,7 @@ class ResolverVisitor extends ScopedVisitor { |
| @override |
| Object visitArgumentList(ArgumentList node) { |
| - DartType callerType = InferenceContext.getType(node); |
| + DartType callerType = InferenceContext.getNonFutureType(node); |
| if (callerType is FunctionType) { |
| Map<String, DartType> namedParameterTypes = |
| callerType.namedParameterTypes; |
| @@ -6007,13 +6034,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); |
| + TypeContext contextType = InferenceContext.getType(node); |
| if (contextType != null) { |
| - InterfaceType futureT = typeProvider.futureType |
| - .instantiate([contextType.flattenFutures(typeSystem)]); |
| - InferenceContext.setType(node.expression, futureT); |
| + var futureUnion = |
| + FutureUnionTypeContext.from(contextType, typeProvider, typeSystem); |
| + InferenceContext.setType(node.expression, futureUnion); |
| } |
| return super.visitAwaitExpression(node); |
| } |
| @@ -6530,14 +6555,30 @@ class ResolverVisitor extends ScopedVisitor { |
| _enclosingFunction = node.element; |
| _overrideManager.enterScope(); |
| try { |
| - DartType functionType = InferenceContext.getType(node); |
| + DartType functionType = InferenceContext.getNonFutureType(node); |
| if (functionType is FunctionType) { |
| functionType = |
| matchFunctionTypeParameters(node.typeParameters, functionType); |
| if (functionType is FunctionType) { |
| _inferFormalParameterList(node.parameters, functionType); |
| - DartType returnType = |
| - _computeReturnOrYieldType(functionType.returnType); |
| + |
| + TypeContext returnType; |
| + if (_isFutureThenLambda(node)) { |
| + var futureThenType = |
| + InferenceContext.getType(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 = FutureUnionTypeContext.from( |
| + futureThenType.returnType, typeProvider, typeSystem); |
| + } else { |
| + returnType = _computeReturnOrYieldType(functionType.returnType); |
| + } |
| + |
| InferenceContext.setType(node.body, returnType); |
| } |
| } |
| @@ -6653,30 +6694,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; |
| + } |
| } |
| } |
| } |
| @@ -6699,7 +6747,7 @@ class ResolverVisitor extends ScopedVisitor { |
| @override |
| Object visitListLiteral(ListLiteral node) { |
| - DartType contextType = InferenceContext.getType(node); |
| + DartType contextType = InferenceContext.getNonFutureType(node); |
| List<DartType> targs = null; |
| if (node.typeArguments != null) { |
| targs = node.typeArguments.arguments.map((t) => t.type).toList(); |
| @@ -6724,7 +6772,7 @@ class ResolverVisitor extends ScopedVisitor { |
| @override |
| Object visitMapLiteral(MapLiteral node) { |
| - DartType contextType = InferenceContext.getType(node); |
| + DartType contextType = InferenceContext.getNonFutureType(node); |
| List<DartType> targs = null; |
| if (node.typeArguments != null) { |
| targs = node.typeArguments.arguments.map((t) => t.type).toList(); |
| @@ -6756,7 +6804,7 @@ class ResolverVisitor extends ScopedVisitor { |
| try { |
| _currentFunctionBody = node.body; |
| _enclosingFunction = node.element; |
| - DartType returnType = |
| + TypeContext returnType = |
| _computeReturnOrYieldType(_enclosingFunction.type?.returnType); |
| InferenceContext.setType(node.body, returnType); |
| super.visitMethodDeclaration(node); |
| @@ -6790,7 +6838,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); |
| } |
| @@ -6804,7 +6852,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); |
| } |
| @@ -6922,7 +6970,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) { |
| @@ -7058,7 +7106,7 @@ class ResolverVisitor extends ScopedVisitor { |
| * values which should be returned or yielded as appropriate. If a type |
| * cannot be computed from the declared return type, return null. |
| */ |
| - DartType _computeReturnOrYieldType(DartType declaredType) { |
| + TypeContext _computeReturnOrYieldType(DartType declaredType) { |
| bool isGenerator = _enclosingFunction.isGenerator; |
| bool isAsynchronous = _enclosingFunction.isAsynchronous; |
| @@ -7066,21 +7114,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 FutureUnionTypeContext(declaredType, typeProvider, typeSystem); |
| } |
| - // Must be asynchronous to reach here, so strip off any layers of Future |
| - return declaredType.flattenFutures(typeSystem); |
| + return declaredType; |
| } |
| /** |
| @@ -7153,7 +7202,7 @@ class ResolverVisitor extends ScopedVisitor { |
| DartType contextType = node.staticInvokeType; |
| if (contextType is FunctionType) { |
| DartType originalType = node.function.staticType; |
| - DartType returnContextType = InferenceContext.getType(node); |
| + DartType returnContextType = InferenceContext.getNonFutureType(node); |
| TypeSystem ts = typeSystem; |
| if (returnContextType != null && |
| node.typeArguments == null && |
| @@ -7311,6 +7360,19 @@ class ResolverVisitor extends ScopedVisitor { |
| } |
| /** |
| + * 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 |
| * available for local variables (including parameters). |