mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 21:40:07 +00:00
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:
parent
bf085120f6
commit
1c7aca0d6d
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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]); }
|
||||
''');
|
||||
}
|
||||
|
||||
|
|
|
@ -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].
|
||||
|
|
Loading…
Reference in a new issue