[analyzer][cfe] Share constraint generation for generic functions
Part of https://github.com/dart-lang/sdk/issues/54902
Change-Id: Ic025cdc36a266d35094626df175346ccac6f99fe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/393860
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart
index d47f831..3f26fce 100644
--- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart
@@ -667,6 +667,15 @@
List<SharedTypeParameterStructure<TypeStructure>>
typeParametersToEliminate);
+ /// Computes the least closure of a type.
+ ///
+ /// Computing the greatest closure of a type is described here:
+ /// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#type-variable-elimination-least-and-greatest-closure-of-a-type
+ TypeStructure leastClosureOfTypeInternal(
+ TypeStructure type,
+ List<SharedTypeParameterStructure<TypeStructure>>
+ typeParametersToEliminate);
+
/// Converts a type into a corresponding type schema.
SharedTypeSchemaView<TypeStructure> typeToSchema(
SharedTypeView<TypeStructure> type);
@@ -924,16 +933,15 @@
/// Matches [p] against [q].
///
- /// If [p] and [q] are both non-generic function types, and [p] is a subtype
- /// of [q] under some constraints, the constraints making the relation
- /// possible are recorded, and `true` is returned. Otherwise, the constraint
- /// state is unchanged (or rolled back using [restoreState]), and `false` is
- /// returned.
+ /// If [p] and [q] are both function types, and [p] is a subtype of [q] under
+ /// some constraints, the constraints making the relation possible are
+ /// recorded, and `true` is returned. Otherwise, the constraint state is
+ /// unchanged (or rolled back using [restoreState]), and `false` is returned.
///
/// An invariant of the type inference is that only [p] or [q] may be a
/// schema (in other words, may contain the unknown type `_`); the other must
- /// be simply a type. If [leftSchema] is `true`, [p] may contain `_`; if it is
- /// `false`, [q] may contain `_`.
+ /// be simply a type. If [leftSchema] is `true`, [p] may contain `_`; if it
+ /// is `false`, [q] may contain `_`.
bool performSubtypeConstraintGenerationForFunctionTypes(
TypeStructure p, TypeStructure q,
{required bool leftSchema, required AstNode? astNodeForTesting}) {
@@ -941,125 +949,221 @@
FunctionParameterStructure> &&
q is SharedFunctionTypeStructure<TypeStructure, TypeParameterStructure,
FunctionParameterStructure>) {
- // A function type (M0,..., Mn, [M{n+1}, ..., Mm]) -> R0 is a subtype
- // match for a function type (N0,..., Nk, [N{k+1}, ..., Nr]) -> R1 with
- // respect to L under constraints C0 + ... + Cr + C
- //
- // If R0 is a subtype match for a type R1 with respect to L under
- // constraints C. If n <= k and r <= m. And for i in 0...r, Ni is a
- // subtype match for Mi with respect to L under constraints Ci.
+ if (p.typeFormals.isEmpty && q.typeFormals.isEmpty) {
+ return _handleNonGenericFunctionTypes(p, q,
+ leftSchema: leftSchema, astNodeForTesting: astNodeForTesting);
+ } else {
+ return _handleGenericFunctionTypes(p, q,
+ leftSchema: leftSchema, astNodeForTesting: astNodeForTesting);
+ }
+ }
- if (p.typeFormals.isEmpty &&
- q.typeFormals.isEmpty &&
- p.sortedNamedParameters.isEmpty &&
- q.sortedNamedParameters.isEmpty &&
- p.requiredPositionalParameterCount <=
- q.requiredPositionalParameterCount &&
- p.positionalParameterTypes.length >=
- q.positionalParameterTypes.length) {
- final TypeConstraintGeneratorState state = currentState;
+ return false;
+ }
+ /// Matches non-generic function type [p] against non-generic function type
+ /// [q].
+ ///
+ /// See the documentation on
+ /// [performSubtypeConstraintGenerationForFunctionTypes] for details.
+ bool _handleNonGenericFunctionTypes(
+ SharedFunctionTypeStructure<TypeStructure, TypeParameterStructure,
+ FunctionParameterStructure>
+ p,
+ SharedFunctionTypeStructure<TypeStructure, TypeParameterStructure,
+ FunctionParameterStructure>
+ q,
+ {required bool leftSchema,
+ required AstNode? astNodeForTesting}) {
+ assert(p.typeFormals.isEmpty && q.typeFormals.isEmpty);
+ // A function type (M0,..., Mn, [M{n+1}, ..., Mm]) -> R0 is a subtype
+ // match for a function type (N0,..., Nk, [N{k+1}, ..., Nr]) -> R1 with
+ // respect to L under constraints C0 + ... + Cr + C
+ //
+ // If R0 is a subtype match for a type R1 with respect to L under
+ // constraints C. If n <= k and r <= m. And for i in 0...r, Ni is a
+ // subtype match for Mi with respect to L under constraints Ci.
+ if (p.sortedNamedParameters.isEmpty &&
+ q.sortedNamedParameters.isEmpty &&
+ p.requiredPositionalParameterCount <=
+ q.requiredPositionalParameterCount &&
+ p.positionalParameterTypes.length >=
+ q.positionalParameterTypes.length) {
+ final TypeConstraintGeneratorState state = currentState;
+
+ if (!performSubtypeConstraintGenerationInternal(
+ p.returnType, q.returnType,
+ leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
+ return false;
+ }
+ for (int i = 0; i < q.positionalParameterTypes.length; ++i) {
if (!performSubtypeConstraintGenerationInternal(
- p.returnType, q.returnType,
- leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
+ q.positionalParameterTypes[i], p.positionalParameterTypes[i],
+ leftSchema: !leftSchema, astNodeForTesting: astNodeForTesting)) {
+ restoreState(state);
return false;
}
- for (int i = 0; i < q.positionalParameterTypes.length; ++i) {
- if (!performSubtypeConstraintGenerationInternal(
- q.positionalParameterTypes[i], p.positionalParameterTypes[i],
- leftSchema: !leftSchema, astNodeForTesting: astNodeForTesting)) {
- restoreState(state);
- return false;
- }
- }
- return true;
- } else if (p.typeFormals.isEmpty &&
- q.typeFormals.isEmpty &&
- p.positionalParameterTypes.length ==
- p.requiredPositionalParameterCount &&
- q.positionalParameterTypes.length ==
- q.requiredPositionalParameterCount &&
- p.requiredPositionalParameterCount ==
- q.requiredPositionalParameterCount &&
- p.sortedNamedParameters.isNotEmpty &&
- q.sortedNamedParameters.length <= p.sortedNamedParameters.length) {
- // Function types with named parameters are treated analogously to the
- // positional parameter case above.
-
- final TypeConstraintGeneratorState state = currentState;
-
- if (!performSubtypeConstraintGenerationInternal(
- p.returnType, q.returnType,
- leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
- return false;
- }
- for (int i = 0; i < p.positionalParameterTypes.length; ++i) {
- if (!performSubtypeConstraintGenerationInternal(
- q.positionalParameterTypes[i], p.positionalParameterTypes[i],
- leftSchema: !leftSchema, astNodeForTesting: astNodeForTesting)) {
- restoreState(state);
- return false;
- }
- }
- // Consume parameter names from p and q in order. Since the named
- // parameters in p and q are already sorted by name, we can do this by
- // iterating through both lists in tandem.
- int i = 0;
- int j = 0;
- while (true) {
- // Determine whether the next parameter should be consumed from p,
- // q, or both (because the next set of names matches). If the next
- // parameter should be consumed from p, comparisonResult will be set
- // to a value < 0. If the next parameter should be consumed from q,
- // comparisonResult will be set to a value > 0. If the next
- // parameter should be consumed from both, comparisonResult will be
- // set to 0.
- int comparisonResult;
- if (i >= p.sortedNamedParameters.length) {
- if (j >= q.sortedNamedParameters.length) {
- // No parameters left.
- return true;
- } else {
- // No more parameters in p, so the next parameter must come from
- // q.
- comparisonResult = 1;
- }
- } else if (j >= q.sortedNamedParameters.length) {
- // No more parameters in q, so the next parameter must come from
- // p.
- comparisonResult = -1;
- } else {
- comparisonResult = p.sortedNamedParameters[i].name
- .compareTo(q.sortedNamedParameters[j].name);
- }
- if (comparisonResult > 0) {
- // Extra parameter in q that q that doesn't exist in p. No match.
- restoreState(state);
- return false;
- } else if (comparisonResult < 0) {
- // Extra parameter in p that doesn't exist in q. Ok if not
- // required.
- if (p.sortedNamedParameters[i].isRequired) {
- restoreState(state);
- return false;
- } else {
- i++;
- }
- } else {
- // The next parameter in p and q matches, so match their types.
- if (!performSubtypeConstraintGenerationInternal(
- q.sortedNamedParameters[j].type,
- p.sortedNamedParameters[i].type,
- leftSchema: !leftSchema,
- astNodeForTesting: astNodeForTesting)) {
- restoreState(state);
- return false;
- }
- i++;
- j++;
- }
- }
}
+ return true;
+ } else if (p.positionalParameterTypes.length ==
+ p.requiredPositionalParameterCount &&
+ q.positionalParameterTypes.length ==
+ q.requiredPositionalParameterCount &&
+ p.requiredPositionalParameterCount ==
+ q.requiredPositionalParameterCount &&
+ p.sortedNamedParameters.isNotEmpty &&
+ q.sortedNamedParameters.length <= p.sortedNamedParameters.length) {
+ // Function types with named parameters are treated analogously to the
+ // positional parameter case above.
+
+ final TypeConstraintGeneratorState state = currentState;
+
+ if (!performSubtypeConstraintGenerationInternal(
+ p.returnType, q.returnType,
+ leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
+ return false;
+ }
+ for (int i = 0; i < p.positionalParameterTypes.length; ++i) {
+ if (!performSubtypeConstraintGenerationInternal(
+ q.positionalParameterTypes[i], p.positionalParameterTypes[i],
+ leftSchema: !leftSchema, astNodeForTesting: astNodeForTesting)) {
+ restoreState(state);
+ return false;
+ }
+ }
+ // Consume parameter names from p and q in order. Since the named
+ // parameters in p and q are already sorted by name, we can do this by
+ // iterating through both lists in tandem.
+ int i = 0;
+ int j = 0;
+ while (true) {
+ // Determine whether the next parameter should be consumed from p,
+ // q, or both (because the next set of names matches). If the next
+ // parameter should be consumed from p, comparisonResult will be set
+ // to a value < 0. If the next parameter should be consumed from q,
+ // comparisonResult will be set to a value > 0. If the next
+ // parameter should be consumed from both, comparisonResult will be
+ // set to 0.
+ int comparisonResult;
+ if (i >= p.sortedNamedParameters.length) {
+ if (j >= q.sortedNamedParameters.length) {
+ // No parameters left.
+ return true;
+ } else {
+ // No more parameters in p, so the next parameter must come from
+ // q.
+ comparisonResult = 1;
+ }
+ } else if (j >= q.sortedNamedParameters.length) {
+ // No more parameters in q, so the next parameter must come from
+ // p.
+ comparisonResult = -1;
+ } else {
+ comparisonResult = p.sortedNamedParameters[i].name
+ .compareTo(q.sortedNamedParameters[j].name);
+ }
+ if (comparisonResult > 0) {
+ // Extra parameter in q that q that doesn't exist in p. No match.
+ restoreState(state);
+ return false;
+ } else if (comparisonResult < 0) {
+ // Extra parameter in p that doesn't exist in q. Ok if not
+ // required.
+ if (p.sortedNamedParameters[i].isRequired) {
+ restoreState(state);
+ return false;
+ } else {
+ i++;
+ }
+ } else {
+ // The next parameter in p and q matches, so match their types.
+ if (!performSubtypeConstraintGenerationInternal(
+ q.sortedNamedParameters[j].type, p.sortedNamedParameters[i].type,
+ leftSchema: !leftSchema, astNodeForTesting: astNodeForTesting)) {
+ restoreState(state);
+ return false;
+ }
+ i++;
+ j++;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /// Matches generic function type [p] against generic function type [q].
+ ///
+ /// See the documentation on
+ /// [performSubtypeConstraintGenerationForFunctionTypes] for details.
+ bool _handleGenericFunctionTypes(
+ SharedFunctionTypeStructure<TypeStructure, TypeParameterStructure,
+ FunctionParameterStructure>
+ p,
+ SharedFunctionTypeStructure<TypeStructure, TypeParameterStructure,
+ FunctionParameterStructure>
+ q,
+ {required bool leftSchema,
+ required AstNode? astNodeForTesting}) {
+ assert(p.typeFormals.isNotEmpty || q.typeFormals.isNotEmpty);
+ // A generic function type <T0 extends B00, ..., Tn extends B0n>F0 is a
+ // subtype match for a generic function type <S0 extends B10, ..., Sn
+ // extends B1n>F1 with respect to L under constraint set C2
+ //
+ // If B0i is a subtype match for B1i with constraint set Ci0. And B1i
+ // is a subtype match for B0i with constraint set Ci1. And Ci2 is Ci0
+ // + Ci1.
+ //
+ // And Z0...Zn are fresh variables with bounds B20, ..., B2n, Where B2i
+ // is B0i[Z0/T0, ..., Zn/Tn] if P is a type schema. Or B2i is
+ // B1i[Z0/S0, ..., Zn/Sn] if Q is a type schema. In other words, we
+ // choose the bounds for the fresh variables from whichever of the two
+ // generic function types is a type schema and does not contain any
+ // variables from L.
+ //
+ // And F0[Z0/T0, ..., Zn/Tn] is a subtype match for F1[Z0/S0, ...,
+ // Zn/Sn] with respect to L under constraints C0. And C1 is C02 + ...
+ // + Cn2 + C0. And C2 is C1 with each constraint replaced with its
+ // closure with respect to [Z0, ..., Zn].
+ if (p.typeFormals.length == q.typeFormals.length) {
+ final TypeConstraintGeneratorState state = currentState;
+
+ bool isMatch = true;
+ for (int i = 0; isMatch && i < p.typeFormals.length; ++i) {
+ isMatch = isMatch &&
+ performSubtypeConstraintGenerationInternal(
+ p.typeFormals[i].bound ??
+ typeAnalyzerOperations.objectQuestionType.unwrapTypeView(),
+ q.typeFormals[i].bound ??
+ typeAnalyzerOperations.objectQuestionType.unwrapTypeView(),
+ leftSchema: leftSchema,
+ astNodeForTesting: astNodeForTesting) &&
+ performSubtypeConstraintGenerationInternal(
+ q.typeFormals[i].bound ??
+ typeAnalyzerOperations.objectQuestionType.unwrapTypeView(),
+ p.typeFormals[i].bound ??
+ typeAnalyzerOperations.objectQuestionType.unwrapTypeView(),
+ leftSchema: !leftSchema,
+ astNodeForTesting: astNodeForTesting);
+ }
+ if (isMatch) {
+ var (
+ instantiatedP,
+ instantiatedQ,
+ typeParametersToEliminate: typeParametersToEliminate
+ ) = instantiateFunctionTypesAndProvideFreshTypeParameters(p, q,
+ leftSchema: leftSchema);
+
+ if (performSubtypeConstraintGenerationInternal(
+ instantiatedP, instantiatedQ,
+ leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
+ eliminateTypeParametersInGeneratedConstraints(
+ typeParametersToEliminate, state,
+ astNodeForTesting: astNodeForTesting);
+ return true;
+ }
+ }
+ restoreState(state);
}
return false;
@@ -1117,6 +1221,39 @@
return true;
}
+ /// Creates fresh type parameters, instantiates the non-generic parts of [p]
+ /// and [q] with the new parameters as type arguments, and returns the
+ /// instantiated non-generic function types and the eliminator that
+ /// eliminates the new type parameters.
+ ///
+ /// This operation is required as a part of the subtype constraint generation
+ /// algorithm, in the step for the generic function types. See
+ /// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#subtype-constraint-generation.
+ (
+ TypeStructure,
+ TypeStructure, {
+ List<TypeParameterStructure> typeParametersToEliminate
+ }) instantiateFunctionTypesAndProvideFreshTypeParameters(
+ SharedFunctionTypeStructure<TypeStructure, TypeParameterStructure,
+ FunctionParameterStructure>
+ p,
+ SharedFunctionTypeStructure<TypeStructure, TypeParameterStructure,
+ FunctionParameterStructure>
+ q,
+ {required bool leftSchema});
+
+ /// Iterates over all of the type constraints generated since
+ /// [eliminationStartState] and eliminates the type variables in them using
+ /// [typeParametersToEliminate].
+ ///
+ /// This step is required as a part of the subtype constraint generation
+ /// algorithm, in the step for generic function types. See
+ /// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#subtype-constraint-generation.
+ void eliminateTypeParametersInGeneratedConstraints(
+ List<TypeParameterStructure> typeParametersToEliminate,
+ TypeConstraintGeneratorState eliminationStartState,
+ {required AstNode? astNodeForTesting});
+
/// Matches [p] against [q].
///
/// If [q] is of the form `FutureOr<q0>` for some `q0`, and [p] is a subtype
diff --git a/pkg/_fe_analyzer_shared/lib/src/types/shared_type.dart b/pkg/_fe_analyzer_shared/lib/src/types/shared_type.dart
index 1bd0d4e..df4d48f 100644
--- a/pkg/_fe_analyzer_shared/lib/src/types/shared_type.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/types/shared_type.dart
@@ -83,6 +83,9 @@
TypeStructure extends SharedTypeStructure<TypeStructure>> {
/// The name of the type parameter, for display to the user.
String get displayName;
+
+ /// The bound of the type parameter.
+ TypeStructure? get bound;
}
/// Common interface for data structures used by the implementations to
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index d911205..1aa1f53 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -3281,6 +3281,13 @@
// TODO(paulberry): Implement greatest closure of types in mini ast.
throw UnimplementedError();
}
+
+ @override
+ Type leastClosureOfTypeInternal(Type type,
+ List<SharedTypeParameterStructure<Type>> typeParametersToEliminate) {
+ // TODO(paulberry): Implement greatest closure of types in mini ast.
+ throw UnimplementedError();
+ }
}
/// Representation of an expression or statement in the pseudo-Dart language
diff --git a/pkg/_fe_analyzer_shared/test/mini_types.dart b/pkg/_fe_analyzer_shared/test/mini_types.dart
index d0ce5ea..e431ec1 100644
--- a/pkg/_fe_analyzer_shared/test/mini_types.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_types.dart
@@ -513,6 +513,7 @@
class TypeParameter extends TypeNameInfo
implements SharedTypeParameterStructure<Type> {
/// The type variable's bound. Defaults to `Object?`.
+ @override
Type bound;
TypeParameter._(super.name) : bound = Type('Object?');
diff --git a/pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart b/pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart
index 856f02b..ca7620d 100644
--- a/pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart
+++ b/pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart
@@ -879,4 +879,26 @@
{required Node? nodeForTesting}) {
_constraints.add('$lower <: $typeParameter');
}
+
+ @override
+ void eliminateTypeParametersInGeneratedConstraints(
+ Object eliminator, TypeConstraintGeneratorState eliminationStartState,
+ {required Node? astNodeForTesting}) {
+ // TODO(paulberry): implement eliminateTypeParametersInGeneratedConstraints
+ }
+
+ @override
+ (
+ Type,
+ Type, {
+ List<TypeParameter> typeParametersToEliminate
+ }) instantiateFunctionTypesAndProvideFreshTypeParameters(
+ SharedFunctionTypeStructure<Type, TypeParameter, NamedFunctionParameter>
+ p,
+ SharedFunctionTypeStructure<Type, TypeParameter, NamedFunctionParameter>
+ q,
+ {required bool leftSchema}) {
+ // TODO(paulberry): implement instantiateFunctionTypesAndProvideEliminator
+ throw UnimplementedError();
+ }
}
diff --git a/pkg/analyzer/lib/dart/element/element.dart b/pkg/analyzer/lib/dart/element/element.dart
index 91c70e2..589d6eb 100644
--- a/pkg/analyzer/lib/dart/element/element.dart
+++ b/pkg/analyzer/lib/dart/element/element.dart
@@ -2517,6 +2517,7 @@
/// if this parameter does not have an explicit bound. Being able to
/// distinguish between an implicit and explicit bound is needed by the
/// instantiate to bounds algorithm.
+ @override
DartType? get bound;
@override
diff --git a/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart b/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
index 03ba563..4870ac3 100644
--- a/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
+++ b/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
@@ -131,6 +131,30 @@
}
@override
+ void eliminateTypeParametersInGeneratedConstraints(
+ covariant List<TypeParameterElement> eliminator,
+ shared.TypeConstraintGeneratorState eliminationStartState,
+ {required AstNode? astNodeForTesting}) {
+ var constraints = _constraints.sublist(eliminationStartState.count);
+ _constraints.length = eliminationStartState.count;
+ for (var constraint in constraints) {
+ if (constraint.isUpper) {
+ addUpperConstraintForParameter(
+ constraint.typeParameter,
+ typeAnalyzerOperations.leastClosureOfTypeInternal(
+ constraint.constraint.unwrapTypeSchemaView(), eliminator),
+ nodeForTesting: astNodeForTesting);
+ } else {
+ addLowerConstraintForParameter(
+ constraint.typeParameter,
+ typeAnalyzerOperations.greatestClosureOfTypeInternal(
+ constraint.constraint.unwrapTypeSchemaView(), eliminator),
+ nodeForTesting: astNodeForTesting);
+ }
+ }
+ }
+
+ @override
List<DartType>? getTypeArgumentsAsInstanceOf(
InterfaceType type, InterfaceElement typeDeclaration) {
for (var interface in type.element.allSupertypes) {
@@ -145,6 +169,43 @@
}
@override
+ (DartType, DartType, {List<TypeParameterElement> typeParametersToEliminate})
+ instantiateFunctionTypesAndProvideFreshTypeParameters(
+ covariant FunctionType P, covariant FunctionType Q,
+ {required bool leftSchema}) {
+ // And `Z0...Zn` are fresh variables with bounds `B20, ..., B2n`.
+ // Where `B2i` is `B0i[Z0/T0, ..., Zn/Tn]` if `P` is a type schema.
+ // Or `B2i` is `B1i[Z0/S0, ..., Zn/Sn]` if `Q` is a type schema.
+ // In other words, we choose the bounds for the fresh variables from
+ // whichever of the two generic function types is a type schema and does
+ // not contain any variables from `L`.
+ var newTypeParameters = <TypeParameterElement>[];
+ for (var i = 0; i < P.typeFormals.length; i++) {
+ var Z = TypeParameterElementImpl('Z$i', -1);
+ if (leftSchema) {
+ Z.bound = P.typeFormals[i].bound;
+ } else {
+ Z.bound = Q.typeFormals[i].bound;
+ }
+ newTypeParameters.add(Z);
+ }
+
+ // And `F0[Z0/T0, ..., Zn/Tn]` is a subtype match for
+ // `F1[Z0/S0, ..., Zn/Sn]` with respect to `L` under constraints `C0`.
+ var typeArguments = newTypeParameters
+ .map((e) => e.instantiate(nullabilitySuffix: NullabilitySuffix.none))
+ .toList();
+ var P_instantiated = P.instantiate(typeArguments);
+ var Q_instantiated = Q.instantiate(typeArguments);
+
+ return (
+ P_instantiated,
+ Q_instantiated,
+ typeParametersToEliminate: newTypeParameters
+ );
+ }
+
+ @override
bool performSubtypeConstraintGenerationInternal(DartType p, DartType q,
{required bool leftSchema, required AstNode? astNodeForTesting}) {
return trySubtypeMatch(p, q, leftSchema, nodeForTesting: astNodeForTesting);
@@ -299,8 +360,9 @@
}
}
- if (P is FunctionType && Q is FunctionType) {
- return _functionType(P, Q, leftSchema, nodeForTesting: nodeForTesting);
+ if (performSubtypeConstraintGenerationForFunctionTypes(P, Q,
+ leftSchema: leftSchema, astNodeForTesting: nodeForTesting)) {
+ return true;
}
// A type `P` is a subtype match for `Record` with respect to `L` under no
@@ -319,96 +381,6 @@
return false;
}
-
- /// Matches [P] against [Q], where [P] and [Q] are both function types.
- ///
- /// If [P] is a subtype of [Q] under some constraints, the constraints making
- /// the relation possible are recorded to [_constraints], and `true` is
- /// returned. Otherwise, [_constraints] is left unchanged (or rolled back),
- /// and `false` is returned.
- bool _functionType(FunctionType P, FunctionType Q, bool leftSchema,
- {required AstNode? nodeForTesting}) {
- if (P.nullabilitySuffix != NullabilitySuffix.none) {
- return false;
- }
-
- if (Q.nullabilitySuffix != NullabilitySuffix.none) {
- return false;
- }
-
- var P_typeFormals = P.typeFormals;
- var Q_typeFormals = Q.typeFormals;
- if (P_typeFormals.length != Q_typeFormals.length) {
- return false;
- }
-
- if (P_typeFormals.isEmpty && Q_typeFormals.isEmpty) {
- return performSubtypeConstraintGenerationForFunctionTypes(P, Q,
- leftSchema: leftSchema, astNodeForTesting: nodeForTesting);
- }
-
- // We match two generic function types:
- // `<T0 extends B00, ..., Tn extends B0n>F0`
- // `<S0 extends B10, ..., Sn extends B1n>F1`
- // with respect to `L` under constraint set `C2`:
- var rewind = _constraints.length;
-
- // If `B0i` is a subtype match for `B1i` with constraint set `Ci0`.
- // If `B1i` is a subtype match for `B0i` with constraint set `Ci1`.
- // And `Ci2` is `Ci0 + Ci1`.
- for (var i = 0; i < P_typeFormals.length; i++) {
- var B0 = P_typeFormals[i].bound ?? _typeSystem.objectQuestion;
- var B1 = Q_typeFormals[i].bound ?? _typeSystem.objectQuestion;
- if (!trySubtypeMatch(B0, B1, leftSchema,
- nodeForTesting: nodeForTesting)) {
- _constraints.length = rewind;
- return false;
- }
- if (!trySubtypeMatch(B1, B0, !leftSchema,
- nodeForTesting: nodeForTesting)) {
- _constraints.length = rewind;
- return false;
- }
- }
-
- // And `Z0...Zn` are fresh variables with bounds `B20, ..., B2n`.
- // Where `B2i` is `B0i[Z0/T0, ..., Zn/Tn]` if `P` is a type schema.
- // Or `B2i` is `B1i[Z0/S0, ..., Zn/Sn]` if `Q` is a type schema.
- // In other words, we choose the bounds for the fresh variables from
- // whichever of the two generic function types is a type schema and does
- // not contain any variables from `L`.
- var newTypeParameters = <TypeParameterElement>[];
- for (var i = 0; i < P_typeFormals.length; i++) {
- var Z = TypeParameterElementImpl('Z$i', -1);
- if (leftSchema) {
- Z.bound = P_typeFormals[i].bound;
- } else {
- Z.bound = Q_typeFormals[i].bound;
- }
- newTypeParameters.add(Z);
- }
-
- // And `F0[Z0/T0, ..., Zn/Tn]` is a subtype match for
- // `F1[Z0/S0, ..., Zn/Sn]` with respect to `L` under constraints `C0`.
- var typeArguments = newTypeParameters
- .map((e) => e.instantiate(nullabilitySuffix: NullabilitySuffix.none))
- .toList();
- var P_instantiated = P.instantiate(typeArguments);
- var Q_instantiated = Q.instantiate(typeArguments);
- if (!performSubtypeConstraintGenerationForFunctionTypes(
- P_instantiated, Q_instantiated,
- leftSchema: leftSchema, astNodeForTesting: nodeForTesting)) {
- _constraints.length = rewind;
- return false;
- }
-
- // And `C1` is `C02 + ... + Cn2 + C0`.
- // And `C2` is `C1` with each constraint replaced with its closure
- // with respect to `[Z0, ..., Zn]`.
- // TODO(scheglov): do closure
-
- return true;
- }
}
/// Data structure maintaining intermediate type inference results, such as
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
index ce05521..5ab3ade 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
@@ -520,7 +520,6 @@
@override
DartType greatestClosureOfTypeInternal(DartType type,
List<SharedTypeParameterStructure<DartType>> typeParametersToEliminate) {
- typeParametersToEliminate.first;
return typeSystem.greatestClosure(
type, typeParametersToEliminate.cast<TypeParameterElement>());
}
@@ -625,6 +624,13 @@
}
@override
+ DartType leastClosureOfTypeInternal(DartType type,
+ List<SharedTypeParameterStructure<DartType>> typeParametersToEliminate) {
+ return typeSystem.leastClosure(
+ type, typeParametersToEliminate.cast<TypeParameterElement>());
+ }
+
+ @override
DartType listTypeInternal(DartType elementType) {
return typeSystem.typeProvider.listType(elementType);
}
diff --git a/pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart b/pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart
index cc7f701..1eacba7 100644
--- a/pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart
+++ b/pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart
@@ -73,6 +73,72 @@
@override
OperationsCfe get typeAnalyzerOperations => typeOperations;
+ @override
+ (DartType, DartType, {List<StructuralParameter> typeParametersToEliminate})
+ instantiateFunctionTypesAndProvideFreshTypeParameters(
+ covariant FunctionType p, covariant FunctionType q,
+ {required bool leftSchema}) {
+ FunctionType instantiatedP;
+ FunctionType instantiatedQ;
+ if (leftSchema) {
+ List<DartType> typeParametersForAlphaRenaming =
+ new List<DartType>.generate(
+ p.typeFormals.length,
+ (int i) => new StructuralParameterType.forAlphaRenaming(
+ q.typeParameters[i], p.typeParameters[i]));
+ instantiatedP = p.withoutTypeParameters;
+ instantiatedQ = FunctionTypeInstantiator.instantiate(
+ q, typeParametersForAlphaRenaming);
+ } else {
+ // Coverage-ignore-block(suite): Not run.
+ List<DartType> typeParametersForAlphaRenaming =
+ new List<DartType>.generate(
+ p.typeFormals.length,
+ (int i) => new StructuralParameterType.forAlphaRenaming(
+ p.typeParameters[i], q.typeParameters[i]));
+ instantiatedP = FunctionTypeInstantiator.instantiate(
+ p, typeParametersForAlphaRenaming);
+ instantiatedQ = q.withoutTypeParameters;
+ }
+
+ return (
+ instantiatedP,
+ instantiatedQ,
+ typeParametersToEliminate: leftSchema
+ ? p.typeParameters
+ :
+ // Coverage-ignore(suite): Not run.
+ q.typeParameters
+ );
+ }
+
+ @override
+ void eliminateTypeParametersInGeneratedConstraints(
+ covariant List<StructuralParameter> typeParametersToEliminate,
+ shared.TypeConstraintGeneratorState eliminationStartState,
+ {required TreeNode? astNodeForTesting}) {
+ List<GeneratedTypeConstraint> constraints =
+ _protoConstraints.sublist(eliminationStartState.count);
+ _protoConstraints.length = eliminationStartState.count;
+ for (GeneratedTypeConstraint constraint in constraints) {
+ if (constraint.isUpper) {
+ addUpperConstraintForParameter(
+ constraint.typeParameter,
+ typeOperations.leastClosureOfTypeInternal(
+ constraint.constraint.unwrapTypeSchemaView(),
+ typeParametersToEliminate),
+ nodeForTesting: astNodeForTesting);
+ } else {
+ addLowerConstraintForParameter(
+ constraint.typeParameter,
+ typeOperations.greatestClosureOfTypeInternal(
+ constraint.constraint.unwrapTypeSchemaView(),
+ typeParametersToEliminate),
+ nodeForTesting: astNodeForTesting);
+ }
+ }
+ }
+
/// Applies all the argument constraints implied by trying to make
/// [actualTypes] assignable to [formalTypes].
void constrainArguments(
@@ -410,93 +476,6 @@
return true;
}
- // A generic function type <T0 extends B00, ..., Tn extends B0n>F0 is a
- // subtype match for a generic function type <S0 extends B10, ..., Sn
- // extends B1n>F1 with respect to L under constraint set C2
- //
- // If B0i is a subtype match for B1i with constraint set Ci0. And B1i is a
- // subtype match for B0i with constraint set Ci1. And Ci2 is Ci0 + Ci1.
- //
- // And Z0...Zn are fresh variables with bounds B20, ..., B2n, Where B2i is
- // B0i[Z0/T0, ..., Zn/Tn] if P is a type schema. Or B2i is B1i[Z0/S0, ...,
- // Zn/Sn] if Q is a type schema. In other words, we choose the bounds for
- // the fresh variables from whichever of the two generic function types is a
- // type schema and does not contain any variables from L.
- //
- // And F0[Z0/T0, ..., Zn/Tn] is a subtype match for F1[Z0/S0, ..., Zn/Sn]
- // with respect to L under constraints C0. And C1 is C02 + ... + Cn2 + C0.
- // And C2 is C1 with each constraint replaced with its closure with respect
- // to [Z0, ..., Zn].
- if (p is FunctionType &&
- q is FunctionType &&
- p.typeParameters.isNotEmpty &&
- q.typeParameters.isNotEmpty &&
- p.typeParameters.length == q.typeParameters.length) {
- final int baseConstraintCount = _protoConstraints.length;
-
- bool isMatch = true;
- for (int i = 0; isMatch && i < p.typeParameters.length; ++i) {
- isMatch = isMatch &&
- _isNullabilityAwareSubtypeMatch(
- p.typeParameters[i].bound, q.typeParameters[i].bound,
- constrainSupertype: constrainSupertype,
- treeNodeForTesting: treeNodeForTesting) &&
- _isNullabilityAwareSubtypeMatch(
- q.typeParameters[i].bound, p.typeParameters[i].bound,
- constrainSupertype: !constrainSupertype,
- treeNodeForTesting: treeNodeForTesting);
- }
- if (isMatch) {
- List<DartType> typeParametersOfPAsTypesForQ =
- new List<DartType>.generate(
- p.typeParameters.length,
- (int i) => new StructuralParameterType.forAlphaRenaming(
- q.typeParameters[i], p.typeParameters[i]));
- FunctionType instantiatedP = p.withoutTypeParameters;
- FunctionType instantiatedQ = FunctionTypeInstantiator.instantiate(
- q, typeParametersOfPAsTypesForQ);
- if (_isNullabilityAwareSubtypeMatch(instantiatedP, instantiatedQ,
- constrainSupertype: constrainSupertype,
- treeNodeForTesting: treeNodeForTesting)) {
- List<GeneratedTypeConstraint> constraints =
- _protoConstraints.sublist(baseConstraintCount);
- _protoConstraints.length = baseConstraintCount;
- NullabilityAwareTypeParameterEliminator eliminator =
- new NullabilityAwareTypeParameterEliminator(
- structuralEliminationTargets: p.typeParameters.toSet(),
- nominalEliminationTargets: {},
- bottomType: typeOperations.neverType.unwrapTypeView(),
- topType: typeOperations.objectQuestionType.unwrapTypeView(),
- topFunctionType:
- _environment.coreTypes.functionNonNullableRawType,
- unhandledTypeHandler:
- // Coverage-ignore(suite): Not run.
- (DartType type, ignored) => type is UnknownType
- ? false
- : throw new UnsupportedError(
- "Unsupported type '${type.runtimeType}'."));
- for (GeneratedTypeConstraint constraint in constraints) {
- if (constraint.isUpper) {
- addUpperConstraintForParameter(
- constraint.typeParameter,
- eliminator.eliminateToLeast(
- constraint.constraint.unwrapTypeSchemaView()),
- nodeForTesting: treeNodeForTesting);
- } else {
- addLowerConstraintForParameter(
- constraint.typeParameter,
- eliminator.eliminateToGreatest(
- constraint.constraint.unwrapTypeSchemaView()),
- nodeForTesting: treeNodeForTesting);
- }
- }
- return true;
- }
- }
- // Coverage-ignore-block(suite): Not run.
- _protoConstraints.length = baseConstraintCount;
- }
-
// A type P is a subtype match for Record with respect to L under no
// constraints:
//
diff --git a/pkg/front_end/lib/src/type_inference/type_inference_engine.dart b/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
index b833a66..d8a842a 100644
--- a/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
+++ b/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
@@ -996,6 +996,17 @@
.functionRawType(Nullability.nonNullable))
.eliminateToGreatest(type);
}
+
+ @override
+ DartType leastClosureOfTypeInternal(DartType type,
+ List<SharedTypeParameterStructure<DartType>> typeParametersToEliminate) {
+ return new NullabilityAwareFreeTypeParameterEliminator(
+ bottomType: const NeverType.nonNullable(),
+ topType: typeEnvironment.coreTypes.objectNullableRawType,
+ topFunctionType: typeEnvironment.coreTypes
+ .functionRawType(Nullability.nonNullable))
+ .eliminateToLeast(type);
+ }
}
/// Type inference results used for testing.
diff --git a/pkg/front_end/test/coverage_suite_expected.dart b/pkg/front_end/test/coverage_suite_expected.dart
index 2f65fae..68865bc 100644
--- a/pkg/front_end/test/coverage_suite_expected.dart
+++ b/pkg/front_end/test/coverage_suite_expected.dart
@@ -560,7 +560,7 @@
),
// 100.0%.
"package:front_end/src/kernel/body_builder.dart": (
- hitCount: 7120,
+ hitCount: 7121,
missCount: 0,
),
// 100.0%.
@@ -1036,7 +1036,7 @@
),
// 100.0%.
"package:front_end/src/type_inference/type_constraint_gatherer.dart": (
- hitCount: 311,
+ hitCount: 189,
missCount: 0,
),
// 100.0%.
@@ -1046,7 +1046,7 @@
),
// 100.0%.
"package:front_end/src/type_inference/type_inference_engine.dart": (
- hitCount: 521,
+ hitCount: 530,
missCount: 0,
),
// 100.0%.
diff --git a/pkg/kernel/lib/src/ast/types.dart b/pkg/kernel/lib/src/ast/types.dart
index 3073854..9587ed2 100644
--- a/pkg/kernel/lib/src/ast/types.dart
+++ b/pkg/kernel/lib/src/ast/types.dart
@@ -246,6 +246,7 @@
/// This is set to [unsetBoundSentinel] temporarily during IR construction.
/// This is set to the `Object?` for type parameters without an explicit
/// bound.
+ @override
DartType bound;
/// Sentinel value used for the [defaultType] that has not yet been computed.