Skip to content

Commit 9a9166e

Browse files
chloestefantsovaCommit Queue
authored andcommitted
[model] Share constraint solving for one type variable Analyzer/CFE
Part of #54902 Change-Id: Iff6d8b2cf058328cacae307c27fc7d0e63a51227 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/442201 Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 0de97d5 commit 9a9166e

File tree

7 files changed

+157
-196
lines changed

7 files changed

+157
-196
lines changed

pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,44 @@ abstract interface class TypeAnalyzerOperations<
769769
/// The least closure of a type schema is defined in
770770
/// https://github.com/dart-lang/language/blob/main/accepted/future-releases/0323-null-aware-elements/feature-specification.md
771771
SharedTypeView leastClosureOfSchema(SharedTypeSchemaView schema);
772+
773+
/// Computes the constraint solution for a type parameter based on a given
774+
/// set of constraints.
775+
///
776+
/// For example given:
777+
///
778+
/// bool Function(T) makeComparer<T>(T x) => (T y) => x == y;
779+
///
780+
/// main() {
781+
/// bool Function(num) t = makeComparer/* infer <num> */(42);
782+
/// print(t(42.0)); /// false, no error.
783+
/// }
784+
///
785+
/// The constraints we collect are:
786+
///
787+
/// * `num <: T`
788+
/// * `int <: T`
789+
///
790+
/// ... and no upper bound. Therefore the lower bound is the best choice.
791+
///
792+
/// If [grounded] is `true`, then the returned type is guaranteed to be a
793+
/// known type (i.e. it will not contain any instances of `?`) if it is
794+
/// constrained at all. The returned type for unconstrained variables is
795+
/// `?`.
796+
///
797+
/// If [isContravariant] is `true`, then we are solving for a contravariant
798+
/// type parameter which means we choose the upper bound rather than the
799+
/// lower bound for normally covariant type parameters.
800+
///
801+
/// The algorithm for computing the constraint solution for a type variable
802+
/// is described in
803+
/// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md#constraint-solution-for-a-type-variable.
804+
SharedType chooseTypeFromConstraint(
805+
MergedTypeConstraint<Variable, TypeDeclarationType, TypeDeclaration>
806+
constraint, {
807+
required bool grounded,
808+
required bool isContravariant,
809+
});
772810
}
773811

774812
mixin TypeAnalyzerOperationsMixin<
@@ -1083,6 +1121,63 @@ mixin TypeAnalyzerOperationsMixin<
10831121
SharedTypeSchemaView typeToSchema(SharedTypeView type) {
10841122
return new SharedTypeSchemaView(type.unwrapTypeView());
10851123
}
1124+
1125+
@override
1126+
SharedType chooseTypeFromConstraint(
1127+
MergedTypeConstraint<Variable, TypeDeclarationType, TypeDeclaration>
1128+
constraint, {
1129+
required bool grounded,
1130+
required bool isContravariant,
1131+
}) {
1132+
if (!isContravariant) {
1133+
// Prefer the known bound, if any.
1134+
if (isKnownType(constraint.lower)) {
1135+
return constraint.lower.unwrapTypeSchemaView();
1136+
}
1137+
if (isKnownType(constraint.upper)) {
1138+
return constraint.upper.unwrapTypeSchemaView();
1139+
}
1140+
1141+
// Otherwise take whatever bound has partial information,
1142+
// e.g. `Iterable<?>`
1143+
if (constraint.lower is! SharedUnknownTypeSchemaView) {
1144+
return grounded
1145+
? leastClosureOfSchema(constraint.lower).unwrapTypeView()
1146+
: constraint.lower.unwrapTypeSchemaView();
1147+
} else if (constraint.upper is! SharedUnknownTypeSchemaView) {
1148+
return grounded
1149+
? greatestClosureOfSchema(constraint.upper).unwrapTypeView()
1150+
: constraint.upper.unwrapTypeSchemaView();
1151+
} else {
1152+
assert(constraint.lower is SharedUnknownTypeSchemaView);
1153+
return constraint.lower.unwrapTypeSchemaView();
1154+
}
1155+
} else {
1156+
// Prefer the known bound, if any.
1157+
if (isKnownType(constraint.upper)) {
1158+
return constraint.upper.unwrapTypeSchemaView();
1159+
}
1160+
if (isKnownType(constraint.lower)) {
1161+
return constraint.lower.unwrapTypeSchemaView();
1162+
}
1163+
1164+
// Otherwise take whatever bound has partial information,
1165+
// e.g. `Iterable<?>`
1166+
if (constraint.upper is! SharedUnknownTypeSchemaView) {
1167+
// Coverage-ignore-block(suite): Not run.
1168+
return grounded
1169+
? greatestClosureOfSchema(constraint.upper).unwrapTypeView()
1170+
: constraint.upper.unwrapTypeSchemaView();
1171+
} else if (constraint.lower is! SharedUnknownTypeSchemaView) {
1172+
return grounded
1173+
? leastClosureOfSchema(constraint.lower).unwrapTypeView()
1174+
: constraint.lower.unwrapTypeSchemaView();
1175+
} else {
1176+
assert(constraint.upper is SharedUnknownTypeSchemaView);
1177+
return constraint.upper.unwrapTypeSchemaView();
1178+
}
1179+
}
1180+
}
10861181
}
10871182

10881183
/// Abstract interface of a type constraint generator.

pkg/analyzer/lib/src/dart/element/generic_inferrer.dart

Lines changed: 26 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -474,74 +474,6 @@ class GenericInferrer {
474474
}
475475
}
476476

477-
/// Choose the bound that was implied by the return type, if any.
478-
///
479-
/// Which bound this is depends on what positions the type parameter
480-
/// appears in. If the type only appears only in a contravariant position,
481-
/// we will choose the lower bound instead.
482-
///
483-
/// For example given:
484-
///
485-
/// Func1<T, bool> makeComparer<T>(T x) => (T y) => x() == y;
486-
///
487-
/// main() {
488-
/// Func1<num, bool> t = makeComparer/* infer <num> */(42);
489-
/// print(t(42.0)); /// false, no error.
490-
/// }
491-
///
492-
/// The constraints we collect are:
493-
///
494-
/// * `num <: T`
495-
/// * `int <: T`
496-
///
497-
/// ... and no upper bound. Therefore the lower bound is the best choice.
498-
///
499-
/// If [isContravariant] is `true`, then we are solving for a contravariant
500-
/// type parameter which means we choose the upper bound rather than the
501-
/// lower bound for normally covariant type parameters.
502-
TypeImpl _chooseTypeFromConstraint(
503-
MergedTypeConstraint constraint, {
504-
bool toKnownType = false,
505-
required bool isContravariant,
506-
}) {
507-
TypeImpl upper = constraint.upper.unwrapTypeSchemaView();
508-
TypeImpl lower = constraint.lower.unwrapTypeSchemaView();
509-
// Prefer the known bound, if any.
510-
// Otherwise take whatever bound has partial information, e.g. `Iterable<?>`
511-
//
512-
// For both of those, prefer the lower bound (arbitrary heuristic) or upper
513-
// bound if [isContravariant] is `true`
514-
if (isContravariant) {
515-
if (_typeSystemOperations.isKnownType(SharedTypeSchemaView(upper))) {
516-
return upper;
517-
}
518-
if (_typeSystemOperations.isKnownType(SharedTypeSchemaView(lower))) {
519-
return lower;
520-
}
521-
if (!identical(UnknownInferredType.instance, upper)) {
522-
return toKnownType ? _typeSystem.greatestClosureOfSchema(upper) : upper;
523-
}
524-
if (!identical(UnknownInferredType.instance, lower)) {
525-
return toKnownType ? _typeSystem.leastClosureOfSchema(lower) : lower;
526-
}
527-
return upper;
528-
} else {
529-
if (_typeSystemOperations.isKnownType(SharedTypeSchemaView(lower))) {
530-
return lower;
531-
}
532-
if (_typeSystemOperations.isKnownType(SharedTypeSchemaView(upper))) {
533-
return upper;
534-
}
535-
if (!identical(UnknownInferredType.instance, lower)) {
536-
return toKnownType ? _typeSystem.leastClosureOfSchema(lower) : lower;
537-
}
538-
if (!identical(UnknownInferredType.instance, upper)) {
539-
return toKnownType ? _typeSystem.greatestClosureOfSchema(upper) : upper;
540-
}
541-
return lower;
542-
}
543-
}
544-
545477
/// Computes (or recomputes) a set of inferred types based on the constraints
546478
/// that have been recorded so far.
547479
List<TypeImpl> _chooseTypes({required bool preliminary}) {
@@ -579,13 +511,15 @@ class GenericInferrer {
579511
if (previouslyInferredType != null) {
580512
inferredTypes[i] = previouslyInferredType;
581513
} else if (preliminary) {
582-
var inferredType = _inferTypeParameterFromContext(
583-
constraint,
584-
extendsClause,
585-
isContravariant: typeParam.variance.isContravariant,
586-
typeParameterToInfer: typeParam,
587-
inferencePhaseConstraints: inferencePhaseConstraints,
588-
);
514+
var inferredType =
515+
_inferTypeParameterFromContext(
516+
constraint,
517+
extendsClause,
518+
isContravariant: typeParam.variance.isContravariant,
519+
typeParameterToInfer: typeParam,
520+
inferencePhaseConstraints: inferencePhaseConstraints,
521+
)
522+
as TypeImpl;
589523

590524
inferredTypes[i] = inferredType;
591525
if (typeParam.isLegacyCovariant &&
@@ -595,13 +529,15 @@ class GenericInferrer {
595529
_typesInferredSoFar[typeParam] = inferredType;
596530
}
597531
} else {
598-
inferredTypes[i] = _inferTypeParameterFromAll(
599-
constraint,
600-
extendsClause,
601-
isContravariant: typeParam.variance.isContravariant,
602-
typeParameterToInfer: typeParam,
603-
inferencePhaseConstraints: inferencePhaseConstraints,
604-
);
532+
inferredTypes[i] =
533+
_inferTypeParameterFromAll(
534+
constraint,
535+
extendsClause,
536+
isContravariant: typeParam.variance.isContravariant,
537+
typeParameterToInfer: typeParam,
538+
inferencePhaseConstraints: inferencePhaseConstraints,
539+
)
540+
as TypeImpl;
605541
}
606542
}
607543

@@ -667,7 +603,7 @@ class GenericInferrer {
667603
'Consider passing explicit type argument(s) to the generic.\n\n';
668604
}
669605

670-
TypeImpl _inferTypeParameterFromAll(
606+
SharedType _inferTypeParameterFromAll(
671607
MergedTypeConstraint constraint,
672608
MergedTypeConstraint? extendsClause, {
673609
required bool isContravariant,
@@ -702,15 +638,15 @@ class GenericInferrer {
702638
]);
703639
}
704640

705-
var choice = _chooseTypeFromConstraint(
641+
var choice = _typeSystemOperations.chooseTypeFromConstraint(
706642
constraint,
707-
toKnownType: true,
643+
grounded: true,
708644
isContravariant: isContravariant,
709645
);
710646
return choice;
711647
}
712648

713-
TypeImpl _inferTypeParameterFromContext(
649+
SharedType _inferTypeParameterFromContext(
714650
MergedTypeConstraint constraint,
715651
MergedTypeConstraint? extendsClause, {
716652
required bool isContravariant,
@@ -721,8 +657,9 @@ class GenericInferrer {
721657
// Both bits of the bound information should be available at the same time.
722658
assert(extendsClause == null || typeParameterToInfer.bound != null);
723659

724-
TypeImpl t = _chooseTypeFromConstraint(
660+
SharedType t = _typeSystemOperations.chooseTypeFromConstraint(
725661
constraint,
662+
grounded: false,
726663
isContravariant: isContravariant,
727664
);
728665
if (!_typeSystemOperations.isKnownType(SharedTypeSchemaView(t))) {
@@ -761,8 +698,9 @@ class GenericInferrer {
761698
!boundConstraint.isEmpty(_typeSystemOperations))
762699
boundConstraint,
763700
]);
764-
return _chooseTypeFromConstraint(
701+
return _typeSystemOperations.chooseTypeFromConstraint(
765702
constraint,
703+
grounded: false,
766704
isContravariant: isContravariant,
767705
);
768706
}

pkg/front_end/lib/src/type_inference/standard_bounds.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mixin TypeSchemaStandardBounds on StandardBounds {
2121
DartType leastClosureForUpperBound(DartType typeSchema) {
2222
// - We replace all uses of `T1 <: T2` in the `UP` algorithm by `S1 <: S2`
2323
// where `Si` is the least closure of `Ti` with respect to `_`.
24-
return leastClosure(typeSchema, coreTypes: coreTypes);
24+
return leastClosure(typeSchema, coreTypes: hierarchy.coreTypes);
2525
}
2626

2727
@override

pkg/front_end/lib/src/type_inference/type_inference_engine.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,15 +1038,13 @@ class OperationsCfe
10381038
@override
10391039
SharedTypeView greatestClosureOfSchema(SharedTypeSchemaView schema,
10401040
{SharedTypeView? topType}) {
1041-
return new SharedTypeView(
1042-
type_schema_elimination.greatestClosure(schema.unwrapTypeSchemaView(),
1043-
topType: topType?.unwrapTypeView() ??
1044-
// Coverage-ignore(suite): Not run.
1045-
typeEnvironment.coreTypes.objectNullableRawType));
1041+
return new SharedTypeView(type_schema_elimination.greatestClosure(
1042+
schema.unwrapTypeSchemaView(),
1043+
topType: topType?.unwrapTypeView() ??
1044+
typeEnvironment.coreTypes.objectNullableRawType));
10461045
}
10471046

10481047
@override
1049-
// Coverage-ignore(suite): Not run.
10501048
SharedTypeView leastClosureOfSchema(SharedTypeSchemaView schema) {
10511049
return new SharedTypeView(type_schema_elimination.leastClosure(
10521050
schema.unwrapTypeSchemaView(),

0 commit comments

Comments
 (0)