Skip to content

Commit

Permalink
Version 3.7.0-141.0.dev
Browse files Browse the repository at this point in the history
Merge 34a6cbc into dev
  • Loading branch information
Dart CI committed Nov 13, 2024
2 parents 66ab177 + 34a6cbc commit 0c76ac0
Show file tree
Hide file tree
Showing 69 changed files with 1,398 additions and 89 deletions.
10 changes: 10 additions & 0 deletions pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8355,6 +8355,16 @@ class Parser {
listener.beginMetadataStar(start.next!);
listener.endMetadataStar(/* count = */ 0);
}
// Having settled on a variable declaration possibly do some error recovery.
if (beforeType.next!.isA(TokenType.LT)) {
// E.g. `final <int> foo = [42];` where we're missing `List` before
// `<int>`.
insertSyntheticIdentifier(
beforeType, IdentifierContext.localVariableDeclaration,
message:
codes.templateExpectedIdentifier.withArguments(beforeType.next!));
typeInfo = computeType(beforeType, /* required = */ true);
}
token = typeInfo.parseType(beforeType, this);
next = token.next!;
listener.beginVariablesDeclaration(next, lateToken, varFinalOrConst);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,13 @@ abstract interface class TypeAnalyzerOperations<
SharedTypeView<TypeStructure> toType);

/// Returns `true` if [type] is `Function` from `dart:core`. The method
/// returns `false` for `Object?` and `Object*`.
/// returns `false` for `Function?` and `Function*`.
bool isDartCoreFunction(SharedTypeView<TypeStructure> type);

/// Returns `true` if [type] is `Record` from `dart:core`. The method
/// returns `false` for `Record?` and `Record*`.
bool isDartCoreRecord(SharedTypeView<TypeStructure> type);

/// Returns `true` if [type] is `E<T1, ..., Tn>`, `E<T1, ..., Tn>?`, or
/// `E<T1, ..., Tn>*` for some extension type declaration E, some
/// non-negative n, and some types T1, ..., Tn.
Expand Down Expand Up @@ -1266,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 @@ -1312,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
9 changes: 9 additions & 0 deletions pkg/_fe_analyzer_shared/test/mini_ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2959,6 +2959,15 @@ class MiniAstOperations
unwrappedType.args.isEmpty;
}

@override
bool isDartCoreRecord(SharedTypeView<Type> type) {
Type unwrappedType = type.unwrapTypeView();
return unwrappedType is PrimaryType &&
unwrappedType.nullabilitySuffix == NullabilitySuffix.none &&
unwrappedType.name == 'Record' &&
unwrappedType.args.isEmpty;
}

@override
bool isExtensionType(SharedTypeView<Type> type) {
// TODO(cstefantsova): Add the support for extension types in the mini ast
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 @@ -538,6 +568,34 @@ main() {
.isFalse();
check(tcg._constraints).isEmpty();
});

test('Nullable does not match non-nullable', () {
// `(int, T)? <# (int, String)` does not match
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForLeftNullableType(
Type('(int, T)?'), Type('(int, String)'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isFalse();
check(tcg._constraints).isEmpty();
});

test('Both LHS and RHS nullable, matching', () {
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForRightNullableType(
Type('T?'), Type('int?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).deepEquals(['T <: int']);
});

test('Both LHS and RHS nullable, not matching', () {
var tcg = _TypeConstraintGatherer({'T'});
check(tcg.performSubtypeConstraintGenerationForRightNullableType(
Type('(T, int)?'), Type('(int, String)?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isFalse();
check(tcg._constraints).isEmpty();
});
});

group('performSubtypeConstraintGenerationForRightNullableType:', () {
Expand Down Expand Up @@ -583,6 +641,33 @@ main() {
.isTrue();
check(tcg._constraints).isEmpty();
});

test('Dynamic matches Object?', () {
var tcg = _TypeConstraintGatherer({});
check(tcg.performSubtypeConstraintGenerationForRightNullableType(
Type('dynamic'), Type('Object?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).isEmpty();
});

test('void matches Object?', () {
var tcg = _TypeConstraintGatherer({});
check(tcg.performSubtypeConstraintGenerationForRightNullableType(
Type('void'), Type('Object?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).isEmpty();
});

test('LHS not nullable, matches with no constraints', () {
var tcg = _TypeConstraintGatherer({});
check(tcg.performSubtypeConstraintGenerationForRightNullableType(
Type('int'), Type('int?'),
leftSchema: false, astNodeForTesting: Node.placeholder()))
.isTrue();
check(tcg._constraints).isEmpty();
});
});

group('performSubtypeConstraintGenerationForTypeDeclarationTypes', () {
Expand Down Expand Up @@ -789,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 @@ -836,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
21 changes: 5 additions & 16 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 Expand Up @@ -368,7 +357,7 @@ class TypeConstraintGatherer extends shared.TypeConstraintGenerator<
// A type `P` is a subtype match for `Record` with respect to `L` under no
// constraints:
// If `P` is a record type or `Record`.
if (Q_nullability == NullabilitySuffix.none && Q.isDartCoreRecord) {
if (_typeSystemOperations.isDartCoreRecord(SharedTypeView(Q))) {
if (P is SharedRecordTypeStructure<DartType>) {
return true;
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,12 @@ class TypeSystemOperations
type.unwrapTypeView().isDartCoreFunction;
}

@override
bool isDartCoreRecord(SharedTypeView<DartType> type) {
return type.nullabilitySuffix == NullabilitySuffix.none &&
type.unwrapTypeView().isDartCoreRecord;
}

@override
bool isExtensionType(SharedTypeView<DartType> type) {
DartType unwrappedType = type.unwrapTypeView();
Expand Down
Loading

0 comments on commit 0c76ac0

Please sign in to comment.