[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:
Chloe Stefantsova
2024-08-30 08:58:22 +00:00
committed by Commit Queue
parent eff0ece338
commit 59cb100195
3 changed files with 166 additions and 215 deletions
@@ -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: