Index: pkg/analyzer/lib/src/generated/type_system.dart |
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart |
index 1e1d1824caf386948df9cb9b1832c51cb8801a16..595d1a0afa0c9ae5408ecf94c021532e23b21fcc 100644 |
--- a/pkg/analyzer/lib/src/generated/type_system.dart |
+++ b/pkg/analyzer/lib/src/generated/type_system.dart |
@@ -8,7 +8,7 @@ import 'dart:collection'; |
import 'dart:math' as math; |
import 'package:analyzer/dart/ast/ast.dart' show AstNode; |
-import 'package:analyzer/dart/ast/token.dart' show TokenType; |
+import 'package:analyzer/dart/ast/token.dart' show Keyword, TokenType; |
import 'package:analyzer/dart/element/element.dart'; |
import 'package:analyzer/dart/element/type.dart'; |
import 'package:analyzer/error/listener.dart' show ErrorReporter; |
@@ -22,7 +22,10 @@ import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
import 'package:analyzer/src/generated/utilities_dart.dart' show ParameterKind; |
bool _isBottom(DartType t, {bool dynamicIsBottom: false}) { |
- return (t.isDynamic && dynamicIsBottom) || t.isBottom || t.isDartCoreNull; |
+ return (t.isDynamic && dynamicIsBottom) || |
+ t.isBottom || |
+ t.isDartCoreNull || |
+ identical(t, UnknownInferredType.instance); |
} |
bool _isTop(DartType t, {bool dynamicIsBottom: false}) { |
@@ -30,7 +33,9 @@ bool _isTop(DartType t, {bool dynamicIsBottom: false}) { |
if (t.isDartAsyncFutureOr) { |
return _isTop((t as InterfaceType).typeArguments[0]); |
} |
- return (t.isDynamic && !dynamicIsBottom) || t.isObject; |
+ return (t.isDynamic && !dynamicIsBottom) || |
+ t.isObject || |
+ identical(t, UnknownInferredType.instance); |
} |
typedef bool _GuardedSubtypeChecker<T>(T t1, T t2, Set<Element> visited); |
@@ -125,6 +130,14 @@ class StrongTypeSystemImpl extends TypeSystem { |
return type1; |
} |
+ // For any type T, GLB(?, T) == T. |
+ if (identical(type1, UnknownInferredType.instance)) { |
+ return type2; |
+ } |
+ if (identical(type2, UnknownInferredType.instance)) { |
+ return type1; |
+ } |
+ |
// The GLB of top and any type is just that type. |
// Also GLB of bottom and any type is bottom. |
if (_isTop(type1, dynamicIsBottom: dynamicIsBottom) || |
@@ -204,7 +217,7 @@ class StrongTypeSystemImpl extends TypeSystem { |
* Given a generic function type `F<T0, T1, ... Tn>` and a context type C, |
* infer an instantiation of F, such that `F<S0, S1, ..., Sn>` <: C. |
* |
- * This is similar to [inferGenericFunctionCall], but the return type is also |
+ * This is similar to [inferGenericFunctionOrType], but the return type is also |
* considered as part of the solution. |
* |
* If this function is called with a [contextType] that is also |
@@ -212,7 +225,8 @@ class StrongTypeSystemImpl extends TypeSystem { |
* no effect and return [fnType]. |
*/ |
FunctionType inferFunctionTypeInstantiation( |
- FunctionType contextType, FunctionType fnType) { |
+ FunctionType contextType, FunctionType fnType, |
+ {ErrorReporter errorReporter, AstNode errorNode}) { |
if (contextType.typeFormals.isNotEmpty || fnType.typeFormals.isEmpty) { |
return fnType; |
} |
@@ -221,36 +235,27 @@ class StrongTypeSystemImpl extends TypeSystem { |
// inferred. It will optimistically assume these type parameters can be |
// subtypes (or supertypes) as necessary, and track the constraints that |
// are implied by this. |
- var inferringTypeSystem = |
- new _StrongInferenceTypeSystem(typeProvider, this, fnType.typeFormals); |
+ var inferrer = new _GenericInferrer(typeProvider, this, fnType.typeFormals); |
// Since we're trying to infer the instantiation, we want to ignore type |
// formals as we check the parameters and return type. |
var inferFnType = |
fnType.instantiate(TypeParameterTypeImpl.getTypes(fnType.typeFormals)); |
- if (!inferringTypeSystem.isSubtypeOf(inferFnType, contextType)) { |
- return fnType; |
- } |
- |
- // Try to infer and instantiate the resulting type. |
- var resultType = inferringTypeSystem._infer( |
- fnType, fnType.typeFormals, fnType.returnType); |
+ inferrer.constrainReturnType(inferFnType, contextType); |
- // If the instantiation failed (because some type variable constraints |
- // could not be solved, in other words, we could not find a valid subtype), |
- // then return the original type, so the error is in terms of it. |
- // |
- // It would be safe to return a partial solution here, but the user |
- // experience may be better if we simply do not infer in this case. |
- // |
- // TODO(jmesserly): this heuristic is old. Maybe we should we issue the |
- // inference error? |
- return resultType ?? fnType; |
+ // Infer and instantiate the resulting type. |
+ return inferrer.infer(fnType, fnType.typeFormals, |
+ downwardsInferPhase: true, |
+ errorReporter: errorReporter, errorNode: errorNode); |
} |
- /// Given a function type with generic type parameters, infer the type |
- /// parameters from the actual argument types, and return the instantiated |
- /// function type. If we can't, returns the original function type. |
+ /// Infers a generic type, function, method, or list/map literal |
+ /// instantiation, using the downward context type as well as the argument |
+ /// types if available. |
+ /// |
+ /// For example, given a function type with generic type parameters, this |
+ /// infers the type parameters from the actual argument types, and returns the |
+ /// instantiated function type. |
/// |
/// Concretely, given a function type with parameter types P0, P1, ... Pn, |
/// result type R, and generic type parameters T0, T1, ... Tm, use the |
@@ -261,21 +266,20 @@ class StrongTypeSystemImpl extends TypeSystem { |
/// recording the lower or upper bound it must satisfy. At the end, all |
/// constraints can be combined to determine the type. |
/// |
- /// As a simplification, we do not actually store all constraints on each type |
- /// parameter Tj. Instead we track Uj and Lj where U is the upper bound and |
- /// L is the lower bound of that type parameter. |
- /*=T*/ inferGenericFunctionCall/*<T extends ParameterizedType>*/( |
+ /// All constraints on each type parameter Tj are tracked, as well as where |
+ /// they originated, so we can issue an error message tracing back to the |
+ /// argument values, type parameter "extends" clause, or the return type |
+ /// context. |
+ /*=T*/ inferGenericFunctionOrType/*<T extends ParameterizedType>*/( |
/*=T*/ genericType, |
- List<DartType> declaredParameterTypes, |
+ List<ParameterElement> parameters, |
List<DartType> argumentTypes, |
- DartType declaredReturnType, |
DartType returnContextType, |
{ErrorReporter errorReporter, |
- AstNode errorNode}) { |
+ AstNode errorNode, |
+ bool downwards: false}) { |
// TODO(jmesserly): expose typeFormals on ParameterizedType. |
- List<TypeParameterElement> typeFormals = genericType is FunctionType |
- ? genericType.typeFormals |
- : genericType.typeParameters; |
+ List<TypeParameterElement> typeFormals = typeFormalsAsElements(genericType); |
if (typeFormals.isEmpty) { |
return genericType; |
} |
@@ -284,22 +288,27 @@ class StrongTypeSystemImpl extends TypeSystem { |
// inferred. It will optimistically assume these type parameters can be |
// subtypes (or supertypes) as necessary, and track the constraints that |
// are implied by this. |
- var inferringTypeSystem = |
- new _StrongInferenceTypeSystem(typeProvider, this, typeFormals); |
+ var inferrer = new _GenericInferrer(typeProvider, this, typeFormals); |
+ |
+ DartType declaredReturnType = |
+ genericType is FunctionType ? genericType.returnType : genericType; |
if (returnContextType != null) { |
- inferringTypeSystem.isSubtypeOf(declaredReturnType, returnContextType); |
+ inferrer.constrainReturnType(declaredReturnType, returnContextType); |
} |
for (int i = 0; i < argumentTypes.length; i++) { |
// Try to pass each argument to each parameter, recording any type |
// parameter bounds that were implied by this assignment. |
- inferringTypeSystem.isSubtypeOf( |
- argumentTypes[i], declaredParameterTypes[i]); |
+ inferrer.constrainArgument( |
+ argumentTypes[i], parameters[i].type, parameters[i].name, |
+ genericType: genericType); |
} |
- return inferringTypeSystem._infer( |
- genericType, typeFormals, declaredReturnType, errorReporter, errorNode); |
+ return inferrer.infer(genericType, typeFormals, |
+ errorReporter: errorReporter, |
+ errorNode: errorNode, |
+ downwardsInferPhase: downwards); |
} |
/** |
@@ -312,7 +321,8 @@ class StrongTypeSystemImpl extends TypeSystem { |
* TODO(scheglov) Move this method to elements for classes, typedefs, |
* and generic functions; compute lazily and cache. |
*/ |
- DartType instantiateToBounds(DartType type, {List<bool> hasError}) { |
+ DartType instantiateToBounds(DartType type, |
+ {List<bool> hasError, Map<TypeParameterType, DartType> knownTypes}) { |
List<TypeParameterElement> typeFormals = typeFormalsAsElements(type); |
int count = typeFormals.length; |
if (count == 0) { |
@@ -320,16 +330,20 @@ class StrongTypeSystemImpl extends TypeSystem { |
} |
Set<TypeParameterType> all = new Set<TypeParameterType>(); |
- Map<TypeParameterType, DartType> defaults = {}; // all ground |
- Map<TypeParameterType, DartType> partials = {}; // not ground |
+ // all ground |
+ Map<TypeParameterType, DartType> defaults = knownTypes ?? {}; |
+ // not ground |
+ Map<TypeParameterType, DartType> partials = {}; |
for (TypeParameterElement typeParameterElement in typeFormals) { |
TypeParameterType typeParameter = typeParameterElement.type; |
all.add(typeParameter); |
- if (typeParameter.bound == null) { |
- defaults[typeParameter] = DynamicTypeImpl.instance; |
- } else { |
- partials[typeParameter] = typeParameter.bound; |
+ if (!defaults.containsKey(typeParameter)) { |
+ if (typeParameter.bound == null) { |
+ defaults[typeParameter] = DynamicTypeImpl.instance; |
+ } else { |
+ partials[typeParameter] = typeParameter.bound; |
+ } |
} |
} |
@@ -570,6 +584,87 @@ class StrongTypeSystemImpl extends TypeSystem { |
return t; |
} |
+ // Given a [type] T that may have an unknown type `?`, returns a type |
+ // R such that T <: R for any type substituted for `?`. |
+ // |
+ // In practice this will always replace `?` with either bottom or top |
+ // (dynamic), depending on the position of `?`. |
+ DartType upperBoundForType(DartType type) { |
+ return _substituteForUnknownType(type); |
+ } |
+ |
+ // Given a [type] T that may have an unknown type `?`, returns a type |
+ // R such that R <: T for any type substituted for `?`. |
+ // |
+ // In practice this will always replace `?` with either bottom or top |
+ // (dynamic), depending on the position of `?`. |
+ DartType lowerBoundForType(DartType type) { |
+ return _substituteForUnknownType(type, lowerBound: true); |
+ } |
+ |
+ DartType _substituteForUnknownType(DartType type, |
+ {bool lowerBound: false, dynamicIsBottom: false}) { |
+ if (identical(type, UnknownInferredType.instance)) { |
+ if (lowerBound && !dynamicIsBottom) { |
+ // TODO(jmesserly): this should be the bottom type, once i can be |
+ // reified. |
+ return typeProvider.nullType; |
+ } |
+ return typeProvider.dynamicType; |
+ } |
+ if (type is InterfaceTypeImpl) { |
+ // Generic types are covariant, so keep the constraint direction. |
+ var newTypeArgs = _transformList(type.typeArguments, |
+ (t) => _substituteForUnknownType(t, lowerBound: lowerBound)); |
+ if (identical(type.typeArguments, newTypeArgs)) return type; |
+ return new InterfaceTypeImpl(type.element, type.prunedTypedefs) |
+ ..typeArguments = newTypeArgs; |
+ } |
+ if (type is FunctionType) { |
+ var parameters = type.parameters; |
+ var returnType = type.returnType; |
+ var newParameters = _transformList(parameters, (ParameterElement p) { |
+ // Parameters are contravariant, so flip the constraint direction. |
+ // Also pass dynamicIsBottom, because this is a fuzzy arrow. |
+ var newType = _substituteForUnknownType(p.type, |
+ lowerBound: !lowerBound, dynamicIsBottom: true); |
+ return identical(p.type, newType) && p is ParameterElementImpl |
+ ? p |
+ : new ParameterElementImpl.synthetic( |
+ p.name, newType, p.parameterKind); |
+ }); |
+ // Return type is covariant. |
+ var newReturnType = |
+ _substituteForUnknownType(returnType, lowerBound: lowerBound); |
+ if (identical(parameters, newParameters) && |
+ identical(returnType, newReturnType)) { |
+ return type; |
+ } |
+ |
+ var function = new FunctionElementImpl(type.name, -1) |
+ ..isSynthetic = true |
+ ..returnType = newReturnType |
+ ..shareTypeParameters(type.typeFormals) |
+ ..shareParameters(newParameters); |
+ return function.type = new FunctionTypeImpl(function); |
+ } |
+ return type; |
+ } |
+ |
+ static List/*<T>*/ _transformList/*<T>*/( |
+ List/*<T>*/ list, /*=T*/ f(/*=T*/ t)) { |
+ List/*<T>*/ newList = null; |
+ for (var i = 0; i < list.length; i++) { |
+ var item = list[i]; |
+ var newItem = f(item); |
+ if (!identical(item, newItem)) { |
+ newList ??= new List.from(list); |
+ newList[i] = newItem; |
+ } |
+ } |
+ return newList ?? list; |
+ } |
+ |
/** |
* Compute the greatest lower bound of function types [f] and [g]. |
* |
@@ -829,15 +924,15 @@ class StrongTypeSystemImpl extends TypeSystem { |
return true; |
} |
- // Guard recursive calls |
- _GuardedSubtypeChecker<DartType> guardedSubtype = _guard(_isSubtypeOf); |
- |
// The types are void, dynamic, bottom, interface types, function types, |
// FutureOr<T> and type parameters. |
// |
// We proceed by eliminating these different classes from consideration. |
// Trivially true. |
+ // |
+ // Note that `?` is treated as a top and a bottom type during inference, |
+ // so it's also covered here. |
if (_isTop(t2, dynamicIsBottom: dynamicIsBottom) || |
_isBottom(t1, dynamicIsBottom: dynamicIsBottom)) { |
return true; |
@@ -879,11 +974,13 @@ class StrongTypeSystemImpl extends TypeSystem { |
if (t1 is TypeParameterType) { |
if (t2 is TypeParameterType && |
t1.definition == t2.definition && |
- guardedSubtype(t1.bound, t2.bound, visited)) { |
+ _typeParameterBoundsSubtype(t1.bound, t2.bound, true)) { |
return true; |
} |
DartType bound = t1.element.bound; |
- return bound == null ? false : guardedSubtype(bound, t2, visited); |
+ return bound == null |
+ ? false |
+ : _typeParameterBoundsSubtype(bound, t2, false); |
} |
if (t2 is TypeParameterType) { |
return false; |
@@ -979,6 +1076,21 @@ class StrongTypeSystemImpl extends TypeSystem { |
.substitute2([typeProvider.objectType], [type2]); |
return getLeastUpperBound(type1, type2); |
} |
+ |
+ bool _typeParameterBoundsSubtype( |
+ DartType t1, DartType t2, bool recursionValue) { |
+ if (_comparingTypeParameterBounds) { |
+ return recursionValue; |
+ } |
+ _comparingTypeParameterBounds = true; |
+ try { |
+ return isSubtypeOf(t1, t2); |
+ } finally { |
+ _comparingTypeParameterBounds = false; |
+ } |
+ } |
+ |
+ static bool _comparingTypeParameterBounds = false; |
} |
/** |
@@ -1026,6 +1138,15 @@ abstract class TypeSystem { |
if (identical(type1, type2)) { |
return type1; |
} |
+ |
+ // For any type T, LUB(?, T) == T. |
+ if (identical(type1, UnknownInferredType.instance)) { |
+ return type2; |
+ } |
+ if (identical(type2, UnknownInferredType.instance)) { |
+ return type1; |
+ } |
+ |
// The least upper bound of top and any type T is top. |
// The least upper bound of bottom and any type T is T. |
if (_isTop(type1, dynamicIsBottom: dynamicIsBottom) || |
@@ -1444,7 +1565,7 @@ class TypeSystemImpl extends TypeSystem { |
/// (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 |
+/// 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. |
/// |
@@ -1454,164 +1575,594 @@ class TypeSystemImpl extends TypeSystem { |
/// |
/// 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 { |
- /// The outer strong mode type system, used for GLB and LUB, so we don't |
- /// recurse into our constraint solving code. |
+class _GenericInferrer { |
final StrongTypeSystemImpl _typeSystem; |
- final Map<TypeParameterType, _TypeParameterBound> _bounds; |
+ final TypeProvider typeProvider; |
+ final Map<TypeParameterElement, List<_TypeConstraint>> _constraints; |
- _StrongInferenceTypeSystem(TypeProvider typeProvider, this._typeSystem, |
+ /// Counter internally by [_matchSubtypeOf] to see if a recursive call |
+ /// added anything to [_constraints]. |
+ /// |
+ /// Essentially just an optimization for: |
+ /// `_constraints.values.expand((x) => x).length` |
+ int _constraintCount = 0; |
+ |
+ _GenericInferrer(this.typeProvider, this._typeSystem, |
Iterable<TypeParameterElement> typeFormals) |
- : _bounds = new Map.fromIterable(typeFormals, |
- key: (t) => t.type, value: (t) => new _TypeParameterBound()), |
- super(typeProvider); |
+ : _constraints = new HashMap( |
+ equals: (x, y) => x.location == y.location, |
+ hashCode: (x) => x.location.hashCode) { |
+ for (var formal in typeFormals) { |
+ _constraints[formal] = []; |
+ } |
+ } |
+ |
+ /// Apply a return type constraint, which asserts that the [declaredType] |
+ /// is a subtype of the [contextType]. |
+ void constrainReturnType(DartType declaredType, DartType contextType) { |
+ var origin = new _TypeConstraintFromReturnType(declaredType, contextType); |
+ _matchSubtypeOf(declaredType, contextType, null, origin, covariant: true); |
+ } |
+ |
+ /// Apply an argument constraint, which asserts that the [argument] staticType |
+ /// is a subtype of the [parameterType]. |
+ void constrainArgument( |
+ DartType argumentType, DartType parameterType, String parameterName, |
+ {DartType genericType}) { |
+ var origin = new _TypeConstraintFromArgument( |
+ argumentType, parameterType, parameterName, |
+ genericType: genericType); |
+ _matchSubtypeOf(argumentType, parameterType, null, origin, |
+ covariant: false); |
+ } |
+ |
+ /// Assert that [t1] will be a subtype of [t2], and returns if the constraint |
+ /// can be satisfied. |
+ /// |
+ /// [covariant] must be true if [t1] is a declared type of the generic |
+ /// function and [t2] is the context type, or false if the reverse. For |
+ /// example [covariant] is used when [t1] is the declared return type |
+ /// and [t2] is the context type. Contravariant would be used if [t1] is the |
+ /// argument type (i.e. passed in to the generic function) and [t2] is the |
+ /// declared parameter type. |
+ /// |
+ /// [origin] indicates where the constraint came from, for example an argument |
+ /// or return type. |
+ void _matchSubtypeOf(DartType t1, DartType t2, Set<Element> visited, |
+ _TypeConstraintOrigin origin, |
+ {bool covariant, bool dynamicIsBottom: false}) { |
+ // TODO(jmesserly): I think we should handle `dynamicIsBottom` |
+ // https://github.com/dart-lang/sdk/issues/29041 |
+ if (covariant && t1 is TypeParameterType) { |
+ var constraints = _constraints[t1.element]; |
+ if (constraints != null) { |
+ if (!identical(t2, UnknownInferredType.instance)) { |
+ constraints.add(new _TypeConstraint(origin, t1, upper: t2)); |
+ _constraintCount++; |
+ } |
+ return; |
+ } |
+ } |
+ if (!covariant && t2 is TypeParameterType) { |
+ var constraints = _constraints[t2.element]; |
+ if (constraints != null) { |
+ if (!identical(t1, UnknownInferredType.instance)) { |
+ constraints.add(new _TypeConstraint(origin, t2, lower: t1)); |
+ _constraintCount++; |
+ } |
+ return; |
+ } |
+ } |
+ |
+ if (identical(t1, t2)) { |
+ return; |
+ } |
+ |
+ // TODO(jmesserly): this logic is taken from subtype. |
+ void matchSubtype(DartType t1, DartType t2) { |
+ _matchSubtypeOf(t1, t2, null, origin, covariant: covariant); |
+ } |
+ |
+ // Handle FutureOr<T> union type. |
+ if (t1 is InterfaceType && t1.isDartAsyncFutureOr) { |
+ var t1TypeArg = t1.typeArguments[0]; |
+ if (t2 is InterfaceType && t2.isDartAsyncFutureOr) { |
+ var t2TypeArg = t2.typeArguments[0]; |
+ // FutureOr<A> <: FutureOr<B> iff A <: B |
+ matchSubtype(t1TypeArg, t2TypeArg); |
+ return; |
+ } |
+ |
+ // given t1 is Future<A> | A, then: |
+ // (Future<A> | A) <: t2 iff Future<A> <: t2 and A <: t2. |
+ var t1Future = typeProvider.futureType.instantiate([t1TypeArg]); |
+ matchSubtype(t1Future, t2); |
+ matchSubtype(t1TypeArg, t2); |
+ return; |
+ } |
+ |
+ if (t2 is InterfaceType && t2.isDartAsyncFutureOr) { |
+ // given t2 is Future<A> | A, then: |
+ // t1 <: (Future<A> | A) iff t1 <: Future<A> or t1 <: A |
+ var t2TypeArg = t2.typeArguments[0]; |
+ var t2Future = typeProvider.futureType.instantiate([t2TypeArg]); |
+ |
+ int constraintCount = _constraintCount; |
+ matchSubtype(t1, t2Future); |
+ |
+ // We only want to record these as "or" constraints, so if we matched |
+ // the `t1 <: Future<A>` constraint, don't add `t1 <: A` constraint, as |
+ // that would be interpreted incorrectly as `t1 <: Future<A> && t1 <: A`. |
+ if (constraintCount == _constraintCount) { |
+ matchSubtype(t1, t2TypeArg); |
+ } |
+ return; |
+ } |
+ |
+ // S <: T where S is a type variable |
+ // T is not dynamic or object (handled above) |
+ // True if T == S |
+ // Or true if bound of S is S' and S' <: T |
+ |
+ if (t1 is TypeParameterType) { |
+ // Guard against recursive type parameters |
+ void guardedSubtype(DartType t1, DartType t2) { |
+ var visitedSet = visited ?? new HashSet<Element>(); |
+ if (visitedSet.add(t1.element)) { |
+ matchSubtype(t1, t2); |
+ visitedSet.remove(t1.element); |
+ } |
+ } |
+ |
+ if (t2 is TypeParameterType && t1.definition == t2.definition) { |
+ guardedSubtype(t1.bound, t2.bound); |
+ return; |
+ } |
+ guardedSubtype(t1.bound, t2); |
+ return; |
+ } |
+ if (t2 is TypeParameterType) { |
+ return; |
+ } |
+ |
+ if (t1 is InterfaceType && t2 is InterfaceType) { |
+ _matchInterfaceSubtypeOf(t1, t2, visited, origin, covariant: covariant); |
+ return; |
+ } |
+ |
+ // An interface type can only subtype a function type if |
+ // the interface type declares a call method with a type |
+ // which is a super type of the function type. |
+ if (t1 is InterfaceType) { |
+ t1 = _typeSystem.getCallMethodDefiniteType(t1); |
+ if (t1 == null) return; |
+ } |
+ |
+ if (t1 is FunctionType && t2 is FunctionType) { |
+ FunctionTypeImpl.relate( |
+ t1, |
+ t2, |
+ (t1, t2, _, __) { |
+ _matchSubtypeOf(t2, t1, null, origin, |
+ covariant: !covariant, dynamicIsBottom: true); |
+ return true; |
+ }, |
+ _typeSystem.instantiateToBounds, |
+ returnRelation: (t1, t2) { |
+ matchSubtype(t1, t2); |
+ return true; |
+ }); |
+ } |
+ } |
+ |
+ void _matchInterfaceSubtypeOf(InterfaceType i1, InterfaceType i2, |
+ Set<Element> visited, _TypeConstraintOrigin origin, |
+ {bool covariant}) { |
+ if (identical(i1, i2)) { |
+ return; |
+ } |
+ |
+ if (i1.element == i2.element) { |
+ List<DartType> tArgs1 = i1.typeArguments; |
+ List<DartType> tArgs2 = i2.typeArguments; |
+ assert(tArgs1.length == tArgs2.length); |
+ for (int i = 0; i < tArgs1.length; i++) { |
+ _matchSubtypeOf(tArgs1[i], tArgs2[i], visited, origin, |
+ covariant: covariant); |
+ } |
+ return; |
+ } |
+ if (i2.isDartCoreFunction && i1.element.getMethod("call") != null) { |
+ return; |
+ } |
+ if (i1.isObject) { |
+ return; |
+ } |
+ |
+ // Guard against loops in the class hierarchy |
+ void guardedInterfaceSubtype(InterfaceType t1) { |
+ var visitedSet = visited ?? new HashSet<Element>(); |
+ if (visitedSet.add(t1.element)) { |
+ _matchInterfaceSubtypeOf(t1, i2, visited, origin, covariant: covariant); |
+ visitedSet.remove(t1.element); |
+ } |
+ } |
+ |
+ guardedInterfaceSubtype(i1.superclass); |
+ for (final parent in i1.interfaces) { |
+ guardedInterfaceSubtype(parent); |
+ } |
+ for (final parent in i1.mixins) { |
+ guardedInterfaceSubtype(parent); |
+ } |
+ } |
/// Given the constraints that were given by calling [isSubtypeOf], find the |
/// instantiation of the generic function that satisfies these constraints. |
- /*=T*/ _infer/*<T extends ParameterizedType>*/(/*=T*/ genericType, |
- List<TypeParameterElement> typeFormals, DartType declaredReturnType, |
- [ErrorReporter errorReporter, AstNode errorNode]) { |
- List<TypeParameterType> fnTypeParams = |
- TypeParameterTypeImpl.getTypes(typeFormals); |
+ /// |
+ /// If [downwardsInferPhase] is set, we are in the first pass of inference, |
+ /// pushing context types down. At that point we are allowed to push down |
+ /// `?` to precisely represent an unknown type. If [downwardsInferPhase] is |
+ /// false, we are on our final inference pass, have all available information |
+ /// including argument types, and must not conclude `?` for any type formal. |
+ /*=T*/ infer/*<T extends ParameterizedType>*/( |
+ /*=T*/ genericType, |
+ List<TypeParameterElement> typeFormals, |
+ {ErrorReporter errorReporter, |
+ AstNode errorNode, |
+ bool downwardsInferPhase: false}) { |
+ var fnTypeParams = TypeParameterTypeImpl.getTypes(typeFormals); |
// Initialize the inferred type array. |
// |
- // They all start as `dynamic` to offer reasonable degradation for f-bounded |
- // type parameters. |
+ // In the downwards phase, they all start as `?` to offer reasonable |
+ // degradation for f-bounded type parameters. |
var inferredTypes = new List<DartType>.filled( |
- fnTypeParams.length, DynamicTypeImpl.instance, |
- growable: false); |
+ fnTypeParams.length, UnknownInferredType.instance); |
+ var _inferTypeParameter = downwardsInferPhase |
+ ? _inferTypeParameterFromContext |
+ : _inferTypeParameterFromAll; |
for (int i = 0; i < fnTypeParams.length; i++) { |
TypeParameterType typeParam = fnTypeParams[i]; |
- _TypeParameterBound bound = _bounds[typeParam]; |
- // Apply the `extends` clause for the type parameter, if any. |
- // |
- // Assumption: if the current type parameter has an "extends" clause |
- // that refers to another type variable we are inferring, it will appear |
- // before us or in this list position. For example: |
+ var typeParamBound = typeParam.bound; |
+ _TypeConstraint extendsClause; |
+ if (!typeParamBound.isDynamic) { |
+ extendsClause = new _TypeConstraint.fromExtends(typeParam, |
+ typeParam.bound.substitute2(inferredTypes, fnTypeParams)); |
+ } |
+ |
+ var constraints = _constraints[typeParam.element]; |
+ inferredTypes[i] = _inferTypeParameter(constraints, extendsClause); |
+ } |
+ |
+ // If the downwards infer phase has failed, we'll catch this in the upwards |
+ // phase later on. |
+ if (downwardsInferPhase) { |
+ return genericType.instantiate(inferredTypes) as dynamic/*=T*/; |
+ } |
+ |
+ // Check the inferred types against all of the constraints. |
+ var knownTypes = new HashMap<TypeParameterType, DartType>( |
+ equals: (x, y) => x.element == y.element, |
+ hashCode: (x) => x.element.hashCode); |
+ for (int i = 0; i < fnTypeParams.length; i++) { |
+ TypeParameterType typeParam = fnTypeParams[i]; |
+ var constraints = _constraints[typeParam.element]; |
+ var typeParamBound = |
+ typeParam.bound.substitute2(inferredTypes, fnTypeParams); |
+ if (!typeParamBound.isDynamic) { |
+ constraints |
+ .add(new _TypeConstraint.fromExtends(typeParam, typeParamBound)); |
+ } |
+ var inferred = inferredTypes[i]; |
+ if (constraints.any((c) => !c.isSatisifedBy(_typeSystem, inferred))) { |
+ // Heuristic: keep the erroneous type, it should satisfy at least some |
+ // of the constraints (e.g. the return context). If we fall back to |
+ // instantiateToBounds, we'll typically get more errors (e.g. because |
+ // `dynamic` is the most common bound). |
+ knownTypes[typeParam] = inferred; |
+ errorReporter?.reportErrorForNode(StrongModeCode.COULD_NOT_INFER, |
+ errorNode, [typeParam, _formatError(inferred, constraints)]); |
+ } else if (UnknownInferredType.isKnown(inferred)) { |
+ knownTypes[typeParam] = inferred; |
+ } |
+ } |
+ |
+ // Use instantiate to bounds to finish things off. |
+ var hasError = new List<bool>.filled(fnTypeParams.length, false); |
+ var result = _typeSystem.instantiateToBounds(genericType, |
+ hasError: hasError, knownTypes: knownTypes) as dynamic/*=T*/; |
+ |
+ // Report any errors from instantiateToBounds. |
+ for (int i = 0; i < hasError.length; i++) { |
+ if (hasError[i]) { |
+ TypeParameterType typeParam = fnTypeParams[i]; |
+ var typeParamBound = |
+ typeParam.bound.substitute2(inferredTypes, fnTypeParams); |
+ // TODO(jmesserly): improve this error message. |
+ errorReporter |
+ ?.reportErrorForNode(StrongModeCode.COULD_NOT_INFER, errorNode, [ |
+ typeParam, |
+ "\nRecursive bound cannot be instantiated: '$typeParamBound'." |
+ "\nConsider passing explicit type argument(s) " |
+ "to the generic.\n\n'" |
+ ]); |
+ } |
+ } |
+ return result; |
+ } |
+ |
+ DartType _inferTypeParameterFromContext( |
+ Iterable<_TypeConstraint> constraints, _TypeConstraint extendsClause) { |
+ DartType t = _chooseTypeFromConstraints(constraints); |
+ if (UnknownInferredType.isUnknown(t)) { |
+ return t; |
+ } |
+ |
+ // If we're about to make our final choice, apply the extends clause. |
+ // This gives us a chance to refine the choice, in case it would violate |
+ // the `extends` clause. For example: |
+ // |
+ // Object obj = math.min/*<infer Object, error>*/(1, 2); |
+ // |
+ // If we consider the `T extends num` we conclude `<num>`, which works. |
+ if (extendsClause != null) { |
+ constraints = constraints.toList()..add(extendsClause); |
+ return _chooseTypeFromConstraints(constraints); |
+ } |
+ return t; |
+ } |
+ |
+ DartType _inferTypeParameterFromAll( |
+ Iterable<_TypeConstraint> constraints, _TypeConstraint extendsClause) { |
+ // See if we already fixed this type from downwards inference. |
+ // If so, then we aren't allowed to change it based on argument types. |
+ DartType t = _inferTypeParameterFromContext( |
+ constraints.where((c) => c.isDownwards), extendsClause); |
+ if (UnknownInferredType.isKnown(t)) { |
+ return t; |
+ } |
+ |
+ if (extendsClause != null) { |
+ constraints = constraints.toList()..add(extendsClause); |
+ } |
+ |
+ var choice = _chooseTypeFromConstraints(constraints, toKnownType: true); |
+ return choice; |
+ } |
+ |
+ /// Choose the bound that was implied by the return type, if any. |
+ /// |
+ /// Which bound this is depends on what positions the type parameter |
+ /// appears in. If the type only appears only in a contravariant position, |
+ /// we will choose the lower bound instead. |
+ /// |
+ /// For example given: |
+ /// |
+ /// Func1<T, bool> makeComparer<T>(T x) => (T y) => x() == y; |
+ /// |
+ /// main() { |
+ /// Func1<num, bool> t = makeComparer/* infer <num> */(42); |
+ /// print(t(42.0)); /// false, no error. |
+ /// } |
+ /// |
+ /// The constraints we collect are: |
+ /// |
+ /// * `num <: T` |
+ /// * `int <: T` |
+ /// |
+ /// ... and no upper bound. Therefore the lower bound is the best choice. |
+ DartType _chooseTypeFromConstraints(Iterable<_TypeConstraint> constraints, |
+ {bool toKnownType: false}) { |
+ DartType lower = UnknownInferredType.instance; |
+ DartType upper = UnknownInferredType.instance; |
+ for (var constraint in constraints) { |
+ // Given constraints: |
// |
- // <TFrom, TTo extends TFrom> |
+ // L1 <: T <: U1 |
+ // L2 <: T <: U2 |
// |
- // We may infer TTo is TFrom. In that case, we already know what TFrom |
- // is inferred as, so we can substitute it now. This also handles more |
- // complex cases such as: |
+ // These can be combined to produce: |
// |
- // <TFrom, TTo extends Iterable<TFrom>> |
+ // LUB(L1, L2) <: T <: GLB(U1, U2). |
// |
- // Or if the type parameter's bound depends on itself such as: |
+ // This can then be done for all constraints in sequence. |
// |
- // <T extends Clonable<T>> |
- DartType declaredUpperBound = typeParam.element.bound; |
- if (declaredUpperBound != null) { |
- // Assert that the type parameter is a subtype of its bound. |
- // TODO(jmesserly): the order of calling GLB here matters, because of |
- // https://github.com/dart-lang/sdk/issues/28513 |
- bound.upper = _typeSystem.getGreatestLowerBound(bound.upper, |
- declaredUpperBound.substitute2(inferredTypes, fnTypeParams)); |
- } |
+ // This resulting constraint may be unsatisfiable; in that case inference |
+ // will fail. |
+ upper = _getGreatestLowerBound(upper, constraint.upperBound); |
+ lower = _typeSystem.getLeastUpperBound(lower, constraint.lowerBound); |
+ } |
- // Now we've computed lower and upper bounds for each type parameter. |
- // |
- // To decide on which type to assign, we look at the return type and see |
- // if the type parameter occurs in covariant or contravariant positions. |
- // |
- // If the type is "passed in" at all, or if our lower bound was bottom, |
- // we choose the upper bound as being the most useful. |
- // |
- // Otherwise we choose the more precise lower bound. |
- _TypeParameterVariance variance = |
- new _TypeParameterVariance.from(typeParam, declaredReturnType); |
- |
- DartType lowerBound = bound.lower; |
- DartType upperBound = bound.upper; |
- |
- // See if the bounds can be satisfied. |
- // TODO(jmesserly): also we should have an error for unconstrained type |
- // parameters, rather than silently inferring dynamic. |
- if (upperBound.isBottom || |
- !_typeSystem.isSubtypeOf(lowerBound, upperBound)) { |
- // Inference failed. |
- if (errorReporter == null) { |
- return null; |
+ // Prefer the known bound, if any. |
+ // Otherwise take whatever bound has partial information, e.g. `Iterable<?>` |
+ // |
+ // For both of those, prefer the lower bound (arbitrary heuristic). |
+ if (UnknownInferredType.isKnown(lower)) { |
+ return lower; |
+ } |
+ if (UnknownInferredType.isKnown(upper)) { |
+ return upper; |
+ } |
+ if (!identical(UnknownInferredType.instance, lower)) { |
+ return toKnownType ? _typeSystem.lowerBoundForType(lower) : lower; |
+ } |
+ if (!identical(UnknownInferredType.instance, upper)) { |
+ return toKnownType ? _typeSystem.upperBoundForType(upper) : upper; |
+ } |
+ return lower; |
+ } |
+ |
+ /// This is first calls strong mode's GLB, but if it fails to find anything |
+ /// (i.e. returns the bottom type), we kick in a few additional rules: |
+ /// |
+ /// - `GLB(FutureOr<A>, B)` is defined as: |
+ /// - `GLB(FutureOr<A>, FutureOr<B>) == FutureOr<GLB(A, B)>` |
+ /// - `GLB(FutureOr<A>, Future<B>) == Future<GLB(A, B)>` |
+ /// - else `GLB(FutureOr<A>, B) == GLB(A, B)` |
+ /// - `GLB(A, FutureOr<B>) == GLB(FutureOr<A>, B)` (defined above), |
+ /// - else `GLB(A, B) == Null` |
+ DartType _getGreatestLowerBound(DartType t1, DartType t2) { |
+ var result = _typeSystem.getGreatestLowerBound(t1, t2); |
+ if (result.isBottom) { |
+ // See if we can do better by considering FutureOr rules. |
+ if (t1 is InterfaceType && t1.isDartAsyncFutureOr) { |
+ var t1TypeArg = t1.typeArguments[0]; |
+ if (t2 is InterfaceType) { |
+ // GLB(FutureOr<A>, FutureOr<B>) == FutureOr<GLB(A, B)> |
+ if (t2.isDartAsyncFutureOr) { |
+ var t2TypeArg = t2.typeArguments[0]; |
+ return typeProvider.futureOrType |
+ .instantiate([_getGreatestLowerBound(t1TypeArg, t2TypeArg)]); |
+ } |
+ // GLB(FutureOr<A>, Future<B>) == Future<GLB(A, B)> |
+ if (t2.isDartAsyncFuture) { |
+ var t2TypeArg = t2.typeArguments[0]; |
+ return typeProvider.futureType |
+ .instantiate([_getGreatestLowerBound(t1TypeArg, t2TypeArg)]); |
+ } |
} |
- errorReporter.reportErrorForNode(StrongModeCode.COULD_NOT_INFER, |
- errorNode, [typeParam, lowerBound, upperBound]); |
- |
- // To make the errors more useful, we swap the normal heuristic. |
- // |
- // The normal heuristic prefers using the argument types (upwards |
- // inference, lower bound) to choose a tighter type. |
- // |
- // Here we want to prefer the return context type, so we can put the |
- // blame on the arguments to the function. That will result in narrow |
- // error spans. But ultimately it's just a heuristic, as the code is |
- // already erroneous. |
- // |
- // (we may adjust the normal heuristic too, once upwards+downwards |
- // inference are fully integrated, to prefer downwards info). |
- lowerBound = bound.upper; |
- upperBound = bound.lower; |
+ // GLB(FutureOr<A>, B) == GLB(A, B) |
+ return _getGreatestLowerBound(t1TypeArg, t2); |
} |
- inferredTypes[i] = |
- variance.passedIn && !upperBound.isDynamic || lowerBound.isBottom |
- ? upperBound |
- : lowerBound; |
+ if (t2 is InterfaceType && t2.isDartAsyncFutureOr) { |
+ // GLB(A, FutureOr<B>) == GLB(FutureOr<A>, B) |
+ return _getGreatestLowerBound(t2, t1); |
+ } |
+ // TODO(jmesserly): fix this rule once we support non-nullable types. |
+ return typeProvider.nullType; |
} |
- |
- // Return the instantiated type. |
- return genericType.instantiate(inferredTypes) as dynamic/*=T*/; |
+ return result; |
} |
- @override |
- bool _isSubtypeOf(DartType t1, DartType t2, Set<Element> visited, |
- {bool dynamicIsBottom: false}) { |
- // TODO(jmesserly): the trivial constraints are not treated as part of |
- // the constraint set here. This seems incorrect once we are able to pin the |
- // inferred type of a type parameter based on the downwards information. |
- if (identical(t1, t2) || |
- _isTop(t2, dynamicIsBottom: dynamicIsBottom) || |
- _isBottom(t1, dynamicIsBottom: dynamicIsBottom)) { |
- return true; |
+ String _formatError( |
+ DartType inferred, Iterable<_TypeConstraint> constraints) { |
+ var intro = "Inferred type '$inferred' does not work with constraints:"; |
+ |
+ var constraintsByOrigin = <_TypeConstraintOrigin, List<_TypeConstraint>>{}; |
+ for (var c in constraints) { |
+ constraintsByOrigin.putIfAbsent(c.origin, () => []).add(c); |
} |
- if (t1 is TypeParameterType) { |
- // TODO(jmesserly): we ignore `dynamicIsBottom` here, is that correct? |
- _TypeParameterBound bound = _bounds[t1]; |
- if (bound != null) { |
- // Ensure T1 <: T2, where T1 is a type parameter we are inferring. |
- // T2 is an upper bound, so merge it with our existing upper bound. |
- // |
- // We already know T1 <: U, for some U. |
- // So update U to reflect the new constraint T1 <: GLB(U, T2) |
- // |
- bound.upper = _typeSystem.getGreatestLowerBound(bound.upper, t2); |
- // Optimistically assume we will be able to satisfy the constraint. |
- return true; |
- } |
+ Iterable<_TypeConstraint> isSatisified(bool expected) => constraintsByOrigin |
+ .values |
+ .where((l) => |
+ l.every((c) => c.isSatisifedBy(_typeSystem, inferred)) == expected) |
+ .expand((i) => i); |
+ |
+ // Only report unique constraint origins. |
+ String unsatisified = _formatConstraints(isSatisified(false)); |
+ String satisified = _formatConstraints(isSatisified(true)); |
+ |
+ assert(unsatisified.isNotEmpty); |
+ if (satisified.isNotEmpty) { |
+ satisified = "\nThe type '$inferred' was inferred from:\n$satisified"; |
} |
- if (t2 is TypeParameterType) { |
- _TypeParameterBound bound = _bounds[t2]; |
- if (bound != null) { |
- // Ensure T1 <: T2, where T2 is a type parameter we are inferring. |
- // T1 is a lower bound, so merge it with our existing lower bound. |
- // |
- // We already know L <: T2, for some L. |
- // So update L to reflect the new constraint LUB(L, T1) <: T2 |
- // |
- bound.lower = _typeSystem.getLeastUpperBound(bound.lower, t1); |
- // Optimistically assume we will be able to satisfy the constraint. |
- return true; |
- } |
+ |
+ return '\n\n$intro\n$unsatisified$satisified\n\n' |
+ 'Consider passing explicit type argument(s) to the generic.\n\n'; |
+ } |
+ |
+ static String _formatConstraints(Iterable<_TypeConstraint> constraints) { |
+ List<List<String>> lineParts = |
+ new Set<_TypeConstraintOrigin>.from(constraints.map((c) => c.origin)) |
+ .map((o) => o.formatError()) |
+ .toList(); |
+ |
+ int prefixMax = lineParts.map((p) => p[0].length).fold(0, math.max); |
+ int middleMax = lineParts.map((p) => p[1].length).fold(0, math.max); |
+ |
+ // Use a set to prevent identical message lines. |
+ // (It's not uncommon for the same constraint to show up in a few places.) |
+ var messageLines = new Set<String>.from(lineParts.map((parts) { |
+ var prefix = parts[0]; |
+ var middle = parts[1]; |
+ var prefixPad = ' ' * (prefixMax - prefix.length); |
+ var middlePad = ' ' * (middleMax - middle.length); |
+ return ' $prefix$prefixPad $middle$middlePad ${parts[2]}'.trimRight(); |
+ })); |
+ |
+ return messageLines.join('\n'); |
+ } |
+} |
+ |
+/// The origin of a type constraint, for the purposes of producing a human |
+/// readable error message during type inference as well as determining whether |
+/// the constraint was used to fix the type parameter or not. |
+abstract class _TypeConstraintOrigin { |
+ List<String> formatError(); |
+} |
+ |
+class _TypeConstraintFromArgument extends _TypeConstraintOrigin { |
+ final DartType argumentType; |
+ final DartType parameterType; |
+ final String parameterName; |
+ final DartType genericType; |
+ |
+ _TypeConstraintFromArgument( |
+ this.argumentType, this.parameterType, this.parameterName, |
+ {this.genericType}); |
+ |
+ @override |
+ formatError() { |
+ // TODO(jmesserly): we should highlight the span. That would be more useful. |
+ // However in summary code it doesn't look like the AST node with span is |
+ // available. |
+ String prefix; |
+ if ((genericType.name == "List" || genericType.name == "Map") && |
+ genericType?.element?.library?.isDartCore == true) { |
+ // This will become: |
+ // "List element" |
+ // "Map key" |
+ // "Map value" |
+ prefix = "${genericType.name} $parameterName"; |
+ } else { |
+ prefix = "Argument '$parameterName'"; |
} |
- return super |
- ._isSubtypeOf(t1, t2, visited, dynamicIsBottom: dynamicIsBottom); |
+ |
+ return [ |
+ prefix, |
+ "inferred as '$argumentType'", |
+ "must be a '$parameterType'." |
+ ]; |
} |
} |
-/// An [upper] and [lower] bound for a type variable. |
-class _TypeParameterBound { |
+class _TypeConstraintFromReturnType extends _TypeConstraintOrigin { |
+ final DartType contextType; |
+ final DartType declaredType; |
+ |
+ _TypeConstraintFromReturnType(this.declaredType, this.contextType); |
+ |
+ @override |
+ formatError() { |
+ return [ |
+ "Return type", |
+ "declared as '$declaredType'", |
+ "used where a '$contextType' is required." |
+ ]; |
+ } |
+} |
+ |
+class _TypeConstraintFromExtendsClause extends _TypeConstraintOrigin { |
+ final TypeParameterType typeParam; |
+ final DartType extendsType; |
+ |
+ _TypeConstraintFromExtendsClause(this.typeParam, this.extendsType); |
+ |
+ @override |
+ formatError() { |
+ return [ |
+ "Type parameter '$typeParam'", |
+ "declared to extend '$extendsType'.", |
+ "" |
+ ]; |
+ } |
+} |
+ |
+class _TypeRange { |
/// The upper bound of the type parameter. In other words, T <: upperBound. |
/// |
/// In Dart this can be written as `<T extends UpperBoundType>`. |
@@ -1633,7 +2184,7 @@ class _TypeParameterBound { |
/// |
/// Here the [lower] will be `String` and the upper bound will be `num`, |
/// which cannot be satisfied, so this is ill typed. |
- DartType upper = DynamicTypeImpl.instance; |
+ final DartType upperBound; |
/// The lower bound of the type parameter. In other words, lowerBound <: T. |
/// |
@@ -1652,71 +2203,137 @@ class _TypeParameterBound { |
/// |
/// In general, we choose the lower bound as our inferred type, so we can |
/// offer the most constrained (strongest) result type. |
- DartType lower = BottomTypeImpl.instance; |
+ final DartType lowerBound; |
+ |
+ _TypeRange({DartType lower, DartType upper}) |
+ : lowerBound = lower ?? UnknownInferredType.instance, |
+ upperBound = upper ?? UnknownInferredType.instance; |
} |
-/// Records what positions a type parameter is used in. |
-class _TypeParameterVariance { |
- /// The type parameter is a value passed out. It must satisfy T <: S, |
- /// where T is the type parameter and S is what it's assigned to. |
- /// |
- /// For example, this could be the return type, or a parameter to a parameter: |
- /// |
- /// TOut method<TOut>(void f(TOut t)); |
- bool passedOut = false; |
+/// A constraint on a type parameter that we're inferring. |
+class _TypeConstraint extends _TypeRange { |
+ /// The type parameter that is constrained by [lowerBound] or [upperBound]. |
+ final TypeParameterType typeParameter; |
- /// The type parameter is a value passed in. It must satisfy S <: T, |
- /// where T is the type parameter and S is what's being assigned to it. |
- /// |
- /// For example, this could be a parameter type, or the parameter of the |
- /// return value: |
+ /// Where this constraint comes from, used for error messages. |
/// |
- /// typedef void Func<T>(T t); |
- /// Func<TIn> method<TIn>(TIn t); |
- bool passedIn = false; |
+ /// See [toString]. |
+ final _TypeConstraintOrigin origin; |
+ |
+ _TypeConstraint(this.origin, this.typeParameter, |
+ {DartType upper, DartType lower}) |
+ : super(upper: upper, lower: lower); |
+ |
+ _TypeConstraint.fromExtends(TypeParameterType type, DartType extendsType) |
+ : this(new _TypeConstraintFromExtendsClause(type, extendsType), type, |
+ upper: extendsType); |
+ |
+ bool get isDownwards => origin is! _TypeConstraintFromArgument; |
+ |
+ bool isSatisifedBy(TypeSystem ts, DartType type) => |
+ ts.isSubtypeOf(lowerBound, type) && ts.isSubtypeOf(type, upperBound); |
- _TypeParameterVariance.from(TypeParameterType typeParam, DartType type) { |
- _visitType(typeParam, type, false); |
+ /// Converts this constraint to a message suitable for a type inference error. |
+ @override |
+ String toString() => !identical(upperBound, UnknownInferredType.instance) |
+ ? "'$typeParameter' must extend '$upperBound'" |
+ : "'$lowerBound' must extend '$typeParameter'"; |
+} |
+ |
+/// The synthetic element for [UnknownInferredType]. |
+class UnknownInferredTypeElement extends ElementImpl |
+ implements TypeDefiningElement { |
+ static final UnknownInferredTypeElement instance = |
+ new UnknownInferredTypeElement._(); |
+ |
+ @override |
+ UnknownInferredType get type => UnknownInferredType.instance; |
+ |
+ UnknownInferredTypeElement._() : super(Keyword.DYNAMIC.syntax, -1) { |
+ setModifier(Modifier.SYNTHETIC, true); |
} |
- void _visitFunctionType( |
- TypeParameterType typeParam, FunctionType type, bool paramIn) { |
- for (ParameterElement p in type.parameters) { |
- // If a lambda L is passed in to a function F, the parameters are |
- // "passed out" of F into L. Thus we invert the "passedIn" state. |
- _visitType(typeParam, p.type, !paramIn); |
+ @override |
+ ElementKind get kind => ElementKind.DYNAMIC; |
+ |
+ @override |
+ /*=T*/ accept/*<T>*/(ElementVisitor visitor) => null; |
+} |
+ |
+/// A type that is being inferred but is not currently known. |
+/// |
+/// This type will only appear in a downward inference context for type |
+/// parameters that we do not know yet. Notationally it is written `?`, for |
+/// example `List<?>`. This is distinct from `List<dynamic>`. These types will |
+/// never appear in the final resolved AST. |
+class UnknownInferredType extends TypeImpl { |
+ static final UnknownInferredType instance = new UnknownInferredType._(); |
+ |
+ UnknownInferredType._() |
+ : super(UnknownInferredTypeElement.instance, Keyword.DYNAMIC.syntax); |
+ |
+ @override |
+ int get hashCode => 1; |
+ |
+ @override |
+ bool get isDynamic => true; |
+ |
+ @override |
+ bool operator ==(Object object) => identical(object, this); |
+ |
+ @override |
+ bool isMoreSpecificThan(DartType type, |
+ [bool withDynamic = false, Set<Element> visitedElements]) { |
+ // T is S |
+ if (identical(this, type)) { |
+ return true; |
} |
- // If a lambda L is passed in to a function F, and we call L, the result of |
- // L is then "passed in" to F. So we keep the "passedIn" state. |
- _visitType(typeParam, type.returnType, paramIn); |
+ // else |
+ return withDynamic; |
} |
- void _visitInterfaceType( |
- TypeParameterType typeParam, InterfaceType type, bool paramIn) { |
- // Currently in "strong mode" generic type parameters are covariant. |
- // |
- // This means we treat them as "out" type parameters similar to the result |
- // of a function, and thus they follow the same rules. |
- // |
- // For example, we pass in Iterable<T> as a parameter. Then we iterate over |
- // it. The "T" is essentially an input. So it keeps the same state. |
- // Similarly, if we return an Iterable<T> it's equivalent to returning a T. |
- for (DartType typeArg in type.typeArguments) { |
- _visitType(typeParam, typeArg, paramIn); |
+ @override |
+ bool isSubtypeOf(DartType type) => true; |
+ |
+ @override |
+ bool isSupertypeOf(DartType type) => true; |
+ |
+ @override |
+ TypeImpl pruned(List<FunctionTypeAliasElement> prune) => this; |
+ |
+ @override |
+ DartType substitute2( |
+ List<DartType> argumentTypes, List<DartType> parameterTypes, |
+ [List<FunctionTypeAliasElement> prune]) { |
+ int length = parameterTypes.length; |
+ for (int i = 0; i < length; i++) { |
+ if (parameterTypes[i] == this) { |
+ return argumentTypes[i]; |
+ } |
} |
+ return this; |
} |
- void _visitType(TypeParameterType typeParam, DartType type, bool paramIn) { |
- if (type == typeParam) { |
- if (paramIn) { |
- passedIn = true; |
- } else { |
- passedOut = true; |
- } |
- } else if (type is FunctionType) { |
- _visitFunctionType(typeParam, type, paramIn); |
- } else if (type is InterfaceType) { |
- _visitInterfaceType(typeParam, type, paramIn); |
+ @override |
+ void appendTo(StringBuffer buffer, Set<TypeImpl> types) { |
+ buffer.write('?'); |
+ } |
+ |
+ /// Given a [type] T, return true if it does not have an unknown type `?`. |
+ static bool isKnown(DartType type) => !isUnknown(type); |
+ |
+ /// Given a [type] T, return true if it has an unknown type `?`. |
+ static bool isUnknown(DartType type) { |
+ if (identical(type, UnknownInferredType.instance)) { |
+ return true; |
} |
+ if (type is InterfaceTypeImpl) { |
+ return type.typeArguments.any(isUnknown); |
+ } |
+ if (type is FunctionType) { |
+ return isUnknown(type.returnType) || |
+ type.parameters.any((p) => isUnknown(p.type)); |
+ } |
+ return false; |
} |
} |