[analyzer][cfe] Share constraint generation for declaration types
Part of https://github.com/dart-lang/sdk/issues/54902 Change-Id: Id83077113319031b3e11061110ce0b1beb45918a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/382562 Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
committed by
Commit Queue
parent
eff0ece338
commit
59cb100195
@@ -1220,6 +1220,146 @@ abstract class TypeConstraintGenerator<
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Matches [p] against [q] as a subtype against supertype and returns true if
|
||||
/// [p] and [q] are both type declaration types as defined by the enum
|
||||
/// [TypeDeclarationKind], and [p] is a subtype of [q] under some constraints
|
||||
/// imposed on type parameters occurring in [q], and false otherwise.
|
||||
///
|
||||
/// 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 `_`.
|
||||
///
|
||||
/// As the generator computes the constraints making the relation possible, it
|
||||
/// changes its internal state. The current state of the generator can be
|
||||
/// obtained by [currentState], and the generator can be restored to a state
|
||||
/// via [restoreState]. All of the shared constraint generation methods are
|
||||
/// supposed to restore the generator to the prior state in case of a
|
||||
/// mismatch, taking that responsibility away from the caller.
|
||||
bool? performSubtypeConstraintGenerationForTypeDeclarationTypes(
|
||||
TypeStructure p, TypeStructure q,
|
||||
{required bool leftSchema, required AstNode? astNodeForTesting}) {
|
||||
switch ((
|
||||
typeAnalyzerOperations.matchTypeDeclarationType(new SharedTypeView(p)),
|
||||
typeAnalyzerOperations.matchTypeDeclarationType(new SharedTypeView(q))
|
||||
)) {
|
||||
// If `P` is `C<M0, ..., Mk> and `Q` is `C<N0, ..., Nk>`, then the match
|
||||
// holds under constraints `C0 + ... + Ck`:
|
||||
// If `Mi` is a subtype match for `Ni` with respect to L under
|
||||
// constraints `Ci`.
|
||||
case (
|
||||
TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: TypeDeclarationKind pTypeDeclarationKind,
|
||||
typeDeclaration: TypeDeclaration pDeclarationObject,
|
||||
typeArguments: List<TypeStructure> pTypeArguments
|
||||
),
|
||||
TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: TypeDeclarationKind qTypeDeclarationKind,
|
||||
typeDeclaration: TypeDeclaration qDeclarationObject,
|
||||
typeArguments: List<TypeStructure> qTypeArguments
|
||||
)
|
||||
)
|
||||
when pTypeDeclarationKind == qTypeDeclarationKind &&
|
||||
pDeclarationObject == qDeclarationObject:
|
||||
return _interfaceTypeArguments(
|
||||
pDeclarationObject, pTypeArguments, qTypeArguments, leftSchema,
|
||||
astNodeForTesting: astNodeForTesting);
|
||||
|
||||
case (TypeDeclarationMatchResult(), TypeDeclarationMatchResult()):
|
||||
return _interfaceTypes(p, q, leftSchema,
|
||||
astNodeForTesting: astNodeForTesting);
|
||||
|
||||
case (
|
||||
TypeDeclarationMatchResult? pMatched,
|
||||
TypeDeclarationMatchResult? qMatched
|
||||
):
|
||||
assert(pMatched == null || qMatched == null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Match arguments [pTypeArguments] of P against arguments [qTypeArguments]
|
||||
/// of Q, taking into account the variance of type variables in [declaration].
|
||||
/// If returns `false`, the constraints are unchanged.
|
||||
bool _interfaceTypeArguments(
|
||||
TypeDeclaration declaration,
|
||||
List<TypeStructure> pTypeArguments,
|
||||
List<TypeStructure> qTypeArguments,
|
||||
bool leftSchema,
|
||||
{required AstNode? astNodeForTesting}) {
|
||||
assert(pTypeArguments.length == qTypeArguments.length);
|
||||
|
||||
final TypeConstraintGeneratorState state = currentState;
|
||||
|
||||
for (int i = 0; i < pTypeArguments.length; i++) {
|
||||
Variance variance =
|
||||
typeAnalyzerOperations.getTypeParameterVariance(declaration, i);
|
||||
TypeStructure M = pTypeArguments[i];
|
||||
TypeStructure N = qTypeArguments[i];
|
||||
if ((variance == Variance.covariant || variance == Variance.invariant) &&
|
||||
!performSubtypeConstraintGenerationInternal(M, N,
|
||||
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
|
||||
restoreState(state);
|
||||
return false;
|
||||
}
|
||||
if ((variance == Variance.contravariant ||
|
||||
variance == Variance.invariant) &&
|
||||
!performSubtypeConstraintGenerationInternal(N, M,
|
||||
leftSchema: !leftSchema, astNodeForTesting: astNodeForTesting)) {
|
||||
restoreState(state);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _interfaceTypes(TypeStructure p, TypeStructure q, bool leftSchema,
|
||||
{required AstNode? astNodeForTesting}) {
|
||||
if (p.nullabilitySuffix != NullabilitySuffix.none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (q.nullabilitySuffix != NullabilitySuffix.none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If `P` is `C0<M0, ..., Mk>` and `Q` is `C1<N0, ..., Nj>` then the match
|
||||
// holds with respect to `L` under constraints `C`:
|
||||
// If `C1<B0, ..., Bj>` is a superinterface of `C0<M0, ..., Mk>` and
|
||||
// `C1<B0, ..., Bj>` is a subtype match for `C1<N0, ..., Nj>` with
|
||||
// respect to `L` under constraints `C`.
|
||||
|
||||
if ((
|
||||
typeAnalyzerOperations.matchTypeDeclarationType(new SharedTypeView(p)),
|
||||
typeAnalyzerOperations.matchTypeDeclarationType(new SharedTypeView(q))
|
||||
)
|
||||
case (
|
||||
TypeDeclarationMatchResult(
|
||||
typeDeclarationType: TypeDeclarationType pTypeDeclarationType
|
||||
),
|
||||
TypeDeclarationMatchResult(
|
||||
typeDeclaration: TypeDeclaration qTypeDeclaration,
|
||||
typeArguments: List<TypeStructure> qTypeArguments
|
||||
)
|
||||
)) {
|
||||
if (getTypeArgumentsAsInstanceOf(pTypeDeclarationType, qTypeDeclaration)
|
||||
case List<TypeStructure> typeArguments) {
|
||||
return _interfaceTypeArguments(
|
||||
qTypeDeclaration, typeArguments, qTypeArguments, leftSchema,
|
||||
astNodeForTesting: astNodeForTesting);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns the type arguments of the supertype of [type] that is an
|
||||
/// instantiation of [typeDeclaration]. If none of the supertypes of [type]
|
||||
/// are instantiations of [typeDeclaration], returns null.
|
||||
List<TypeStructure>? getTypeArgumentsAsInstanceOf(
|
||||
TypeDeclarationType type, TypeDeclaration typeDeclaration);
|
||||
}
|
||||
|
||||
/// Representation of the state of [TypeConstraintGenerator].
|
||||
|
||||
@@ -7,10 +7,7 @@ import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.
|
||||
show
|
||||
TypeConstraintGenerator,
|
||||
TypeConstraintGeneratorMixin,
|
||||
TypeConstraintGeneratorState,
|
||||
TypeDeclarationKind,
|
||||
TypeDeclarationMatchResult,
|
||||
Variance;
|
||||
TypeConstraintGeneratorState;
|
||||
import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart';
|
||||
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
@@ -93,6 +90,20 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
List<DartType>? getTypeArgumentsAsInstanceOf(
|
||||
InterfaceType type, InterfaceElement typeDeclaration) {
|
||||
for (var interface in type.element.allSupertypes) {
|
||||
if (interface.element == typeDeclaration) {
|
||||
var substitution = Substitution.fromInterfaceType(type);
|
||||
var substitutedInterface =
|
||||
substitution.substituteType(interface) as InterfaceType;
|
||||
return substitutedInterface.typeArguments;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
bool performSubtypeConstraintGenerationInternal(DartType p, DartType q,
|
||||
{required bool leftSchema, required AstNode? astNodeForTesting}) {
|
||||
@@ -291,52 +302,11 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
|
||||
_constraints.length = rewind;
|
||||
}
|
||||
|
||||
switch ((
|
||||
_typeSystemOperations.matchTypeDeclarationType(SharedTypeView(P)),
|
||||
_typeSystemOperations.matchTypeDeclarationType(SharedTypeView(Q))
|
||||
)) {
|
||||
// If `P` is `C<M0, ..., Mk> and `Q` is `C<N0, ..., Nk>`, then the match
|
||||
// holds under constraints `C0 + ... + Ck`:
|
||||
// If `Mi` is a subtype match for `Ni` with respect to L under
|
||||
// constraints `Ci`.
|
||||
case (
|
||||
shared.TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: shared.TypeDeclarationKind
|
||||
P_typeDeclarationKind,
|
||||
typeDeclaration: InterfaceElement P_declarationObject,
|
||||
typeDeclarationType: InterfaceType _,
|
||||
typeArguments: List<DartType> P_typeArguments
|
||||
),
|
||||
shared.TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: shared.TypeDeclarationKind
|
||||
Q_typeDeclarationKind,
|
||||
typeDeclaration: InterfaceElement Q_declarationObject,
|
||||
typeDeclarationType: InterfaceType _,
|
||||
typeArguments: List<DartType> Q_typeArguments
|
||||
)
|
||||
)
|
||||
when P_typeDeclarationKind == Q_typeDeclarationKind &&
|
||||
P_declarationObject == Q_declarationObject:
|
||||
return _interfaceType_arguments(P_declarationObject, P_typeArguments,
|
||||
Q_declarationObject, Q_typeArguments, leftSchema,
|
||||
nodeForTesting: nodeForTesting);
|
||||
|
||||
case (
|
||||
shared.TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: shared.TypeDeclarationKind _,
|
||||
typeDeclaration: InterfaceElement _,
|
||||
typeDeclarationType: InterfaceType P_interfaceType,
|
||||
typeArguments: List<DartType> _
|
||||
),
|
||||
shared.TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: shared.TypeDeclarationKind _,
|
||||
typeDeclaration: InterfaceElement _,
|
||||
typeDeclarationType: InterfaceType Q_interfaceType,
|
||||
typeArguments: List<DartType> _
|
||||
)
|
||||
):
|
||||
return _interfaceType(P_interfaceType, Q_interfaceType, leftSchema,
|
||||
nodeForTesting: nodeForTesting);
|
||||
bool? result = performSubtypeConstraintGenerationForTypeDeclarationTypes(
|
||||
P, Q,
|
||||
leftSchema: leftSchema, astNodeForTesting: nodeForTesting);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// If `Q` is `Function` then the match holds under no constraints:
|
||||
@@ -584,77 +554,6 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _interfaceType(InterfaceType P, InterfaceType Q, bool leftSchema,
|
||||
{required AstNode? nodeForTesting}) {
|
||||
if (P.nullabilitySuffix != NullabilitySuffix.none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Q.nullabilitySuffix != NullabilitySuffix.none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If `P` is `C0<M0, ..., Mk>` and `Q` is `C1<N0, ..., Nj>` then the match
|
||||
// holds with respect to `L` under constraints `C`:
|
||||
// If `C1<B0, ..., Bj>` is a superinterface of `C0<M0, ..., Mk>` and
|
||||
// `C1<B0, ..., Bj>` is a subtype match for `C1<N0, ..., Nj>` with
|
||||
// respect to `L` under constraints `C`.
|
||||
var C0 = P.element;
|
||||
var C1 = Q.element;
|
||||
for (var interface in C0.allSupertypes) {
|
||||
if (interface.element == C1) {
|
||||
var substitution = Substitution.fromInterfaceType(P);
|
||||
var substitutedInterface =
|
||||
substitution.substituteType(interface) as InterfaceType;
|
||||
return _interfaceType_arguments(
|
||||
substitutedInterface.element,
|
||||
substitutedInterface.typeArguments,
|
||||
Q.element,
|
||||
Q.typeArguments,
|
||||
leftSchema,
|
||||
nodeForTesting: nodeForTesting);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Match arguments [P_typeArguments] of P against arguments [Q_typeArguments]
|
||||
/// of Q, taking into account the variance of type variables in [P_element]
|
||||
/// and [Q_element]. If returns `false`, the constraints are unchanged.
|
||||
bool _interfaceType_arguments(
|
||||
InterfaceElement P_element,
|
||||
List<DartType> P_typeArguments,
|
||||
InterfaceElement Q_element,
|
||||
List<DartType> Q_typeArguments,
|
||||
bool leftSchema,
|
||||
{required AstNode? nodeForTesting}) {
|
||||
assert(P_typeArguments.length == Q_typeArguments.length);
|
||||
|
||||
var rewind = _constraints.length;
|
||||
|
||||
for (var i = 0; i < P_typeArguments.length; i++) {
|
||||
var variance =
|
||||
_typeSystemOperations.getTypeParameterVariance(P_element, i);
|
||||
var M = P_typeArguments[i];
|
||||
var N = Q_typeArguments[i];
|
||||
if ((variance == shared.Variance.covariant ||
|
||||
variance == shared.Variance.invariant) &&
|
||||
!trySubtypeMatch(M, N, leftSchema, nodeForTesting: nodeForTesting)) {
|
||||
_constraints.length = rewind;
|
||||
return false;
|
||||
}
|
||||
if ((variance == shared.Variance.contravariant ||
|
||||
variance == shared.Variance.invariant) &&
|
||||
!trySubtypeMatch(N, M, leftSchema, nodeForTesting: nodeForTesting)) {
|
||||
_constraints.length = rewind;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// If `P` is `(M0, ..., Mk)` and `Q` is `(N0, ..., Nk)`, then the match
|
||||
/// holds under constraints `C0 + ... + Ck`:
|
||||
/// If `Mi` is a subtype match for `Ni` with respect to L under
|
||||
|
||||
@@ -10,8 +10,6 @@ import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.
|
||||
TypeConstraintGenerator,
|
||||
TypeConstraintGeneratorMixin,
|
||||
TypeConstraintGeneratorState,
|
||||
TypeDeclarationKind,
|
||||
TypeDeclarationMatchResult,
|
||||
Variance;
|
||||
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
@@ -95,6 +93,7 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
|
||||
.getInterfaceMember(class_, name, setter: setter);
|
||||
}
|
||||
|
||||
@override
|
||||
List<DartType>? getTypeArgumentsAsInstanceOf(
|
||||
TypeDeclarationType type, TypeDeclaration typeDeclaration) {
|
||||
return _environment.getTypeArgumentsAsInstanceOf(type, typeDeclaration);
|
||||
@@ -690,98 +689,11 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
|
||||
_protoConstraints.length = baseConstraintCount;
|
||||
}
|
||||
|
||||
switch ((
|
||||
typeOperations.matchTypeDeclarationType(new SharedTypeView(p)),
|
||||
typeOperations.matchTypeDeclarationType(new SharedTypeView(q))
|
||||
)) {
|
||||
// If P is C<M0, ..., Mk> and Q is C<N0, ..., Nk>, then the match holds
|
||||
// under constraints C0 + ... + Ck:
|
||||
//
|
||||
// If Mi is a subtype match for Ni with respect to L under constraints Ci.
|
||||
case (
|
||||
shared.TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: shared.TypeDeclarationKind pDeclarationKind,
|
||||
typeDeclaration: TypeDeclaration pDeclarationObject,
|
||||
typeDeclarationType: TypeDeclarationType _,
|
||||
typeArguments: List<DartType> pTypeArguments
|
||||
),
|
||||
shared.TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: shared.TypeDeclarationKind qDeclarationKind,
|
||||
typeDeclaration: TypeDeclaration qDeclarationObject,
|
||||
typeDeclarationType: TypeDeclarationType _,
|
||||
typeArguments: List<DartType> qTypeArguments
|
||||
)
|
||||
)
|
||||
when pDeclarationKind == qDeclarationKind &&
|
||||
pDeclarationObject == qDeclarationObject:
|
||||
assert(pTypeArguments.length == qTypeArguments.length);
|
||||
|
||||
final int baseConstraintCount = _protoConstraints.length;
|
||||
bool isMatch = true;
|
||||
for (int i = 0; isMatch && i < pTypeArguments.length; ++i) {
|
||||
shared.Variance variance =
|
||||
typeOperations.getTypeParameterVariance(pDeclarationObject, i);
|
||||
if (variance == shared.Variance.covariant ||
|
||||
variance == shared.Variance.invariant) {
|
||||
isMatch = isMatch &&
|
||||
_isNullabilityAwareSubtypeMatch(
|
||||
pTypeArguments[i], qTypeArguments[i],
|
||||
constrainSupertype: constrainSupertype,
|
||||
treeNodeForTesting: treeNodeForTesting);
|
||||
}
|
||||
if (variance == shared.Variance.contravariant ||
|
||||
variance == shared.Variance.invariant) {
|
||||
isMatch = isMatch &&
|
||||
_isNullabilityAwareSubtypeMatch(
|
||||
qTypeArguments[i], pTypeArguments[i],
|
||||
constrainSupertype: !constrainSupertype,
|
||||
treeNodeForTesting: treeNodeForTesting);
|
||||
}
|
||||
}
|
||||
if (isMatch) return true;
|
||||
_protoConstraints.length = baseConstraintCount;
|
||||
|
||||
// If P is C0<M0, ..., Mk> and Q is C1<N0, ..., Nj> then the match holds
|
||||
// with respect to L under constraints C:
|
||||
//
|
||||
// If C1<B0, ..., Bj> is a superinterface of C0<M0, ..., Mk> and C1<B0,
|
||||
// ..., Bj> is a subtype match for C1<N0, ..., Nj> with respect to L under
|
||||
// constraints C.
|
||||
case (
|
||||
shared.TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: shared.TypeDeclarationKind _,
|
||||
typeDeclaration: TypeDeclaration _,
|
||||
typeDeclarationType: TypeDeclarationType pTypeDeclarationType,
|
||||
typeArguments: List<DartType> _
|
||||
),
|
||||
shared.TypeDeclarationMatchResult(
|
||||
typeDeclarationKind: shared.TypeDeclarationKind _,
|
||||
typeDeclaration: TypeDeclaration qDeclarationObject,
|
||||
typeDeclarationType: TypeDeclarationType _,
|
||||
typeArguments: List<DartType> qTypeArguments
|
||||
)
|
||||
):
|
||||
final List<DartType>? sArguments = getTypeArgumentsAsInstanceOf(
|
||||
pTypeDeclarationType, qDeclarationObject);
|
||||
if (sArguments != null) {
|
||||
assert(sArguments.length == qTypeArguments.length);
|
||||
|
||||
final int baseConstraintCount = _protoConstraints.length;
|
||||
bool isMatch = true;
|
||||
for (int i = 0; isMatch && i < sArguments.length; ++i) {
|
||||
isMatch = isMatch &&
|
||||
_isNullabilityAwareSubtypeMatch(
|
||||
sArguments[i], qTypeArguments[i],
|
||||
constrainSupertype: constrainSupertype,
|
||||
treeNodeForTesting: treeNodeForTesting);
|
||||
}
|
||||
if (isMatch) return true;
|
||||
// Coverage-ignore-block(suite): Not run.
|
||||
_protoConstraints.length = baseConstraintCount;
|
||||
}
|
||||
|
||||
case (_, _):
|
||||
// Do nothing.
|
||||
bool? result = performSubtypeConstraintGenerationForTypeDeclarationTypes(
|
||||
p, q,
|
||||
leftSchema: constrainSupertype, astNodeForTesting: treeNodeForTesting);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// If Q is Function then the match holds under no constraints:
|
||||
|
||||
Reference in New Issue
Block a user