Skip to content

Commit e25fecf

Browse files
chloestefantsovaCommit Queue
authored andcommitted
[analyzer][cfe] Share left-FutureOr constraint gathering
Part of #54902 Change-Id: I825e522f370ac7fd39466b5f6aa6571fea5b5633 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394501 Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 097d0a3 commit e25fecf

File tree

5 files changed

+108
-45
lines changed

5 files changed

+108
-45
lines changed

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,7 @@ abstract class TypeConstraintGenerator<
12701270
/// schema (in other words, may contain the unknown type `_`); the other must
12711271
/// be simply a type. If [leftSchema] is `true`, [p] may contain `_`; if it is
12721272
/// `false`, [q] may contain `_`.
1273-
bool performSubtypeConstraintGenerationForFutureOr(
1273+
bool performSubtypeConstraintGenerationForRightFutureOr(
12741274
TypeStructure p, TypeStructure q,
12751275
{required bool leftSchema, required AstNode? astNodeForTesting}) {
12761276
// If `Q` is `FutureOr<Q0>` the match holds under constraint set `C`:
@@ -1316,6 +1316,43 @@ abstract class TypeConstraintGenerator<
13161316
return false;
13171317
}
13181318

1319+
/// Matches [p] against [q].
1320+
///
1321+
/// If [p] is of the form `FutureOr<p0>` for some `p0`, and [p] is a subtype
1322+
/// of [q] under some constraints, the constraints making the relation
1323+
/// possible are recorded, and `true` is returned. Otherwise, the constraint
1324+
/// state is unchanged (or rolled back using [restoreState]), and `false` is
1325+
/// returned.
1326+
///
1327+
/// An invariant of the type inference is that only [p] or [q] may be a
1328+
/// schema (in other words, may contain the unknown type `_`); the other must
1329+
/// be simply a type. If [leftSchema] is `true`, [p] may contain `_`; if it is
1330+
/// `false`, [q] may contain `_`.
1331+
bool performSubtypeConstraintGenerationForLeftFutureOr(
1332+
TypeStructure p, TypeStructure q,
1333+
{required bool leftSchema, required AstNode? astNodeForTesting}) {
1334+
// If `P` is `FutureOr<P0>` the match holds under constraint set `C1 + C2`:
1335+
NullabilitySuffix pNullability = p.nullabilitySuffix;
1336+
if (typeAnalyzerOperations.matchFutureOrInternal(p) case var p0?
1337+
when pNullability == NullabilitySuffix.none) {
1338+
final TypeConstraintGeneratorState state = currentState;
1339+
1340+
// If `Future<P0>` is a subtype match for `Q` under constraint set `C1`.
1341+
// And if `P0` is a subtype match for `Q` under constraint set `C2`.
1342+
TypeStructure futureP0 = typeAnalyzerOperations.futureTypeInternal(p0);
1343+
if (performSubtypeConstraintGenerationInternal(futureP0, q,
1344+
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting) &&
1345+
performSubtypeConstraintGenerationInternal(p0, q,
1346+
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
1347+
return true;
1348+
}
1349+
1350+
restoreState(state);
1351+
}
1352+
1353+
return false;
1354+
}
1355+
13191356
/// Matches [p] against [q] as a subtype against supertype.
13201357
///
13211358
/// If [p] and [q] are both type declaration types, then:

pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ main() {
385385
test('FutureOr matches FutureOr with constraints based on arguments', () {
386386
// `FutureOr<T> <# FutureOr<int>` reduces to `T <# int`
387387
var tcg = _TypeConstraintGatherer({'T'});
388-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
388+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
389389
Type('FutureOr<T>'), Type('FutureOr<int>'),
390390
leftSchema: false, astNodeForTesting: Node.placeholder()))
391391
.isTrue();
@@ -396,7 +396,7 @@ main() {
396396
() {
397397
// `FutureOr<int> <# FutureOr<String>` reduces to `int <# String`
398398
var tcg = _TypeConstraintGatherer({});
399-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
399+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
400400
Type('FutureOr<int>'), Type('FutureOr<String>'),
401401
leftSchema: false, astNodeForTesting: Node.placeholder()))
402402
.isFalse();
@@ -412,7 +412,7 @@ main() {
412412
// In cases where both branches produce a constraint, the "Future" branch
413413
// is favored.
414414
var tcg = _TypeConstraintGatherer({'T'});
415-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
415+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
416416
Type('Future<int>'), Type('FutureOr<T>'),
417417
leftSchema: false, astNodeForTesting: Node.placeholder()))
418418
.isTrue();
@@ -428,7 +428,7 @@ main() {
428428
// In cases where only one branch produces a constraint, that branch is
429429
// favored.
430430
var tcg = _TypeConstraintGatherer({'T'});
431-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
431+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
432432
Type('Future<_>'), Type('FutureOr<T>'),
433433
leftSchema: true, astNodeForTesting: Node.placeholder()))
434434
.isTrue();
@@ -444,18 +444,48 @@ main() {
444444
// In cases where both branches produce a constraint, the "Future" branch
445445
// is favored.
446446
var tcg = _TypeConstraintGatherer({'T'});
447-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
447+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
448448
Type('T'), Type('FutureOr<int>'),
449449
leftSchema: false, astNodeForTesting: Node.placeholder()))
450450
.isTrue();
451451
check(tcg._constraints).deepEquals(['T <: Future<int>']);
452452
});
453453

454+
test('Testing FutureOr as the lower bound of the constraint', () {
455+
var tcg = _TypeConstraintGatherer({'T'});
456+
check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
457+
Type('FutureOr<T>'), Type('dynamic'),
458+
leftSchema: false, astNodeForTesting: Node.placeholder()))
459+
.isTrue();
460+
check(tcg._constraints).deepEquals(['T <: dynamic']);
461+
});
462+
463+
test('FutureOr does not match Future in general', () {
464+
// `FutureOr<P0> <# Q` if `Future<P0> <# Q` and `P0 <# Q`. This test case
465+
// verifies that if `Future<P0> <# Q` matches but `P0 <# Q` does not, then
466+
// the match fails.
467+
var tcg = _TypeConstraintGatherer({'T'});
468+
check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
469+
Type('FutureOr<(T,)>'), Type('Future<(int,)>'),
470+
leftSchema: false, astNodeForTesting: Node.placeholder()))
471+
.isFalse();
472+
check(tcg._constraints).isEmpty();
473+
});
474+
475+
test('Testing nested FutureOr as the lower bound of the constraint', () {
476+
var tcg = _TypeConstraintGatherer({'T'});
477+
check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
478+
Type('FutureOr<FutureOr<T>>'), Type('FutureOr<dynamic>'),
479+
leftSchema: false, astNodeForTesting: Node.placeholder()))
480+
.isTrue();
481+
check(tcg._constraints).deepEquals(['T <: dynamic', 'T <: dynamic']);
482+
});
483+
454484
test('Future matches FutureOr with no constraints', () {
455485
// `Future<int> <# FutureOr<int>` matches (taking the "Future" branch of
456486
// the FutureOr) without generating any constraints.
457487
var tcg = _TypeConstraintGatherer({});
458-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
488+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
459489
Type('Future<int>'), Type('FutureOr<int>'),
460490
leftSchema: false, astNodeForTesting: Node.placeholder()))
461491
.isTrue();
@@ -467,7 +497,7 @@ main() {
467497
// "non-Future" branch of the FutureOr, so the constraint `T <: int` is
468498
// produced.
469499
var tcg = _TypeConstraintGatherer({'T'});
470-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
500+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
471501
Type('List<T>'), Type('FutureOr<List<int>>'),
472502
leftSchema: false, astNodeForTesting: Node.placeholder()))
473503
.isTrue();
@@ -477,7 +507,7 @@ main() {
477507
group('Nullable FutureOr on RHS:', () {
478508
test('Does not match, according to spec', () {
479509
var tcg = _TypeConstraintGatherer({'T'});
480-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
510+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
481511
Type('FutureOr<T>'), Type('FutureOr<int>?'),
482512
leftSchema: false, astNodeForTesting: Node.placeholder()))
483513
.isFalse();
@@ -487,7 +517,7 @@ main() {
487517
test('Matches, according to CFE discrepancy', () {
488518
var tcg = _TypeConstraintGatherer({'T'},
489519
enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr: true);
490-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
520+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
491521
Type('FutureOr<T>'), Type('FutureOr<int>?'),
492522
leftSchema: false, astNodeForTesting: Node.placeholder()))
493523
.isTrue();
@@ -498,7 +528,7 @@ main() {
498528
group('Nullable FutureOr on LHS:', () {
499529
test('Does not match, according to spec', () {
500530
var tcg = _TypeConstraintGatherer({'T'});
501-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
531+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
502532
Type('FutureOr<T>?'), Type('FutureOr<int>'),
503533
leftSchema: false, astNodeForTesting: Node.placeholder()))
504534
.isFalse();
@@ -508,7 +538,7 @@ main() {
508538
test('Matches, according to CFE discrepancy', () {
509539
var tcg = _TypeConstraintGatherer({'T'},
510540
enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr: true);
511-
check(tcg.performSubtypeConstraintGenerationForFutureOr(
541+
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
512542
Type('FutureOr<T>?'), Type('FutureOr<int>'),
513543
leftSchema: false, astNodeForTesting: Node.placeholder()))
514544
.isTrue();
@@ -844,6 +874,7 @@ class _TypeConstraintGatherer extends TypeConstraintGenerator<Type,
844874
case (PrimaryType(name: 'int'), 'String'):
845875
case (PrimaryType(name: 'List'), 'Future'):
846876
case (PrimaryType(name: 'String'), 'int'):
877+
case (PrimaryType(name: 'Future'), 'String'):
847878
// Unrelated types
848879
return null;
849880
default:
@@ -891,16 +922,32 @@ class _TypeConstraintGatherer extends TypeConstraintGenerator<Type,
891922
return true;
892923
}
893924

925+
if (performSubtypeConstraintGenerationForRightFutureOr(p, q,
926+
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
927+
return true;
928+
}
929+
894930
if (performSubtypeConstraintGenerationForRightNullableType(p, q,
895931
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
896932
return true;
897933
}
898934

935+
if (performSubtypeConstraintGenerationForLeftFutureOr(p, q,
936+
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
937+
return true;
938+
}
939+
899940
if (performSubtypeConstraintGenerationForLeftNullableType(p, q,
900941
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
901942
return true;
902943
}
903944

945+
if (q is SharedDynamicTypeStructure ||
946+
q is SharedVoidTypeStructure ||
947+
q == typeAnalyzerOperations.objectQuestionType.unwrapTypeView()) {
948+
return true;
949+
}
950+
904951
bool? result = performSubtypeConstraintGenerationForTypeDeclarationTypes(
905952
p, q,
906953
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting);

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
274274
// in case [performSubtypeConstraintGenerationForFutureOr] returns false, as
275275
// [performSubtypeConstraintGenerationForFutureOr] handles the rewinding of
276276
// the state itself.
277-
if (performSubtypeConstraintGenerationForFutureOr(P, Q,
277+
if (performSubtypeConstraintGenerationForRightFutureOr(P, Q,
278278
leftSchema: leftSchema, astNodeForTesting: nodeForTesting)) {
279279
return true;
280280
}
@@ -285,20 +285,9 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
285285
}
286286

287287
// If `P` is `FutureOr<P0>` the match holds under constraint set `C1 + C2`:
288-
if (_typeSystemOperations.matchFutureOrInternal(P) case var P0?
289-
when P_nullability == NullabilitySuffix.none) {
290-
var rewind = _constraints.length;
291-
292-
// If `Future<P0>` is a subtype match for `Q` under constraint set `C1`.
293-
// And if `P0` is a subtype match for `Q` under constraint set `C2`.
294-
var future_P0 = _typeSystemOperations.futureTypeInternal(P0);
295-
if (trySubtypeMatch(future_P0, Q, leftSchema,
296-
nodeForTesting: nodeForTesting) &&
297-
trySubtypeMatch(P0, Q, leftSchema, nodeForTesting: nodeForTesting)) {
298-
return true;
299-
}
300-
301-
_constraints.length = rewind;
288+
if (performSubtypeConstraintGenerationForLeftFutureOr(P, Q,
289+
leftSchema: leftSchema, astNodeForTesting: nodeForTesting)) {
290+
return true;
302291
}
303292

304293
// If `P` is `P0?` the match holds under constraint set `C1 + C2`:

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

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
356356
return true;
357357
}
358358

359-
if (performSubtypeConstraintGenerationForFutureOr(p, q,
359+
if (performSubtypeConstraintGenerationForRightFutureOr(p, q,
360360
leftSchema: constrainSupertype,
361361
astNodeForTesting: treeNodeForTesting)) {
362362
return true;
@@ -380,20 +380,10 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
380380
//
381381
// If Future<P0> is a subtype match for Q under constraint set C1.
382382
// And if P0 is a subtype match for Q under constraint set C2.
383-
384-
if (typeOperations.matchFutureOrInternal(p) case DartType p0?) {
385-
final int baseConstraintCount = _protoConstraints.length;
386-
if (_isNullabilityAwareSubtypeMatch(
387-
typeOperations.futureTypeInternal(p0), q,
388-
constrainSupertype: constrainSupertype,
389-
treeNodeForTesting: treeNodeForTesting) &&
390-
// Coverage-ignore(suite): Not run.
391-
_isNullabilityAwareSubtypeMatch(p0, q,
392-
constrainSupertype: constrainSupertype,
393-
treeNodeForTesting: treeNodeForTesting)) {
394-
return true;
395-
}
396-
_protoConstraints.length = baseConstraintCount;
383+
if (performSubtypeConstraintGenerationForLeftFutureOr(p, q,
384+
leftSchema: constrainSupertype,
385+
astNodeForTesting: treeNodeForTesting)) {
386+
return true;
397387
}
398388

399389
// If P is P0? the match holds under constraint set C1 + C2:

pkg/front_end/test/coverage_suite_expected.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
560560
),
561561
// 100.0%.
562562
"package:front_end/src/kernel/body_builder.dart": (
563-
hitCount: 7121,
563+
hitCount: 7128,
564564
missCount: 0,
565565
),
566566
// 100.0%.
@@ -870,7 +870,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
870870
),
871871
// 100.0%.
872872
"package:front_end/src/source/outline_builder.dart": (
873-
hitCount: 2118,
873+
hitCount: 2122,
874874
missCount: 0,
875875
),
876876
// 100.0%.
@@ -1036,7 +1036,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
10361036
),
10371037
// 100.0%.
10381038
"package:front_end/src/type_inference/type_constraint_gatherer.dart": (
1039-
hitCount: 188,
1039+
hitCount: 179,
10401040
missCount: 0,
10411041
),
10421042
// 100.0%.

0 commit comments

Comments
 (0)