From 59cb10019580babbb88c6ed85bbfcdd7c6d9938c Mon Sep 17 00:00:00 2001 From: Chloe Stefantsova Date: Fri, 30 Aug 2024 08:58:22 +0000 Subject: [PATCH] [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 Commit-Queue: Chloe Stefantsova --- .../type_analyzer_operations.dart | 140 +++++++++++++++++ .../element/type_constraint_gatherer.dart | 141 +++--------------- .../type_constraint_gatherer.dart | 100 +------------ 3 files changed, 166 insertions(+), 215 deletions(-) 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 7e0aba4dd01..7bc5f932d2d 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 @@ -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 and `Q` is `C`, 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 pTypeArguments + ), + TypeDeclarationMatchResult( + typeDeclarationKind: TypeDeclarationKind qTypeDeclarationKind, + typeDeclaration: TypeDeclaration qDeclarationObject, + typeArguments: List 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 pTypeArguments, + List 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` and `Q` is `C1` then the match + // holds with respect to `L` under constraints `C`: + // If `C1` is a superinterface of `C0` and + // `C1` is a subtype match for `C1` 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 qTypeArguments + ) + )) { + if (getTypeArgumentsAsInstanceOf(pTypeDeclarationType, qTypeDeclaration) + case List 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? getTypeArgumentsAsInstanceOf( + TypeDeclarationType type, TypeDeclaration typeDeclaration); } /// Representation of the state of [TypeConstraintGenerator]. 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 e62eaf4abfc..e2cfa59b7b1 100644 --- a/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart +++ b/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart @@ -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? 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 and `Q` is `C`, 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 P_typeArguments - ), - shared.TypeDeclarationMatchResult( - typeDeclarationKind: shared.TypeDeclarationKind - Q_typeDeclarationKind, - typeDeclaration: InterfaceElement Q_declarationObject, - typeDeclarationType: InterfaceType _, - typeArguments: List 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 _ - ), - shared.TypeDeclarationMatchResult( - typeDeclarationKind: shared.TypeDeclarationKind _, - typeDeclaration: InterfaceElement _, - typeDeclarationType: InterfaceType Q_interfaceType, - typeArguments: List _ - ) - ): - 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` and `Q` is `C1` then the match - // holds with respect to `L` under constraints `C`: - // If `C1` is a superinterface of `C0` and - // `C1` is a subtype match for `C1` 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 P_typeArguments, - InterfaceElement Q_element, - List 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 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 f429216f959..a1e08a208c1 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 @@ -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? 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 and Q is C, 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 pTypeArguments - ), - shared.TypeDeclarationMatchResult( - typeDeclarationKind: shared.TypeDeclarationKind qDeclarationKind, - typeDeclaration: TypeDeclaration qDeclarationObject, - typeDeclarationType: TypeDeclarationType _, - typeArguments: List 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 and Q is C1 then the match holds - // with respect to L under constraints C: - // - // If C1 is a superinterface of C0 and C1 is a subtype match for C1 with respect to L under - // constraints C. - case ( - shared.TypeDeclarationMatchResult( - typeDeclarationKind: shared.TypeDeclarationKind _, - typeDeclaration: TypeDeclaration _, - typeDeclarationType: TypeDeclarationType pTypeDeclarationType, - typeArguments: List _ - ), - shared.TypeDeclarationMatchResult( - typeDeclarationKind: shared.TypeDeclarationKind _, - typeDeclaration: TypeDeclaration qDeclarationObject, - typeDeclarationType: TypeDeclarationType _, - typeArguments: List qTypeArguments - ) - ): - final List? 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: