Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Unified Diff: packages/analyzer/lib/src/generated/type_system.dart

Issue 2990843002: Removed fixed dependencies (Closed)
Patch Set: Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: packages/analyzer/lib/src/generated/type_system.dart
diff --git a/packages/analyzer/lib/src/generated/type_system.dart b/packages/analyzer/lib/src/generated/type_system.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5574aed89f2b09b3e9abf83af23f13bfb8b2f982
--- /dev/null
+++ b/packages/analyzer/lib/src/generated/type_system.dart
@@ -0,0 +1,1765 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library analyzer.src.generated.type_system;
+
+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/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/error/listener.dart' show ErrorReporter;
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/type.dart';
+import 'package:analyzer/src/error/codes.dart' show StrongModeCode;
+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' show ParameterKind;
+import 'package:analyzer/src/generated/utilities_general.dart'
+ show JenkinsSmiHash;
+
+bool _isBottom(DartType t, {bool dynamicIsBottom: false}) {
+ return (t.isDynamic && dynamicIsBottom) || t.isBottom;
+}
+
+bool _isTop(DartType t, {bool dynamicIsBottom: false}) {
+ // TODO(leafp): Document the rules in play here
+ return (t.isDynamic && !dynamicIsBottom) || t.isObject;
+}
+
+typedef bool _GuardedSubtypeChecker<T>(T t1, T t2, Set<Element> visited);
+
+/**
+ * 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<T> | T`.
+ */
+ FutureUnionType._(DartType type, TypeProvider provider, TypeSystem system)
+ : _types = [
+ provider.futureType.instantiate([type]),
+ type
+ ],
+ super(null, null);
+
+ DartType get futureOfType => _types[0];
+
+ @override
+ int get hashCode {
+ int hash = 0;
+ for (var t in types) {
+ hash = JenkinsSmiHash.combine(hash, t.hashCode);
+ }
+ return JenkinsSmiHash.finish(hash);
+ }
+
+ DartType get type => _types[1];
+
+ Iterable<DartType> get types => _types;
+
+ @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
+ 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
+ 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 or a
+ * future-union, in which case it simply returns `T`.
+ *
+ * Conceptually this is used as the inverse of the `flatten(T)` operation,
+ * defined as:
+ *
+ * - `flatten(Future<T>) -> T`
+ * - `flatten(T) -> T`
+ *
+ * Thus the inverse will give us `T | Future<T>`.
+ *
+ * If [type] is top (dynamic or Object) then the resulting union type is
+ * equivalent to top, so we simply return it.
+ *
+ * For a similar reason `Future<T> | Future<Future<T>>` is equivalent to just
+ * `Future<T>`, so we return it. Note that it is not possible to get a
+ * `Future<T>` as a result of `flatten`, so a this case likely indicates a
+ * type error in the code, but it will be reported elsewhere.
+ */
+ static DartType from(
+ DartType type, TypeProvider provider, TypeSystem system) {
+ if (_isTop(type)) {
+ return type;
+ }
+ if (!identical(type, type.flattenFutures(system))) {
+ // As noted above, this most likely represents erroneous input.
+ return type;
+ }
+
+ if (type is FutureUnionType) {
+ return type;
+ }
+ return new FutureUnionType._(type, provider, system);
+ }
+}
+
+/**
+ * Implementation of [TypeSystem] using the strong mode rules.
+ * https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md
+ */
+class StrongTypeSystemImpl extends TypeSystem {
+ /**
+ * True if implicit casts should be allowed, otherwise false.
+ *
+ * This affects the behavior of [isAssignableTo].
+ */
+ final bool implicitCasts;
+
+ /**
+ * A list of non-nullable type names (e.g., 'int', 'bool', etc.).
+ */
+ final List<String> nonnullableTypes;
+
+ StrongTypeSystemImpl(
+ {this.implicitCasts: true,
+ this.nonnullableTypes: AnalysisOptionsImpl.NONNULLABLE_TYPES});
+
+ bool anyParameterType(FunctionType ft, bool predicate(DartType t)) {
+ return ft.parameters.any((p) => predicate(p.type));
+ }
+
+ @override
+ bool canPromoteToType(DartType to, DartType from) {
+ // Allow promoting to a subtype, for example:
+ //
+ // f(Base b) {
+ // if (b is SubTypeOfBase) {
+ // // promote `b` to SubTypeOfBase for this block
+ // }
+ // }
+ //
+ // This allows the variable to be used wherever the supertype (here `Base`)
+ // is expected, while gaining a more precise type.
+ if (isSubtypeOf(to, from)) {
+ return true;
+ }
+ // For a type parameter `T extends U`, allow promoting from the upper bound
+ // `U` to `S` where `S <: U`.
+ //
+ // This does restrict the variable, because `S </: T`, it can no longer be
+ // used as a `T` without another cast.
+ //
+ // However the members you could access from a variable of type `T`, were
+ // already those on the upper bound `U`. So all members on `U` will be
+ // accessible, as well as those on `S`. Pragmatically this feels like a
+ // useful enough trade-off to allow promotion.
+ //
+ // (In general we would need union types to support this feature precisely.)
+ if (from is TypeParameterType) {
+ return isSubtypeOf(to, from.resolveToBound(DynamicTypeImpl.instance));
+ }
+
+ return false;
+ }
+
+ @override
+ FunctionType functionTypeToConcreteType(
+ TypeProvider typeProvider, FunctionType t) {
+ // TODO(jmesserly): should we use a real "fuzzyArrow" bit on the function
+ // type? That would allow us to implement this in the subtype relation.
+ // TODO(jmesserly): we'll need to factor this differently if we want to
+ // move CodeChecker's functionality into existing analyzer. Likely we can
+ // let the Expression have a strict arrow, then in places were we do
+ // inference, convert back to a fuzzy arrow.
+
+ if (!t.parameters.any((p) => p.type.isDynamic)) {
+ return t;
+ }
+ ParameterElement shave(ParameterElement p) {
+ if (p.type.isDynamic) {
+ return new ParameterElementImpl.synthetic(
+ p.name, typeProvider.objectType, p.parameterKind);
+ }
+ return p;
+ }
+
+ List<ParameterElement> parameters = t.parameters.map(shave).toList();
+ FunctionElementImpl function = new FunctionElementImpl("", -1);
+ function.synthetic = true;
+ function.returnType = t.returnType;
+ function.shareTypeParameters(t.typeFormals);
+ function.shareParameters(parameters);
+ return function.type = new FunctionTypeImpl(function);
+ }
+
+ /**
+ * Given a type t, if t is an interface type with a call method
+ * defined, return the function type for the call method, otherwise
+ * return null.
+ */
+ FunctionType getCallMethodType(DartType t) {
+ if (t is InterfaceType) {
+ return t.lookUpInheritedMethod("call")?.type;
+ }
+ return null;
+ }
+
+ /// Computes the greatest lower bound of [type1] and [type2].
+ DartType getGreatestLowerBound(
+ TypeProvider provider, DartType type1, DartType type2,
+ {dynamicIsBottom: false}) {
+ // The greatest lower bound relation is reflexive.
+ if (identical(type1, type2)) {
+ 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) ||
+ _isBottom(type2, dynamicIsBottom: dynamicIsBottom)) {
+ return type2;
+ }
+ if (_isTop(type2, dynamicIsBottom: dynamicIsBottom) ||
+ _isBottom(type1, dynamicIsBottom: dynamicIsBottom)) {
+ return type1;
+ }
+
+ // Treat void as top-like for GLB. This only comes into play with the
+ // return types of two functions whose GLB is being taken. We allow a
+ // non-void-returning function to subtype a void-returning one, so match
+ // that logic here by treating the non-void arm as the subtype for GLB.
+ if (type1.isVoid) {
+ return type2;
+ }
+ if (type2.isVoid) {
+ return type1;
+ }
+
+ // Function types have structural GLB.
+ if (type1 is FunctionType && type2 is FunctionType) {
+ return _functionGreatestLowerBound(provider, type1, type2);
+ }
+
+ // Otherwise, the GLB of two types is one of them it if it is a subtype of
+ // the other.
+ if (isSubtypeOf(type1, type2)) {
+ return type1;
+ }
+
+ if (isSubtypeOf(type2, type1)) {
+ return type2;
+ }
+
+ // No subtype relation, so no known GLB.
+ return provider.bottomType;
+ }
+
+ /**
+ * Compute the least supertype of [type], which is known to be an interface
+ * type.
+ *
+ * In the event that the algorithm fails (which might occur due to a bug in
+ * the analyzer), `null` is returned.
+ */
+ DartType getLeastNullableSupertype(InterfaceType type) {
+ // compute set of supertypes
+ List<InterfaceType> s = InterfaceTypeImpl
+ .computeSuperinterfaceSet(type)
+ .where(isNullableType)
+ .toList();
+ return InterfaceTypeImpl.computeTypeAtMaxUniqueDepth(s);
+ }
+
+ /**
+ * Compute the least upper bound of two types.
+ */
+ @override
+ DartType getLeastUpperBound(
+ TypeProvider typeProvider, DartType type1, DartType type2,
+ {bool dynamicIsBottom: false}) {
+ if (isNullableType(type1) && isNonNullableType(type2)) {
+ assert(type2 is InterfaceType);
+ type2 = getLeastNullableSupertype(type2 as InterfaceType);
+ }
+ if (isNullableType(type2) && isNonNullableType(type1)) {
+ assert(type1 is InterfaceType);
+ type1 = getLeastNullableSupertype(type1 as InterfaceType);
+ }
+ return super.getLeastUpperBound(typeProvider, type1, type2,
+ dynamicIsBottom: dynamicIsBottom);
+ }
+
+ /**
+ * 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
+ * considered as part of the solution.
+ *
+ * If this function is called with a [contextType] that is also
+ * uninstantiated, or a [fnType] that is already instantiated, it will have
+ * no effect and return [fnType].
+ */
+ FunctionType inferFunctionTypeInstantiation(TypeProvider typeProvider,
+ FunctionType contextType, FunctionType fnType) {
+ if (contextType.typeFormals.isNotEmpty || fnType.typeFormals.isEmpty) {
+ return fnType;
+ }
+
+ // Create a TypeSystem that will allow certain type parameters to be
+ // 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);
+
+ // 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);
+
+ // 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;
+ }
+
+ /// 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.
+ ///
+ /// Concretely, given a function type with parameter types P0, P1, ... Pn,
+ /// result type R, and generic type parameters T0, T1, ... Tm, use the
+ /// argument types A0, A1, ... An to solve for the type parameters.
+ ///
+ /// For each parameter Pi, we want to ensure that Ai <: Pi. We can do this by
+ /// running the subtype algorithm, and when we reach a type parameter Tj,
+ /// 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>*/(
+ TypeProvider typeProvider,
+ /*=T*/ genericType,
+ List<DartType> declaredParameterTypes,
+ List<DartType> argumentTypes,
+ DartType declaredReturnType,
+ DartType returnContextType,
+ {ErrorReporter errorReporter,
+ AstNode errorNode}) {
+ // TODO(jmesserly): expose typeFormals on ParameterizedType.
+ List<TypeParameterElement> typeFormals = genericType is FunctionType
+ ? genericType.typeFormals
+ : genericType.typeParameters;
+ if (typeFormals.isEmpty) {
+ return genericType;
+ }
+
+ // Create a TypeSystem that will allow certain type parameters to be
+ // 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);
+
+ if (returnContextType != null) {
+ // If we're in a future union context, choose either the Future<T>
+ // or the T based on the declared return type.
+ if (returnContextType is FutureUnionType) {
+ var futureUnion = returnContextType as FutureUnionType;
+ returnContextType =
+ isSubtypeOf(declaredReturnType, typeProvider.futureDynamicType)
+ ? futureUnion.futureOfType
+ : futureUnion.type;
+ }
+
+ inferringTypeSystem.isSubtypeOf(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]);
+ }
+
+ return inferringTypeSystem._infer(
+ genericType, typeFormals, declaredReturnType, errorReporter, errorNode);
+ }
+
+ /**
+ * Given a [DartType] [type], if [type] is an uninstantiated
+ * parameterized type then instantiate the parameters to their
+ * bounds. Specifically, if [type] is of the form
+ * `<T0 extends B0, ... Tn extends Bn>.F` or
+ * `class C<T0 extends B0, ... Tn extends Bn> {...}`
+ * (where Bi is implicitly dynamic if absent),
+ * compute `{I0/T0, ..., In/Tn}F or C<I0, ..., In>` respectively
+ * where I_(i+1) = {I0/T0, ..., Ii/Ti, dynamic/T_(i+1)}B_(i+1).
+ * That is, we instantiate the generic with its bounds, replacing
+ * each Ti in Bi with dynamic to get Ii, and then replacing Ti with
+ * Ii in all of the remaining bounds.
+ */
+ DartType instantiateToBounds(DartType type) {
+ List<TypeParameterElement> typeFormals = typeFormalsAsElements(type);
+ int count = typeFormals.length;
+ if (count == 0) {
+ return type;
+ }
+
+ // We build up a substitution replacing bound parameters with
+ // their instantiated bounds, {substituted/variables}
+ List<DartType> substituted = new List<DartType>();
+ List<DartType> variables = new List<DartType>();
+ for (int i = 0; i < count; i++) {
+ TypeParameterElement param = typeFormals[i];
+ DartType bound = param.bound ?? DynamicTypeImpl.instance;
+ DartType variable = param.type;
+ // For each Ti extends Bi, first compute Ii by replacing
+ // Ti in Bi with dynamic (simultaneously replacing all
+ // of the previous Tj (j < i) with their instantiated bounds.
+ substituted.add(DynamicTypeImpl.instance);
+ variables.add(variable);
+ // Now update the substitution to replace Ti with Ii instead
+ // of dynamic in subsequent rounds.
+ substituted[i] = bound.substitute2(substituted, variables);
+ }
+
+ return instantiateType(type, substituted);
+ }
+
+ @override
+ bool isAssignableTo(DartType fromType, DartType toType) {
+ // TODO(leafp): Document the rules in play here
+
+ // An actual subtype
+ if (isSubtypeOf(fromType, toType)) {
+ return true;
+ }
+
+ if (!implicitCasts) {
+ return false;
+ }
+
+ // Don't allow implicit downcasts between function types
+ // and call method objects, as these will almost always fail.
+ if (fromType is FunctionType && getCallMethodType(toType) != null) {
+ return false;
+ }
+
+ // Don't allow a non-generic function where a generic one is expected. The
+ // former wouldn't know how to handle type arguments being passed to it.
+ // TODO(rnystrom): This same check also exists in FunctionTypeImpl.relate()
+ // but we don't always reliably go through that code path. This should be
+ // cleaned up to avoid the redundancy.
+ if (fromType is FunctionType &&
+ toType is FunctionType &&
+ fromType.typeFormals.isEmpty &&
+ toType.typeFormals.isNotEmpty) {
+ return false;
+ }
+
+ // If the subtype relation goes the other way, allow the implicit
+ // downcast.
+ if (isSubtypeOf(toType, fromType)) {
+ // TODO(leafp,jmesserly): we emit warnings/hints for these in
+ // src/task/strong/checker.dart, which is a bit inconsistent. That
+ // code should be handled into places that use isAssignableTo, such as
+ // ErrorVerifier.
+ return true;
+ }
+
+ return false;
+ }
+
+ bool isGroundType(DartType t) {
+ // TODO(leafp): Revisit this.
+ if (t is TypeParameterType) {
+ return false;
+ }
+ if (_isTop(t)) {
+ return true;
+ }
+
+ if (t is FunctionType) {
+ if (!_isTop(t.returnType) ||
+ anyParameterType(t, (pt) => !_isBottom(pt, dynamicIsBottom: true))) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ if (t is InterfaceType) {
+ List<DartType> typeArguments = t.typeArguments;
+ for (DartType typeArgument in typeArguments) {
+ if (!_isTop(typeArgument)) return false;
+ }
+ return true;
+ }
+
+ // We should not see any other type aside from malformed code.
+ return false;
+ }
+
+ @override
+ bool isMoreSpecificThan(DartType t1, DartType t2) => isSubtypeOf(t1, t2);
+
+ /// Check if [type] is in a set of preselected non-nullable types.
+ /// [FunctionType]s are always nullable.
+ bool isNonNullableType(DartType type) {
+ return !isNullableType(type);
+ }
+
+ /// Opposite of [isNonNullableType].
+ bool isNullableType(DartType type) {
+ return type is FunctionType ||
+ !nonnullableTypes.contains(_getTypeFullyQualifiedName(type));
+ }
+
+ /// Check that [f1] is a subtype of [f2] for an override.
+ ///
+ /// This is different from the normal function subtyping in two ways:
+ /// - we know the function types are strict arrows,
+ /// - it allows opt-in covariant parameters.
+ bool isOverrideSubtypeOf(FunctionType f1, FunctionType f2) {
+ return FunctionTypeImpl.relate(
+ f1,
+ f2,
+ (t1, t2, t1Covariant, _) =>
+ isSubtypeOf(t2, t1) || t1Covariant && isSubtypeOf(t1, t2),
+ instantiateToBounds,
+ returnRelation: isSubtypeOf);
+ }
+
+ @override
+ bool isSubtypeOf(DartType leftType, DartType rightType) {
+ return _isSubtypeOf(leftType, rightType, null);
+ }
+
+ @override
+ DartType refineBinaryExpressionType(
+ TypeProvider typeProvider,
+ DartType leftType,
+ TokenType operator,
+ DartType rightType,
+ DartType currentType) {
+ if (leftType is TypeParameterType &&
+ leftType.element.bound == typeProvider.numType) {
+ if (rightType == leftType || rightType == typeProvider.intType) {
+ if (operator == TokenType.PLUS ||
+ operator == TokenType.MINUS ||
+ operator == TokenType.STAR ||
+ operator == TokenType.PLUS_EQ ||
+ operator == TokenType.MINUS_EQ ||
+ operator == TokenType.STAR_EQ) {
+ return leftType;
+ }
+ }
+ if (rightType == typeProvider.doubleType) {
+ if (operator == TokenType.PLUS ||
+ operator == TokenType.MINUS ||
+ operator == TokenType.STAR ||
+ operator == TokenType.SLASH) {
+ return typeProvider.doubleType;
+ }
+ }
+ return currentType;
+ }
+ return super.refineBinaryExpressionType(
+ typeProvider, leftType, operator, rightType, currentType);
+ }
+
+ @override
+ DartType typeToConcreteType(TypeProvider typeProvider, DartType t) {
+ if (t is FunctionType) {
+ return functionTypeToConcreteType(typeProvider, t);
+ }
+ return t;
+ }
+
+ /**
+ * Compute the greatest lower bound of function types [f] and [g].
+ *
+ * The spec rules for GLB on function types, informally, are pretty simple:
+ *
+ * - If a parameter is required in both, it stays required.
+ *
+ * - If a positional parameter is optional or missing in one, it becomes
+ * optional.
+ *
+ * - Named parameters are unioned together.
+ *
+ * - For any parameter that exists in both functions, use the LUB of them as
+ * the resulting parameter type.
+ *
+ * - Use the GLB of their return types.
+ */
+ DartType _functionGreatestLowerBound(
+ TypeProvider provider, FunctionType f, FunctionType g) {
+ // Calculate the LUB of each corresponding pair of parameters.
+ List<ParameterElement> parameters = [];
+
+ bool hasPositional = false;
+ bool hasNamed = false;
+ addParameter(
+ String name, DartType fType, DartType gType, ParameterKind kind) {
+ DartType paramType;
+ if (fType != null && gType != null) {
+ // If both functions have this parameter, include both of their types.
+ paramType =
+ getLeastUpperBound(provider, fType, gType, dynamicIsBottom: true);
+ } else {
+ paramType = fType ?? gType;
+ }
+
+ parameters.add(new ParameterElementImpl.synthetic(name, paramType, kind));
+ }
+
+ // TODO(rnystrom): Right now, this assumes f and g do not have any type
+ // parameters. Revisit that in the presence of generic methods.
+ List<DartType> fRequired = f.normalParameterTypes;
+ List<DartType> gRequired = g.normalParameterTypes;
+
+ // We need some parameter names for in the synthesized function type.
+ List<String> fRequiredNames = f.normalParameterNames;
+ List<String> gRequiredNames = g.normalParameterNames;
+
+ // Parameters that are required in both functions are required in the
+ // result.
+ int requiredCount = math.min(fRequired.length, gRequired.length);
+ for (int i = 0; i < requiredCount; i++) {
+ addParameter(fRequiredNames[i], fRequired[i], gRequired[i],
+ ParameterKind.REQUIRED);
+ }
+
+ // Parameters that are optional or missing in either end up optional.
+ List<DartType> fPositional = f.optionalParameterTypes;
+ List<DartType> gPositional = g.optionalParameterTypes;
+ List<String> fPositionalNames = f.optionalParameterNames;
+ List<String> gPositionalNames = g.optionalParameterNames;
+
+ int totalPositional = math.max(fRequired.length + fPositional.length,
+ gRequired.length + gPositional.length);
+ for (int i = requiredCount; i < totalPositional; i++) {
+ // Find the corresponding positional parameters (required or optional) at
+ // this index, if there is one.
+ DartType fType;
+ String fName;
+ if (i < fRequired.length) {
+ fType = fRequired[i];
+ fName = fRequiredNames[i];
+ } else if (i < fRequired.length + fPositional.length) {
+ fType = fPositional[i - fRequired.length];
+ fName = fPositionalNames[i - fRequired.length];
+ }
+
+ DartType gType;
+ String gName;
+ if (i < gRequired.length) {
+ gType = gRequired[i];
+ gName = gRequiredNames[i];
+ } else if (i < gRequired.length + gPositional.length) {
+ gType = gPositional[i - gRequired.length];
+ gName = gPositionalNames[i - gRequired.length];
+ }
+
+ // The loop should not let us go past both f and g's positional params.
+ assert(fType != null || gType != null);
+
+ addParameter(fName ?? gName, fType, gType, ParameterKind.POSITIONAL);
+ hasPositional = true;
+ }
+
+ // Union the named parameters together.
+ Map<String, DartType> fNamed = f.namedParameterTypes;
+ Map<String, DartType> gNamed = g.namedParameterTypes;
+ for (String name in fNamed.keys.toSet()..addAll(gNamed.keys)) {
+ addParameter(name, fNamed[name], gNamed[name], ParameterKind.NAMED);
+ hasNamed = true;
+ }
+
+ // Edge case. Dart does not support functions with both optional positional
+ // and named parameters. If we would synthesize that, give up.
+ if (hasPositional && hasNamed) return provider.bottomType;
+
+ // Calculate the GLB of the return type.
+ DartType returnType =
+ getGreatestLowerBound(provider, f.returnType, g.returnType);
+ return new FunctionElementImpl.synthetic(parameters, returnType).type;
+ }
+
+ @override
+ DartType _functionParameterBound(
+ TypeProvider provider, DartType f, DartType g) =>
+ getGreatestLowerBound(provider, f, g, dynamicIsBottom: true);
+
+ /// Given a type return its name prepended with the URI to its containing
+ /// library and separated by a comma.
+ String _getTypeFullyQualifiedName(DartType type) {
+ return "${type?.element?.library?.identifier},$type";
+ }
+
+ /**
+ * Guard against loops in the class hierarchy
+ */
+ _GuardedSubtypeChecker<DartType> _guard(
+ _GuardedSubtypeChecker<DartType> check) {
+ return (DartType t1, DartType t2, Set<Element> visited) {
+ Element element = t1.element;
+ if (visited == null) {
+ visited = new HashSet<Element>();
+ }
+ if (element == null || !visited.add(element)) {
+ return false;
+ }
+ try {
+ return check(t1, t2, visited);
+ } finally {
+ visited.remove(element);
+ }
+ };
+ }
+
+ /// If [t1] or [t2] is a type parameter we are inferring, update its bound.
+ /// Returns `true` if we could possibly find a compatible type,
+ /// otherwise `false`.
+ bool _inferTypeParameterSubtypeOf(
+ DartType t1, DartType t2, Set<Element> visited) {
+ return false;
+ }
+
+ /**
+ * This currently does not implement a very complete least upper bound
+ * algorithm, but handles a couple of the very common cases that are
+ * causing pain in real code. The current algorithm is:
+ * 1. If either of the types is a supertype of the other, return it.
+ * This is in fact the best result in this case.
+ * 2. If the two types have the same class element, then take the
+ * pointwise least upper bound of the type arguments. This is again
+ * the best result, except that the recursive calls may not return
+ * the true least uppper bounds. The result is guaranteed to be a
+ * well-formed type under the assumption that the input types were
+ * well-formed (and assuming that the recursive calls return
+ * well-formed types).
+ * 3. Otherwise return the spec-defined least upper bound. This will
+ * be an upper bound, might (or might not) be least, and might
+ * (or might not) be a well-formed type.
+ *
+ * TODO(leafp): Use matchTypes or something similar here to handle the
+ * case where one of the types is a superclass (but not supertype) of
+ * the other, e.g. LUB(Iterable<double>, List<int>) = Iterable<num>
+ * TODO(leafp): Figure out the right final algorithm and implement it.
+ */
+ @override
+ DartType _interfaceLeastUpperBound(
+ TypeProvider provider, InterfaceType type1, InterfaceType type2) {
+ if (isSubtypeOf(type1, type2)) {
+ return type2;
+ }
+ if (isSubtypeOf(type2, type1)) {
+ return type1;
+ }
+ if (type1.element == type2.element) {
+ List<DartType> tArgs1 = type1.typeArguments;
+ List<DartType> tArgs2 = type2.typeArguments;
+
+ assert(tArgs1.length == tArgs2.length);
+ List<DartType> tArgs = new List(tArgs1.length);
+ for (int i = 0; i < tArgs1.length; i++) {
+ tArgs[i] = getLeastUpperBound(provider, tArgs1[i], tArgs2[i]);
+ }
+ InterfaceTypeImpl lub = new InterfaceTypeImpl(type1.element);
+ lub.typeArguments = tArgs;
+ return lub;
+ }
+ return InterfaceTypeImpl.computeLeastUpperBound(type1, type2) ??
+ provider.dynamicType;
+ }
+
+ /// Check that [f1] is a subtype of [f2].
+ ///
+ /// This will always assume function types use fuzzy arrows, in other words
+ /// that dynamic parameters of f1 and f2 are treated as bottom.
+ bool _isFunctionSubtypeOf(FunctionType f1, FunctionType f2) {
+ return FunctionTypeImpl.relate(
+ f1,
+ f2,
+ (t1, t2, _, __) => _isSubtypeOf(t2, t1, null, dynamicIsBottom: true),
+ instantiateToBounds,
+ returnRelation: isSubtypeOf);
+ }
+
+ bool _isInterfaceSubtypeOf(
+ InterfaceType i1, InterfaceType i2, Set<Element> visited) {
+ if (identical(i1, i2)) {
+ return true;
+ }
+
+ // Guard recursive calls
+ _GuardedSubtypeChecker<InterfaceType> guardedInterfaceSubtype = _guard(
+ (DartType i1, DartType i2, Set<Element> visited) =>
+ _isInterfaceSubtypeOf(i1, i2, visited));
+
+ 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++) {
+ DartType t1 = tArgs1[i];
+ DartType t2 = tArgs2[i];
+ if (!isSubtypeOf(t1, t2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (i2.isDartCoreFunction && i1.element.getMethod("call") != null) {
+ return true;
+ }
+
+ if (i1.isObject) {
+ return false;
+ }
+
+ if (guardedInterfaceSubtype(i1.superclass, i2, visited)) {
+ return true;
+ }
+
+ for (final parent in i1.interfaces) {
+ if (guardedInterfaceSubtype(parent, i2, visited)) {
+ return true;
+ }
+ }
+
+ for (final parent in i1.mixins) {
+ if (guardedInterfaceSubtype(parent, i2, visited)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool _isSubtypeOf(DartType t1, DartType t2, Set<Element> visited,
+ {bool dynamicIsBottom: false}) {
+ if (identical(t1, t2)) {
+ return true;
+ }
+
+ // Guard recursive calls
+ _GuardedSubtypeChecker<DartType> guardedSubtype = _guard(_isSubtypeOf);
+ _GuardedSubtypeChecker<DartType> guardedInferTypeParameter =
+ _guard(_inferTypeParameterSubtypeOf);
+
+ // The types are void, dynamic, bottom, interface types, function types,
+ // and type parameters. We proceed by eliminating these different classes
+ // from consideration.
+
+ // Trivially true.
+ if (_isTop(t2, dynamicIsBottom: dynamicIsBottom) ||
+ _isBottom(t1, dynamicIsBottom: dynamicIsBottom)) {
+ return true;
+ }
+
+ // Trivially false.
+ if (_isTop(t1, dynamicIsBottom: dynamicIsBottom) ||
+ _isBottom(t2, dynamicIsBottom: dynamicIsBottom)) {
+ return guardedInferTypeParameter(t1, t2, visited);
+ }
+
+ // 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) {
+ if (t1 == t2) {
+ return true;
+ }
+ if (guardedInferTypeParameter(t1, t2, visited)) {
+ return true;
+ }
+ DartType bound = t1.element.bound;
+ return bound == null ? false : guardedSubtype(bound, t2, visited);
+ }
+
+ if (t2 is TypeParameterType) {
+ return guardedInferTypeParameter(t1, t2, visited);
+ }
+
+ // Void only appears as the return type of a function, and we handle it
+ // directly in the function subtype rules. We should not get to a point
+ // where we're doing a subtype test on a "bare" void, but just in case we
+ // do, handle it safely.
+ // TODO(rnystrom): Determine how this can ever be reached. If it can't,
+ // remove it.
+ if (t1.isVoid || t2.isVoid) {
+ return t1.isVoid && t2.isVoid;
+ }
+
+ // We've eliminated void, dynamic, bottom, and type parameters. The only
+ // cases are the combinations of interface type and function type.
+
+ // A function type can only subtype an interface type if
+ // the interface type is Function
+ if (t1 is FunctionType && t2 is InterfaceType) {
+ return t2.isDartCoreFunction;
+ }
+
+ // 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 && t2 is FunctionType) {
+ var callType = getCallMethodType(t1);
+ return (callType != null) && _isFunctionSubtypeOf(callType, t2);
+ }
+
+ // Two interface types
+ if (t1 is InterfaceType && t2 is InterfaceType) {
+ return _isInterfaceSubtypeOf(t1, t2, visited);
+ }
+
+ return _isFunctionSubtypeOf(t1 as FunctionType, t2 as FunctionType);
+ }
+
+ /**
+ * This currently just implements a simple least upper bound to
+ * handle some common cases. It also avoids some termination issues
+ * with the naive spec algorithm. The least upper bound of two types
+ * (at least one of which is a type parameter) is computed here as:
+ * 1. If either type is a supertype of the other, return it.
+ * 2. If the first type is a type parameter, replace it with its bound,
+ * with recursive occurrences of itself replaced with Object.
+ * The second part of this should ensure termination. Informally,
+ * each type variable instantiation in one of the arguments to the
+ * least upper bound algorithm now strictly reduces the number
+ * of bound variables in scope in that argument position.
+ * 3. If the second type is a type parameter, do the symmetric operation
+ * to #2.
+ *
+ * It's not immediately obvious why this is symmetric in the case that both
+ * of them are type parameters. For #1, symmetry holds since subtype
+ * is antisymmetric. For #2, it's clearly not symmetric if upper bounds of
+ * bottom are allowed. Ignoring this (for various reasons, not least
+ * of which that there's no way to write it), there's an informal
+ * argument (that might even be right) that you will always either
+ * end up expanding both of them or else returning the same result no matter
+ * which order you expand them in. A key observation is that
+ * identical(expand(type1), type2) => subtype(type1, type2)
+ * and hence the contra-positive.
+ *
+ * TODO(leafp): Think this through and figure out what's the right
+ * definition. Be careful about termination.
+ *
+ * I suspect in general a reasonable algorithm is to expand the innermost
+ * type variable first. Alternatively, you could probably choose to treat
+ * it as just an instance of the interface type upper bound problem, with
+ * the "inheritance" chain extended by the bounds placed on the variables.
+ */
+ @override
+ DartType _typeParameterLeastUpperBound(
+ TypeProvider provider, DartType type1, DartType type2) {
+ if (isSubtypeOf(type1, type2)) {
+ return type2;
+ }
+ if (isSubtypeOf(type2, type1)) {
+ return type1;
+ }
+ if (type1 is TypeParameterType) {
+ type1 = type1
+ .resolveToBound(provider.objectType)
+ .substitute2([provider.objectType], [type1]);
+ return getLeastUpperBound(provider, type1, type2);
+ }
+ // We should only be called when at least one of the types is a
+ // TypeParameterType
+ type2 = type2
+ .resolveToBound(provider.objectType)
+ .substitute2([provider.objectType], [type2]);
+ return getLeastUpperBound(provider, type1, type2);
+ }
+}
+
+/**
+ * The interface `TypeSystem` defines the behavior of an object representing
+ * the type system. This provides a common location to put methods that act on
+ * types but may need access to more global data structures, and it paves the
+ * way for a possible future where we may wish to make the type system
+ * pluggable.
+ */
+abstract class TypeSystem {
+ /**
+ * Returns `true` if we can promote to the first type from the second type.
+ *
+ * In the standard Dart type system, it is not possible to promote from or to
+ * `dynamic`, and we must be promoting to a more specific type, see
+ * [isMoreSpecificThan].
+ *
+ * In strong mode, this is equivalent to [isSubtypeOf].
+ */
+ bool canPromoteToType(DartType to, DartType from);
+
+ /**
+ * Make a function type concrete.
+ *
+ * Normally we treat dynamically typed parameters as bottom for function
+ * types. This allows type tests such as `if (f is SingleArgFunction)`.
+ * It also requires a dynamic check on the parameter type to call these
+ * functions.
+ *
+ * When we convert to a strict arrow, dynamically typed parameters become
+ * top. This is safe to do for known functions, like top-level or local
+ * functions and static methods. Those functions must already be essentially
+ * treating dynamic as top.
+ *
+ * Only the outer-most arrow can be strict. Any others must be fuzzy, because
+ * we don't know what function value will be passed there.
+ */
+ FunctionType functionTypeToConcreteType(
+ TypeProvider typeProvider, FunctionType t);
+
+ /**
+ * Compute the least upper bound of two types.
+ */
+ DartType getLeastUpperBound(
+ TypeProvider typeProvider, DartType type1, DartType type2,
+ {bool dynamicIsBottom: false}) {
+ // The least upper bound relation is reflexive.
+ if (identical(type1, type2)) {
+ 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) ||
+ _isBottom(type2, dynamicIsBottom: dynamicIsBottom)) {
+ return type1;
+ }
+ if (_isTop(type2, dynamicIsBottom: dynamicIsBottom) ||
+ _isBottom(type1, dynamicIsBottom: dynamicIsBottom)) {
+ return type2;
+ }
+ // The least upper bound of void and any type T != dynamic is void.
+ if (type1.isVoid) {
+ return type1;
+ }
+ if (type2.isVoid) {
+ return type2;
+ }
+
+ if (type1 is TypeParameterType || type2 is TypeParameterType) {
+ return _typeParameterLeastUpperBound(typeProvider, type1, type2);
+ }
+
+ // The least upper bound of a function type and an interface type T is the
+ // least upper bound of Function and T.
+ if (type1 is FunctionType && type2 is InterfaceType) {
+ type1 = typeProvider.functionType;
+ }
+ if (type2 is FunctionType && type1 is InterfaceType) {
+ type2 = typeProvider.functionType;
+ }
+
+ // At this point type1 and type2 should both either be interface types or
+ // function types.
+ if (type1 is InterfaceType && type2 is InterfaceType) {
+ return _interfaceLeastUpperBound(typeProvider, type1, type2);
+ }
+
+ if (type1 is FunctionType && type2 is FunctionType) {
+ return _functionLeastUpperBound(typeProvider, type1, type2);
+ }
+
+ // Should never happen. As a defensive measure, return the dynamic type.
+ assert(false);
+ return typeProvider.dynamicType;
+ }
+
+ /**
+ * Given a [DartType] [type], instantiate it with its bounds.
+ *
+ * The behavior of this method depends on the type system, for example, in
+ * classic Dart `dynamic` will be used for all type arguments, whereas
+ * strong mode prefers the actual bound type if it was specified.
+ */
+ DartType instantiateToBounds(DartType type);
+
+ /**
+ * Given a [DartType] [type] and a list of types
+ * [typeArguments], instantiate the type formals with the
+ * provided actuals. If [type] is not a parameterized type,
+ * no instantiation is done.
+ */
+ DartType instantiateType(DartType type, List<DartType> typeArguments) {
+ if (type is ParameterizedType) {
+ return type.instantiate(typeArguments);
+ } else {
+ return type;
+ }
+ }
+
+ /**
+ * Return `true` if the [leftType] is assignable to the [rightType] (that is,
+ * if leftType <==> rightType).
+ */
+ bool isAssignableTo(DartType leftType, DartType rightType);
+
+ /**
+ * Return `true` if the [leftType] is more specific than the [rightType]
+ * (that is, if leftType << rightType), as defined in the Dart language spec.
+ *
+ * In strong mode, this is equivalent to [isSubtypeOf].
+ */
+ bool isMoreSpecificThan(DartType leftType, DartType rightType);
+
+ /**
+ * Return `true` if the [leftType] is a subtype of the [rightType] (that is,
+ * if leftType <: rightType).
+ */
+ bool isSubtypeOf(DartType leftType, DartType rightType);
+
+ /**
+ * Searches the superinterfaces of [type] for implementations of [genericType]
+ * and returns the most specific type argument used for that generic type.
+ *
+ * For example, given [type] `List<int>` and [genericType] `Iterable<T>`,
+ * returns [int].
+ *
+ * Returns `null` if [type] does not implement [genericType].
+ */
+ // TODO(jmesserly): this is very similar to code used for flattening futures.
+ // The only difference is, because of a lack of TypeProvider, the other method
+ // has to match the Future type by its name and library. Here was are passed
+ // in the correct type.
+ DartType mostSpecificTypeArgument(DartType type, DartType genericType) {
+ if (type is! InterfaceType) return null;
+
+ // Walk the superinterface hierarchy looking for [genericType].
+ List<DartType> candidates = <DartType>[];
+ HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>();
+ void recurse(InterfaceType interface) {
+ if (interface.element == genericType.element &&
+ interface.typeArguments.isNotEmpty) {
+ candidates.add(interface.typeArguments[0]);
+ }
+ if (visitedClasses.add(interface.element)) {
+ if (interface.superclass != null) {
+ recurse(interface.superclass);
+ }
+ interface.mixins.forEach(recurse);
+ interface.interfaces.forEach(recurse);
+ visitedClasses.remove(interface.element);
+ }
+ }
+
+ recurse(type);
+
+ // Since the interface may be implemented multiple times with different
+ // type arguments, choose the best one.
+ return InterfaceTypeImpl.findMostSpecificType(candidates, this);
+ }
+
+ /**
+ * Attempts to make a better guess for the type of a binary with the given
+ * [operator], given that resolution has so far produced the [currentType].
+ */
+ DartType refineBinaryExpressionType(
+ TypeProvider typeProvider,
+ DartType leftType,
+ TokenType operator,
+ DartType rightType,
+ DartType currentType) {
+ // bool
+ if (operator == TokenType.AMPERSAND_AMPERSAND ||
+ operator == TokenType.BAR_BAR ||
+ operator == TokenType.EQ_EQ ||
+ operator == TokenType.BANG_EQ) {
+ return typeProvider.boolType;
+ }
+ DartType intType = typeProvider.intType;
+ if (leftType == intType) {
+ // int op double
+ if (operator == TokenType.MINUS ||
+ operator == TokenType.PERCENT ||
+ operator == TokenType.PLUS ||
+ operator == TokenType.STAR ||
+ operator == TokenType.MINUS_EQ ||
+ operator == TokenType.PERCENT_EQ ||
+ operator == TokenType.PLUS_EQ ||
+ operator == TokenType.STAR_EQ) {
+ DartType doubleType = typeProvider.doubleType;
+ if (rightType == doubleType) {
+ return doubleType;
+ }
+ }
+ // int op int
+ if (operator == TokenType.MINUS ||
+ operator == TokenType.PERCENT ||
+ operator == TokenType.PLUS ||
+ operator == TokenType.STAR ||
+ operator == TokenType.TILDE_SLASH ||
+ operator == TokenType.MINUS_EQ ||
+ operator == TokenType.PERCENT_EQ ||
+ operator == TokenType.PLUS_EQ ||
+ operator == TokenType.STAR_EQ ||
+ operator == TokenType.TILDE_SLASH_EQ) {
+ if (rightType == intType) {
+ return intType;
+ }
+ }
+ }
+ // default
+ return currentType;
+ }
+
+ /**
+ * Given a [DartType] type, return the [TypeParameterElement]s corresponding
+ * to its formal type parameters (if any).
+ *
+ * @param type the type whose type arguments are to be returned
+ * @return the type arguments associated with the given type
+ */
+ List<TypeParameterElement> typeFormalsAsElements(DartType type) {
+ if (type is FunctionType) {
+ return type.typeFormals;
+ } else if (type is InterfaceType) {
+ return type.typeParameters;
+ } else {
+ return TypeParameterElement.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Given a [DartType] type, return the [DartType]s corresponding
+ * to its formal type parameters (if any).
+ *
+ * @param type the type whose type arguments are to be returned
+ * @return the type arguments associated with the given type
+ */
+ List<DartType> typeFormalsAsTypes(DartType type) =>
+ TypeParameterTypeImpl.getTypes(typeFormalsAsElements(type));
+
+ /**
+ * Make a type concrete. A type is concrete if it is not a function
+ * type, or if it is a function type with no dynamic parameters. A
+ * non-concrete function type is made concrete by replacing dynamic
+ * parameters with Object.
+ */
+ DartType typeToConcreteType(TypeProvider typeProvider, DartType t);
+
+ /**
+ * Compute the least upper bound of function types [f] and [g].
+ *
+ * The spec rules for LUB on function types, informally, are pretty simple
+ * (though unsound):
+ *
+ * - If the functions don't have the same number of required parameters,
+ * always return `Function`.
+ *
+ * - Discard any optional named or positional parameters the two types do not
+ * have in common.
+ *
+ * - Compute the LUB of each corresponding pair of parameter and return types.
+ * Return a function type with those types.
+ */
+ DartType _functionLeastUpperBound(
+ TypeProvider provider, FunctionType f, FunctionType g) {
+ // TODO(rnystrom): Right now, this assumes f and g do not have any type
+ // parameters. Revisit that in the presence of generic methods.
+ List<DartType> fRequired = f.normalParameterTypes;
+ List<DartType> gRequired = g.normalParameterTypes;
+
+ // We need some parameter names for in the synthesized function type, so
+ // arbitrarily use f's.
+ List<String> fRequiredNames = f.normalParameterNames;
+ List<String> fPositionalNames = f.optionalParameterNames;
+
+ // If F and G differ in their number of required parameters, then the
+ // least upper bound of F and G is Function.
+ if (fRequired.length != gRequired.length) {
+ return provider.functionType;
+ }
+
+ // Calculate the LUB of each corresponding pair of parameters.
+ List<ParameterElement> parameters = [];
+
+ for (int i = 0; i < fRequired.length; i++) {
+ parameters.add(new ParameterElementImpl.synthetic(
+ fRequiredNames[i],
+ _functionParameterBound(provider, fRequired[i], gRequired[i]),
+ ParameterKind.REQUIRED));
+ }
+
+ List<DartType> fPositional = f.optionalParameterTypes;
+ List<DartType> gPositional = g.optionalParameterTypes;
+
+ // Ignore any extra optional positional parameters if one has more than the
+ // other.
+ int length = math.min(fPositional.length, gPositional.length);
+ for (int i = 0; i < length; i++) {
+ parameters.add(new ParameterElementImpl.synthetic(
+ fPositionalNames[i],
+ _functionParameterBound(provider, fPositional[i], gPositional[i]),
+ ParameterKind.POSITIONAL));
+ }
+
+ Map<String, DartType> fNamed = f.namedParameterTypes;
+ Map<String, DartType> gNamed = g.namedParameterTypes;
+ for (String name in fNamed.keys.toSet()..retainAll(gNamed.keys)) {
+ parameters.add(new ParameterElementImpl.synthetic(
+ name,
+ _functionParameterBound(provider, fNamed[name], gNamed[name]),
+ ParameterKind.NAMED));
+ }
+
+ // Calculate the LUB of the return type.
+ DartType returnType =
+ getLeastUpperBound(provider, f.returnType, g.returnType);
+ return new FunctionElementImpl.synthetic(parameters, returnType).type;
+ }
+
+ /**
+ * Calculates the appropriate upper or lower bound of a pair of parameters
+ * for two function types whose least upper bound is being calculated.
+ *
+ * In spec mode, this uses least upper bound, which... doesn't really make
+ * much sense. Strong mode overrides this to use greatest lower bound.
+ */
+ DartType _functionParameterBound(
+ TypeProvider provider, DartType f, DartType g) =>
+ getLeastUpperBound(provider, f, g);
+
+ /**
+ * Given two [InterfaceType]s [type1] and [type2] return their least upper
+ * bound in a type system specific manner.
+ */
+ DartType _interfaceLeastUpperBound(
+ TypeProvider provider, InterfaceType type1, InterfaceType type2);
+
+ /**
+ * Given two [DartType]s [type1] and [type2] at least one of which is a
+ * [TypeParameterType], return their least upper bound in a type system
+ * specific manner.
+ */
+ DartType _typeParameterLeastUpperBound(
+ TypeProvider provider, DartType type1, DartType type2);
+
+ /**
+ * Create either a strong mode or regular type system based on context.
+ */
+ static TypeSystem create(AnalysisContext context) {
+ var options = context.analysisOptions as AnalysisOptionsImpl;
+ return options.strongMode
+ ? new StrongTypeSystemImpl(
+ implicitCasts: options.implicitCasts,
+ nonnullableTypes: options.nonnullableTypes)
+ : new TypeSystemImpl();
+ }
+}
+
+/**
+ * Implementation of [TypeSystem] using the rules in the Dart specification.
+ */
+class TypeSystemImpl extends TypeSystem {
+ TypeSystemImpl();
+
+ @override
+ bool canPromoteToType(DartType to, DartType from) {
+ // Declared type should not be "dynamic".
+ // Promoted type should not be "dynamic".
+ // Promoted type should be more specific than declared.
+ return !from.isDynamic && !to.isDynamic && to.isMoreSpecificThan(from);
+ }
+
+ @override
+ FunctionType functionTypeToConcreteType(
+ TypeProvider typeProvider, FunctionType t) =>
+ t;
+
+ /**
+ * Instantiate a parameterized type using `dynamic` for all generic
+ * parameters. Returns the type unchanged if there are no parameters.
+ */
+ DartType instantiateToBounds(DartType type) {
+ List<DartType> typeFormals = typeFormalsAsTypes(type);
+ int count = typeFormals.length;
+ if (count > 0) {
+ List<DartType> typeArguments =
+ new List<DartType>.filled(count, DynamicTypeImpl.instance);
+ return instantiateType(type, typeArguments);
+ }
+ return type;
+ }
+
+ @override
+ bool isAssignableTo(DartType leftType, DartType rightType) {
+ return leftType.isAssignableTo(rightType);
+ }
+
+ @override
+ bool isMoreSpecificThan(DartType t1, DartType t2) =>
+ t1.isMoreSpecificThan(t2);
+
+ @override
+ bool isSubtypeOf(DartType leftType, DartType rightType) {
+ return leftType.isSubtypeOf(rightType);
+ }
+
+ @override
+ DartType typeToConcreteType(TypeProvider typeProvider, DartType t) => t;
+
+ @override
+ DartType _interfaceLeastUpperBound(
+ TypeProvider provider, InterfaceType type1, InterfaceType type2) {
+ InterfaceType result =
+ InterfaceTypeImpl.computeLeastUpperBound(type1, type2);
+ return result ?? provider.dynamicType;
+ }
+
+ @override
+ DartType _typeParameterLeastUpperBound(
+ TypeProvider provider, DartType type1, DartType type2) {
+ type1 = type1.resolveToBound(provider.objectType);
+ type2 = type2.resolveToBound(provider.objectType);
+ return getLeastUpperBound(provider, type1, type2);
+ }
+}
+
+/// 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;
+
+ /// The outer strong mode type system, used for GLB and LUB, so we don't
+ /// recurse into our constraint solving code.
+ final StrongTypeSystemImpl _typeSystem;
+ final Map<TypeParameterType, _TypeParameterBound> _bounds;
+
+ _StrongInferenceTypeSystem(this._typeProvider, this._typeSystem,
+ Iterable<TypeParameterElement> typeFormals)
+ : _bounds = new Map.fromIterable(typeFormals,
+ key: (t) => t.type, value: (t) => new _TypeParameterBound());
+
+ /// 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);
+
+ // Initialize the inferred type array.
+ //
+ // They all start as `dynamic` to offer reasonable degradation for f-bounded
+ // type parameters.
+ var inferredTypes = new List<DartType>.filled(
+ fnTypeParams.length, DynamicTypeImpl.instance,
+ growable: false);
+
+ for (int i = 0; i < fnTypeParams.length; i++) {
+ TypeParameterType typeParam = fnTypeParams[i];
+
+ // 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:
+ //
+ // <TFrom, TTo extends TFrom>
+ //
+ // 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:
+ //
+ // <TFrom, TTo extends Iterable<TFrom>>
+ //
+ // Or if the type parameter's bound depends on itself such as:
+ //
+ // <T extends Clonable<T>>
+ DartType declaredUpperBound = typeParam.element.bound;
+ if (declaredUpperBound != null) {
+ // Assert that the type parameter is a subtype of its bound.
+ _inferTypeParameterSubtypeOf(typeParam,
+ declaredUpperBound.substitute2(inferredTypes, fnTypeParams), null);
+ }
+
+ // 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);
+
+ _TypeParameterBound bound = _bounds[typeParam];
+ 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;
+ }
+ 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;
+ }
+
+ inferredTypes[i] =
+ variance.passedIn && !upperBound.isDynamic || lowerBound.isBottom
+ ? upperBound
+ : lowerBound;
+ }
+
+ // Return the instantiated type.
+ return genericType.instantiate(inferredTypes) as dynamic/*=T*/;
+ }
+
+ @override
+ bool _inferTypeParameterSubtypeOf(
+ DartType t1, DartType t2, Set<Element> visited) {
+ if (t1 is TypeParameterType) {
+ _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(_typeProvider, bound.upper, t2);
+ // Optimistically assume we will be able to satisfy the constraint.
+ return true;
+ }
+ }
+ 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(_typeProvider, bound.lower, t1);
+ // Optimistically assume we will be able to satisfy the constraint.
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+/// An [upper] and [lower] bound for a type variable.
+class _TypeParameterBound {
+ /// The upper bound of the type parameter. In other words, T <: upperBound.
+ ///
+ /// In Dart this can be written as `<T extends UpperBoundType>`.
+ ///
+ /// In inference, this can happen as a result of parameters of function type.
+ /// For example, consider a signature like:
+ ///
+ /// T reduce<T>(List<T> values, T f(T x, T y));
+ ///
+ /// and a call to it like:
+ ///
+ /// reduce(values, (num x, num y) => ...);
+ ///
+ /// From the function expression's parameters, we conclude `T <: num`. We may
+ /// still be able to conclude a different [lower] based on `values` or
+ /// the type of the elided `=> ...` body. For example:
+ ///
+ /// reduce(['x'], (num x, num y) => 'hi');
+ ///
+ /// 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;
+
+ /// The lower bound of the type parameter. In other words, lowerBound <: T.
+ ///
+ /// This kind of constraint cannot be expressed in Dart, but it applies when
+ /// we're doing inference. For example, consider a signature like:
+ ///
+ /// T pickAtRandom<T>(T x, T y);
+ ///
+ /// and a call to it like:
+ ///
+ /// pickAtRandom(1, 2.0)
+ ///
+ /// when we see the first parameter is an `int`, we know that `int <: T`.
+ /// When we see `double` this implies `double <: T`.
+ /// Combining these constraints results in a lower bound of `num`.
+ ///
+ /// 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;
+}
+
+/// 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;
+
+ /// 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:
+ ///
+ /// typedef void Func<T>(T t);
+ /// Func<TIn> method<TIn>(TIn t);
+ bool passedIn = false;
+
+ _TypeParameterVariance.from(TypeParameterType typeParam, DartType type) {
+ _visitType(typeParam, type, false);
+ }
+
+ 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);
+ }
+ // 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);
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698