Skip to content

Commit

Permalink
Version 3.7.0-139.0.dev
Browse files Browse the repository at this point in the history
Merge be6ca13 into dev
  • Loading branch information
Dart CI committed Nov 13, 2024
2 parents c7d140d + be6ca13 commit e5c2c50
Show file tree
Hide file tree
Showing 19 changed files with 3,593 additions and 646 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,117 @@ abstract class TypeConstraintGenerator<
}
}

/// Matches [p] against [q] as a subtype against supertype.
///
/// - If [p] is `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 performSubtypeConstraintGenerationForLeftNullableType(
TypeStructure p, TypeStructure q,
{required bool leftSchema, required AstNode? astNodeForTesting}) {
// If `P` is `P0?` the match holds under constraint set `C1 + C2`:
NullabilitySuffix pNullability = p.nullabilitySuffix;
if (pNullability == NullabilitySuffix.question) {
TypeStructure p0 = typeAnalyzerOperations
.withNullabilitySuffix(new SharedTypeView(p), NullabilitySuffix.none)
.unwrapTypeView();
final TypeConstraintGeneratorState state = currentState;

// If `P0` is a subtype match for `Q` under constraint set `C1`.
// And if `Null` is a subtype match for `Q` under constraint set `C2`.
if (performSubtypeConstraintGenerationInternal(p0, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting) &&
performSubtypeConstraintGenerationInternal(
typeAnalyzerOperations.nullType.unwrapTypeView(), q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}

restoreState(state);
}

return false;
}

/// Matches [p] against [q] as a subtype against supertype.
///
/// - If [q] is `q0?` for some `q0` 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 performSubtypeConstraintGenerationForRightNullableType(
TypeStructure p, TypeStructure q,
{required bool leftSchema, required AstNode? astNodeForTesting}) {
// If `Q` is `Q0?` the match holds under constraint set `C`:
NullabilitySuffix qNullability = q.nullabilitySuffix;
if (qNullability == NullabilitySuffix.question) {
TypeStructure q0 = typeAnalyzerOperations
.withNullabilitySuffix(new SharedTypeView(q), NullabilitySuffix.none)
.unwrapTypeView();
final TypeConstraintGeneratorState state = currentState;

// If `P` is `P0?` and `P0` is a subtype match for `Q0` under
// constraint set `C`.
NullabilitySuffix pNullability = p.nullabilitySuffix;
if (pNullability == NullabilitySuffix.question) {
TypeStructure p0 = typeAnalyzerOperations
.withNullabilitySuffix(
new SharedTypeView(p), NullabilitySuffix.none)
.unwrapTypeView();
if (performSubtypeConstraintGenerationInternal(p0, q0,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}
}

// Or if `P` is `dynamic` or `void` and `Object` is a subtype match
// for `Q0` under constraint set `C`.
if (p is SharedDynamicTypeStructure || p is SharedVoidTypeStructure) {
if (performSubtypeConstraintGenerationInternal(
typeAnalyzerOperations.objectType.unwrapTypeView(), q0,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}
}

// Or if `P` is a subtype match for `Q0` under non-empty
// constraint set `C`.
bool pMatchesQ0 = performSubtypeConstraintGenerationInternal(p, q0,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting);
if (pMatchesQ0 && state != currentState) {
return true;
}

// Or if `P` is a subtype match for `Null` under constraint set `C`.
if (performSubtypeConstraintGenerationInternal(
p, typeAnalyzerOperations.nullType.unwrapTypeView(),
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting)) {
return true;
}

// Or if `P` is a subtype match for `Q0` under empty
// constraint set `C`.
if (pMatchesQ0) {
return true;
}
}

return false;
}

/// Implementation backing [performSubtypeConstraintGenerationLeftSchema] and
/// [performSubtypeConstraintGenerationRightSchema].
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,74 @@ main() {
});
});

group('performSubtypeConstraintGenerationForLeftNullableType:', () {
test('Nullable matches nullable with constraints based on base types', () {
// `T? <# int?` reduces to `T <# int?`
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForLeftNullableType(
Type('T?'), Type('Null'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).deepEquals(['T <: Null']);
});

test('Nullable does not match Nullable because base types fail to match',
() {
// `int? <# String?` reduces to `int <# String`
var tcg = _TypeConstraintGatherer({});
check(tcg.performSubtypeConstraintGenerationForLeftNullableType(
Type('int?'), Type('String?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isFalse();
check(tcg._constraints).isEmpty();
});
});

group('performSubtypeConstraintGenerationForRightNullableType:', () {
test('Null matches Nullable favoring non-Null branch', () {
// `Null <# T?` could match in two possible ways:
// - `Null <# Null` (taking the "Null" branch of the FutureOr), producing
// the empty constraint set.
// - `Null <# T` (taking the "non-Null" branch of the FutureOr),
// producing `Null <: T`
// In cases where both branches produce a constraint, the "non-Null"
// branch is favored.
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForRightNullableType(
Type('Null'), Type('T?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).deepEquals(['Null <: T']);
});

test('Type matches Nullable favoring the non-Null branch', () {
// `T <# int?` could match in two possible ways:
// - `T <# Null` (taking the "Null" branch of the Nullable),
// producing `T <: Null`
// - `T <# int` (taking the "non-Null" branch of the Nullable),
// producing `T <: int`
// In cases where both branches produce a constraint, the "non-Null"
// branch is favored.
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForRightNullableType(
Type('T'), Type('int?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).deepEquals(['T <: int']);
});

test('Null matches Nullable with no constraints', () {
// `Null <# int?` matches (taking the "Null" branch of
// the Nullable) without generating any constraints.
var tcg = _TypeConstraintGatherer({});
check(tcg.performSubtypeConstraintGenerationForRightNullableType(
Type('Null'), Type('int?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).isEmpty();
});
});

group('performSubtypeConstraintGenerationForTypeDeclarationTypes', () {
group('Same base type on both sides:', () {
test('Covariant, matching', () {
Expand Down Expand Up @@ -768,6 +836,16 @@ class _TypeConstraintGatherer extends TypeConstraintGenerator<Type,
return true;
}

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

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

bool? result = performSubtypeConstraintGenerationForTypeDeclarationTypes(
p, q,
leftSchema: leftSchema, astNodeForTesting: astNodeForTesting);
Expand Down
68 changes: 6 additions & 62 deletions pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -218,53 +218,9 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
return true;
}

// If `Q` is `Q0?` the match holds under constraint set `C`:
if (Q_nullability == NullabilitySuffix.question) {
var Q0 = _typeSystemOperations
.withNullabilitySuffix(SharedTypeView(Q), NullabilitySuffix.none)
.unwrapTypeView();
var rewind = _constraints.length;

// If `P` is `P0?` and `P0` is a subtype match for `Q0` under
// constraint set `C`.
if (P_nullability == NullabilitySuffix.question) {
var P0 = _typeSystemOperations
.withNullabilitySuffix(SharedTypeView(P), NullabilitySuffix.none)
.unwrapTypeView();
if (trySubtypeMatch(P0, Q0, leftSchema,
nodeForTesting: nodeForTesting)) {
return true;
}
}

// Or if `P` is `dynamic` or `void` and `Object` is a subtype match
// for `Q0` under constraint set `C`.
if (P is SharedDynamicTypeStructure || P is SharedVoidTypeStructure) {
if (trySubtypeMatch(_typeSystem.objectNone, Q0, leftSchema,
nodeForTesting: nodeForTesting)) {
return true;
}
}

// Or if `P` is a subtype match for `Q0` under non-empty
// constraint set `C`.
var P_matches_Q0 =
trySubtypeMatch(P, Q0, leftSchema, nodeForTesting: nodeForTesting);
if (P_matches_Q0 && _constraints.length != rewind) {
return true;
}

// Or if `P` is a subtype match for `Null` under constraint set `C`.
if (trySubtypeMatch(P, _typeSystem.nullNone, leftSchema,
nodeForTesting: nodeForTesting)) {
return true;
}

// Or if `P` is a subtype match for `Q0` under empty
// constraint set `C`.
if (P_matches_Q0) {
return true;
}
if (performSubtypeConstraintGenerationForRightNullableType(P, Q,
leftSchema: leftSchema, astNodeForTesting: nodeForTesting)) {
return true;
}

// If `P` is `FutureOr<P0>` the match holds under constraint set `C1 + C2`:
Expand All @@ -285,21 +241,9 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
}

// If `P` is `P0?` the match holds under constraint set `C1 + C2`:
if (P_nullability == NullabilitySuffix.question) {
var P0 = _typeSystemOperations
.withNullabilitySuffix(SharedTypeView(P), NullabilitySuffix.none)
.unwrapTypeView();
var rewind = _constraints.length;

// If `P0` is a subtype match for `Q` under constraint set `C1`.
// And if `Null` is a subtype match for `Q` under constraint set `C2`.
if (trySubtypeMatch(P0, Q, leftSchema, nodeForTesting: nodeForTesting) &&
trySubtypeMatch(_typeSystem.nullNone, Q, leftSchema,
nodeForTesting: nodeForTesting)) {
return true;
}

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

// If `Q` is `dynamic`, `Object?`, or `void` then the match holds under
Expand Down
69 changes: 9 additions & 60 deletions pkg/front_end/lib/src/type_inference/type_constraint_gatherer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -304,55 +304,17 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
// Or if P is a subtype match for Q0 under non-empty constraint set C.
// Or if P is a subtype match for Null under constraint set C.
// Or if P is a subtype match for Q0 under empty constraint set C.
if (qNullability == NullabilitySuffix.question) {
final int baseConstraintCount = _protoConstraints.length;
final DartType rawP = typeOperations
.withNullabilitySuffix(new SharedTypeView(p), NullabilitySuffix.none)
.unwrapTypeView();
final DartType rawQ = typeOperations
.withNullabilitySuffix(new SharedTypeView(q), NullabilitySuffix.none)
.unwrapTypeView();

if (pNullability == NullabilitySuffix.question &&
_isNullabilityAwareSubtypeMatch(rawP, rawQ,
constrainSupertype: constrainSupertype,
treeNodeForTesting: treeNodeForTesting)) {
return true;
}

if ((p is SharedDynamicTypeStructure || p is SharedVoidTypeStructure) &&
_isNullabilityAwareSubtypeMatch(
typeOperations.objectType.unwrapTypeView(), rawQ,
constrainSupertype: constrainSupertype,
treeNodeForTesting: treeNodeForTesting)) {
return true;
}

bool isMatchWithRawQ = _isNullabilityAwareSubtypeMatch(p, rawQ,
constrainSupertype: constrainSupertype,
treeNodeForTesting: treeNodeForTesting);
bool matchWithRawQAddsConstraints =
_protoConstraints.length != baseConstraintCount;
if (isMatchWithRawQ && matchWithRawQAddsConstraints) {
return true;
}

if (_isNullabilityAwareSubtypeMatch(
p, typeOperations.nullType.unwrapTypeView(),
constrainSupertype: constrainSupertype,
treeNodeForTesting: treeNodeForTesting)) {
return true;
}

if (isMatchWithRawQ && !matchWithRawQAddsConstraints) {
return true;
}
if (performSubtypeConstraintGenerationForRightNullableType(p, q,
leftSchema: constrainSupertype,
astNodeForTesting: treeNodeForTesting)) {
return true;
}

// If P is FutureOr<P0> the match holds under constraint set C1 + C2:
//
// 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(
Expand All @@ -372,23 +334,10 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
//
// If P0 is a subtype match for Q under constraint set C1.
// And if Null is a subtype match for Q under constraint set C2.
if (pNullability == NullabilitySuffix.question) {
final int baseConstraintCount = _protoConstraints.length;
if (_isNullabilityAwareSubtypeMatch(
typeOperations
.withNullabilitySuffix(
new SharedTypeView(p), NullabilitySuffix.none)
.unwrapTypeView(),
q,
constrainSupertype: constrainSupertype,
treeNodeForTesting: treeNodeForTesting) &&
_isNullabilityAwareSubtypeMatch(
typeOperations.nullType.unwrapTypeView(), q,
constrainSupertype: constrainSupertype,
treeNodeForTesting: treeNodeForTesting)) {
return true;
}
_protoConstraints.length = baseConstraintCount;
if (performSubtypeConstraintGenerationForLeftNullableType(p, q,
leftSchema: constrainSupertype,
astNodeForTesting: treeNodeForTesting)) {
return true;
}

// If Q is dynamic, Object?, or void then the match holds under no
Expand Down
2 changes: 0 additions & 2 deletions pkg/linter/analyzer_use_new_elements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,5 @@ lib/src/rules/use_build_context_synchronously.dart
lib/src/rules/use_late_for_private_fields_and_variables.dart
lib/src/util/dart_type_utilities.dart
lib/src/util/flutter_utils.dart
lib/src/util/leak_detector_visitor.dart
lib/src/util/scope.dart
test/rules/use_build_context_synchronously_test.dart
test/scope_util_test.dart
Loading

0 comments on commit e5c2c50

Please sign in to comment.