Skip to content

Commit

Permalink
[analyzer][cfe] Share left-FutureOr constraint gathering
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
chloestefantsova authored and Commit Queue committed Nov 13, 2024
1 parent 097d0a3 commit e25fecf
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1270,7 +1270,7 @@ abstract class TypeConstraintGenerator<
/// 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 `_`.
bool performSubtypeConstraintGenerationForFutureOr(
bool performSubtypeConstraintGenerationForRightFutureOr(
TypeStructure p, TypeStructure q,
{required bool leftSchema, required AstNode? astNodeForTesting}) {
// If `Q` is `FutureOr<Q0>` the match holds under constraint set `C`:
Expand Down Expand Up @@ -1316,6 +1316,43 @@ abstract class TypeConstraintGenerator<
return false;
}

/// Matches [p] against [q].
///
/// If [p] is of the form `FutureOr<p0>` for some `p0`, and [p] is a subtype
/// of [q] under some constraints, the constraints making the relation
/// possible are recorded, and `true` is returned. Otherwise, the constraint
/// state is unchanged (or rolled back using [restoreState]), and `false` is
/// returned.
///
/// 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 `_`.
bool performSubtypeConstraintGenerationForLeftFutureOr(
TypeStructure p, TypeStructure q,
{required bool leftSchema, required AstNode? astNodeForTesting}) {
// If `P` is `FutureOr<P0>` the match holds under constraint set `C1 + C2`:
NullabilitySuffix pNullability = p.nullabilitySuffix;
if (typeAnalyzerOperations.matchFutureOrInternal(p) case var p0?
when pNullability == NullabilitySuffix.none) {
final TypeConstraintGeneratorState state = currentState;

// If `Future<P0>` is a subtype match for `Q` under constraint set `C1`.
// And if `P0` is a subtype match for `Q` under constraint set `C2`.
TypeStructure futureP0 = typeAnalyzerOperations.futureTypeInternal(p0);
if (performSubtypeConstraintGenerationInternal(futureP0, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting) &&
performSubtypeConstraintGenerationInternal(p0, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}

restoreState(state);
}

return false;
}

/// Matches [p] against [q] as a subtype against supertype.
///
/// If [p] and [q] are both type declaration types, then:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ main() {
test('FutureOr matches FutureOr with constraints based on arguments', () {
// `FutureOr<T> <# FutureOr<int>` reduces to `T <# int`
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('FutureOr<T>'), Type('FutureOr<int>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
Expand All @@ -396,7 +396,7 @@ main() {
() {
// `FutureOr<int> <# FutureOr<String>` reduces to `int <# String`
var tcg = _TypeConstraintGatherer({});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('FutureOr<int>'), Type('FutureOr<String>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isFalse();
Expand All @@ -412,7 +412,7 @@ main() {
// In cases where both branches produce a constraint, the "Future" branch
// is favored.
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('Future<int>'), Type('FutureOr<T>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
Expand All @@ -428,7 +428,7 @@ main() {
// In cases where only one branch produces a constraint, that branch is
// favored.
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('Future<_>'), Type('FutureOr<T>'),
leftSchema: true, astNodeForTesting: Node.placeholder()))
.isTrue();
Expand All @@ -444,18 +444,48 @@ main() {
// In cases where both branches produce a constraint, the "Future" branch
// is favored.
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('T'), Type('FutureOr<int>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).deepEquals(['T <: Future<int>']);
});

test('Testing FutureOr as the lower bound of the constraint', () {
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
Type('FutureOr<T>'), Type('dynamic'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).deepEquals(['T <: dynamic']);
});

test('FutureOr does not match Future in general', () {
// `FutureOr<P0> <# Q` if `Future<P0> <# Q` and `P0 <# Q`. This test case
// verifies that if `Future<P0> <# Q` matches but `P0 <# Q` does not, then
// the match fails.
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
Type('FutureOr<(T,)>'), Type('Future<(int,)>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isFalse();
check(tcg._constraints).isEmpty();
});

test('Testing nested FutureOr as the lower bound of the constraint', () {
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForLeftFutureOr(
Type('FutureOr<FutureOr<T>>'), Type('FutureOr<dynamic>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).deepEquals(['T <: dynamic', 'T <: dynamic']);
});

test('Future matches FutureOr with no constraints', () {
// `Future<int> <# FutureOr<int>` matches (taking the "Future" branch of
// the FutureOr) without generating any constraints.
var tcg = _TypeConstraintGatherer({});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('Future<int>'), Type('FutureOr<int>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
Expand All @@ -467,7 +497,7 @@ main() {
// "non-Future" branch of the FutureOr, so the constraint `T <: int` is
// produced.
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('List<T>'), Type('FutureOr<List<int>>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
Expand All @@ -477,7 +507,7 @@ main() {
group('Nullable FutureOr on RHS:', () {
test('Does not match, according to spec', () {
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('FutureOr<T>'), Type('FutureOr<int>?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isFalse();
Expand All @@ -487,7 +517,7 @@ main() {
test('Matches, according to CFE discrepancy', () {
var tcg = _TypeConstraintGatherer({'T'},
enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr: true);
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('FutureOr<T>'), Type('FutureOr<int>?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
Expand All @@ -498,7 +528,7 @@ main() {
group('Nullable FutureOr on LHS:', () {
test('Does not match, according to spec', () {
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('FutureOr<T>?'), Type('FutureOr<int>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isFalse();
Expand All @@ -508,7 +538,7 @@ main() {
test('Matches, according to CFE discrepancy', () {
var tcg = _TypeConstraintGatherer({'T'},
enableDiscrepantObliviousnessOfNullabilitySuffixOfFutureOr: true);
check(tcg.performSubtypeConstraintGenerationForFutureOr(
check(tcg.performSubtypeConstraintGenerationForRightFutureOr(
Type('FutureOr<T>?'), Type('FutureOr<int>'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
Expand Down Expand Up @@ -844,6 +874,7 @@ class _TypeConstraintGatherer extends TypeConstraintGenerator<Type,
case (PrimaryType(name: 'int'), 'String'):
case (PrimaryType(name: 'List'), 'Future'):
case (PrimaryType(name: 'String'), 'int'):
case (PrimaryType(name: 'Future'), 'String'):
// Unrelated types
return null;
default:
Expand Down Expand Up @@ -891,16 +922,32 @@ class _TypeConstraintGatherer extends TypeConstraintGenerator<Type,
return true;
}

if (performSubtypeConstraintGenerationForRightFutureOr(p, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}

if (performSubtypeConstraintGenerationForRightNullableType(p, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}

if (performSubtypeConstraintGenerationForLeftFutureOr(p, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}

if (performSubtypeConstraintGenerationForLeftNullableType(p, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}

if (q is SharedDynamicTypeStructure ||
q is SharedVoidTypeStructure ||
q == typeAnalyzerOperations.objectQuestionType.unwrapTypeView()) {
return true;
}

bool? result = performSubtypeConstraintGenerationForTypeDeclarationTypes(
p, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting);
Expand Down
19 changes: 4 additions & 15 deletions pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
// in case [performSubtypeConstraintGenerationForFutureOr] returns false, as
// [performSubtypeConstraintGenerationForFutureOr] handles the rewinding of
// the state itself.
if (performSubtypeConstraintGenerationForFutureOr(P, Q,
if (performSubtypeConstraintGenerationForRightFutureOr(P, Q,
leftSchema: leftSchema, astNodeForTesting: nodeForTesting)) {
return true;
}
Expand All @@ -285,20 +285,9 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
}

// If `P` is `FutureOr<P0>` the match holds under constraint set `C1 + C2`:
if (_typeSystemOperations.matchFutureOrInternal(P) case var P0?
when P_nullability == NullabilitySuffix.none) {
var rewind = _constraints.length;

// If `Future<P0>` is a subtype match for `Q` under constraint set `C1`.
// And if `P0` is a subtype match for `Q` under constraint set `C2`.
var future_P0 = _typeSystemOperations.futureTypeInternal(P0);
if (trySubtypeMatch(future_P0, Q, leftSchema,
nodeForTesting: nodeForTesting) &&
trySubtypeMatch(P0, Q, leftSchema, nodeForTesting: nodeForTesting)) {
return true;
}

_constraints.length = rewind;
if (performSubtypeConstraintGenerationForLeftFutureOr(P, Q,
leftSchema: leftSchema, astNodeForTesting: nodeForTesting)) {
return true;
}

// If `P` is `P0?` the match holds under constraint set `C1 + C2`:
Expand Down
20 changes: 5 additions & 15 deletions pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
return true;
}

if (performSubtypeConstraintGenerationForFutureOr(p, q,
if (performSubtypeConstraintGenerationForRightFutureOr(p, q,
leftSchema: constrainSupertype,
astNodeForTesting: treeNodeForTesting)) {
return true;
Expand All @@ -380,20 +380,10 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
//
// If Future<P0> is a subtype match for Q under constraint set C1.
// And if P0 is a subtype match for Q under constraint set C2.

if (typeOperations.matchFutureOrInternal(p) case DartType p0?) {
final int baseConstraintCount = _protoConstraints.length;
if (_isNullabilityAwareSubtypeMatch(
typeOperations.futureTypeInternal(p0), q,
constrainSupertype: constrainSupertype,
treeNodeForTesting: treeNodeForTesting) &&
// Coverage-ignore(suite): Not run.
_isNullabilityAwareSubtypeMatch(p0, q,
constrainSupertype: constrainSupertype,
treeNodeForTesting: treeNodeForTesting)) {
return true;
}
_protoConstraints.length = baseConstraintCount;
if (performSubtypeConstraintGenerationForLeftFutureOr(p, q,
leftSchema: constrainSupertype,
astNodeForTesting: treeNodeForTesting)) {
return true;
}

// If P is P0? the match holds under constraint set C1 + C2:
Expand Down
6 changes: 3 additions & 3 deletions pkg/front_end/test/coverage_suite_expected.dart
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
),
// 100.0%.
"package:front_end/src/kernel/body_builder.dart": (
hitCount: 7121,
hitCount: 7128,
missCount: 0,
),
// 100.0%.
Expand Down Expand Up @@ -870,7 +870,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
),
// 100.0%.
"package:front_end/src/source/outline_builder.dart": (
hitCount: 2118,
hitCount: 2122,
missCount: 0,
),
// 100.0%.
Expand Down Expand Up @@ -1036,7 +1036,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
),
// 100.0%.
"package:front_end/src/type_inference/type_constraint_gatherer.dart": (
hitCount: 188,
hitCount: 179,
missCount: 0,
),
// 100.0%.
Expand Down

0 comments on commit e25fecf

Please sign in to comment.