Skip to content

Commit 8e089c6

Browse files
author
Dart CI
committed
Version 2.19.0-367.0.dev
Merge 6539e20 into dev
2 parents 277d641 + 6539e20 commit 8e089c6

File tree

27 files changed

+1552
-997
lines changed

27 files changed

+1552
-997
lines changed

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

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,34 @@ class CaseHeadOrDefaultInfo<Node extends Object, Expression extends Node> {
2222
CaseHeadOrDefaultInfo({required this.pattern, this.guard});
2323
}
2424

25+
class NamedType<Type extends Object> {
26+
final String name;
27+
final Type type;
28+
29+
NamedType(this.name, this.type);
30+
}
31+
32+
class RecordPatternField<Pattern extends Object> {
33+
/// If not `null` then the field is named, otherwise it is positional.
34+
final String? name;
35+
final Pattern pattern;
36+
37+
RecordPatternField({
38+
required this.name,
39+
required this.pattern,
40+
});
41+
}
42+
43+
class RecordType<Type extends Object> {
44+
final List<Type> positional;
45+
final List<NamedType<Type>> named;
46+
47+
RecordType({
48+
required this.positional,
49+
required this.named,
50+
});
51+
}
52+
2553
/// Information about a relational operator.
2654
class RelationalOperatorResolution<Type extends Object> {
2755
/// Is `true` when the operator is `==` or `!=`.
@@ -533,6 +561,110 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
533561
}
534562
}
535563

564+
/// Analyzes a record pattern. [node] is the pattern itself, and [fields]
565+
/// is the list of subpatterns.
566+
///
567+
/// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and
568+
/// [context].
569+
///
570+
/// Stack effect: pushes (n * Pattern) where n = fields.length.
571+
Type analyzeRecordPattern(
572+
Type matchedType,
573+
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
574+
MatchContext<Node, Expression> context,
575+
Node node, {
576+
required List<RecordPatternField<Node>> fields,
577+
}) {
578+
void dispatchField(RecordPatternField<Node> field, Type matchedType) {
579+
dispatchPattern(matchedType, typeInfos, context, field.pattern);
580+
}
581+
582+
void dispatchFields(Type matchedType) {
583+
for (int i = 0; i < fields.length; i++) {
584+
dispatchField(fields[i], matchedType);
585+
}
586+
}
587+
588+
// Build the required type.
589+
int requiredTypePositionalCount = 0;
590+
List<NamedType<Type>> requiredTypeNamedTypes = [];
591+
for (RecordPatternField<Node> field in fields) {
592+
String? name = field.name;
593+
if (name == null) {
594+
requiredTypePositionalCount++;
595+
} else {
596+
requiredTypeNamedTypes.add(
597+
new NamedType(name, objectQuestionType),
598+
);
599+
}
600+
}
601+
Type requiredType = recordType(
602+
new RecordType(
603+
positional: new List.filled(
604+
requiredTypePositionalCount,
605+
objectQuestionType,
606+
),
607+
named: requiredTypeNamedTypes,
608+
),
609+
);
610+
611+
// Stack: ()
612+
RecordType<Type>? matchedRecordType = asRecordType(matchedType);
613+
if (matchedRecordType != null) {
614+
List<Type>? fieldTypes = _matchRecordTypeShape(fields, matchedRecordType);
615+
if (fieldTypes != null) {
616+
assert(fieldTypes.length == fields.length);
617+
for (int i = 0; i < fields.length; i++) {
618+
dispatchField(fields[i], fieldTypes[i]);
619+
}
620+
} else {
621+
dispatchFields(objectQuestionType);
622+
}
623+
} else if (typeOperations.isDynamic(matchedType)) {
624+
dispatchFields(dynamicType);
625+
} else {
626+
dispatchFields(objectQuestionType);
627+
}
628+
// Stack: (n * Pattern) where n = fields.length
629+
630+
Node? irrefutableContext = context.irrefutableContext;
631+
if (irrefutableContext != null &&
632+
!typeOperations.isAssignableTo(matchedType, requiredType)) {
633+
errors?.patternTypeMismatchInIrrefutableContext(
634+
pattern: node,
635+
context: irrefutableContext,
636+
matchedType: matchedType,
637+
requiredType: requiredType,
638+
);
639+
}
640+
return requiredType;
641+
}
642+
643+
/// Computes the type schema for a record pattern.
644+
///
645+
/// Stack effect: none.
646+
Type analyzeRecordPatternSchema({
647+
required List<RecordPatternField<Node>> fields,
648+
}) {
649+
List<Type> positional = [];
650+
List<NamedType<Type>> named = [];
651+
for (RecordPatternField<Node> field in fields) {
652+
Type fieldType = dispatchPatternSchema(field.pattern);
653+
String? name = field.name;
654+
if (name != null) {
655+
named.add(new NamedType(name, fieldType));
656+
} else {
657+
positional.add(fieldType);
658+
}
659+
}
660+
return recordType(
661+
new RecordType<Type>(
662+
positional: positional,
663+
named: named,
664+
),
665+
);
666+
}
667+
536668
/// Analyzes a relational pattern. [node] is the pattern itself, [operator]
537669
/// is the resolution of the used relational operator, and [operand] is a
538670
/// constant expression.
@@ -836,6 +968,9 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
836968
Type analyzeVariablePatternSchema(Type? declaredType) =>
837969
declaredType ?? unknownType;
838970

971+
/// If [type] is a record type, returns it.
972+
RecordType<Type>? asRecordType(Type type);
973+
839974
/// Calls the appropriate `analyze` method according to the form of
840975
/// [expression], and then adjusts the stack as needed to combine any
841976
/// sub-structures into a single expression.
@@ -1006,6 +1141,9 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
10061141
/// Returns the type `List`, with type parameter [elementType].
10071142
Type listType(Type elementType);
10081143

1144+
/// Builds the client specific record type.
1145+
Type recordType(RecordType<Type> type);
1146+
10091147
/// Records that type inference has assigned a [type] to a [variable]. This
10101148
/// is called once per variable, regardless of whether the variable's type is
10111149
/// explicit or inferred.
@@ -1046,6 +1184,49 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
10461184
}
10471185
}
10481186

1187+
/// If the shape described by [fields] is the same as the shape of the
1188+
/// [matchedType], returns matched types for each field in [fields].
1189+
/// Otherwise returns `null`.
1190+
List<Type>? _matchRecordTypeShape(
1191+
List<RecordPatternField<Node>> fields,
1192+
RecordType<Type> matchedType,
1193+
) {
1194+
Map<String, Type> matchedTypeNamed = {};
1195+
for (NamedType<Type> namedField in matchedType.named) {
1196+
matchedTypeNamed[namedField.name] = namedField.type;
1197+
}
1198+
1199+
List<Type> result = [];
1200+
int positionalIndex = 0;
1201+
int namedCount = 0;
1202+
for (RecordPatternField<Node> field in fields) {
1203+
Type? fieldType;
1204+
String? name = field.name;
1205+
if (name != null) {
1206+
fieldType = matchedTypeNamed[name];
1207+
if (fieldType == null) {
1208+
return null;
1209+
}
1210+
namedCount++;
1211+
} else {
1212+
if (positionalIndex >= matchedType.positional.length) {
1213+
return null;
1214+
}
1215+
fieldType = matchedType.positional[positionalIndex++];
1216+
}
1217+
result.add(fieldType);
1218+
}
1219+
if (positionalIndex != matchedType.positional.length) {
1220+
return null;
1221+
}
1222+
if (namedCount != matchedTypeNamed.length) {
1223+
return null;
1224+
}
1225+
1226+
assert(result.length == fields.length);
1227+
return result;
1228+
}
1229+
10491230
/// Records in [typeInfos] that a [pattern] binds a [variable] with a given
10501231
/// [staticType], and reports any errors caused by type inconsistency.
10511232
/// [isImplicitlyTyped] indicates whether the variable is implicitly typed in

pkg/_fe_analyzer_shared/test/mini_ast.dart

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart'
1010
show EqualityInfo, FlowAnalysis, Operations;
1111
import 'package:_fe_analyzer_shared/src/type_inference/assigned_variables.dart';
1212
import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart';
13-
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart';
13+
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
14+
hide NamedType, RecordType;
15+
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
16+
as shared;
1417
import 'package:_fe_analyzer_shared/src/type_inference/type_operations.dart';
1518
import 'package:_fe_analyzer_shared/src/type_inference/variable_bindings.dart';
1619
import 'package:test/test.dart';
@@ -119,6 +122,11 @@ Statement do_(List<Statement> body, Expression condition) {
119122
Expression expr(String typeStr) =>
120123
new _PlaceholderExpression(new Type(typeStr), location: computeLocation());
121124

125+
/// Creates a pseudo-expression having type [type] that otherwise has no
126+
/// effect on flow analysis.
127+
Expression expr2(Type type) =>
128+
new _PlaceholderExpression(type, location: computeLocation());
129+
122130
/// Creates a conventional `for` statement. Optional boolean [forCollection]
123131
/// indicates that this `for` statement is actually a collection element, so
124132
/// `null` should be passed to [for_bodyBegin].
@@ -220,6 +228,9 @@ Statement match(Pattern pattern, Expression initializer,
220228

221229
CaseHeads mergedCase(List<CaseHead> cases) => _CaseHeads(cases, const []);
222230

231+
Pattern recordPattern(List<RecordPatternField<Pattern>> fields) =>
232+
_RecordPattern(fields, location: computeLocation());
233+
223234
Pattern relationalPattern(
224235
RelationalOperatorResolution<Type>? operator, Expression operand) =>
225236
_RelationalPattern(operator, operand, location: computeLocation());
@@ -2692,6 +2703,22 @@ class _MiniAstTypeAnalyzer
26922703
flow.whileStatement_end();
26932704
}
26942705

2706+
@override
2707+
shared.RecordType<Type>? asRecordType(Type type) {
2708+
if (type is RecordType) {
2709+
return shared.RecordType<Type>(
2710+
positional: type.positional,
2711+
named: type.named.map((namedType) {
2712+
return shared.NamedType(
2713+
namedType.name,
2714+
namedType.innerType,
2715+
);
2716+
}).toList(),
2717+
);
2718+
}
2719+
return null;
2720+
}
2721+
26952722
@override
26962723
ExpressionTypeAnalysisResult<Type> dispatchExpression(
26972724
Expression expression, Type context) =>
@@ -2840,6 +2867,14 @@ class _MiniAstTypeAnalyzer
28402867
return _harness.getMember(receiverType, memberName);
28412868
}
28422869

2870+
@override
2871+
RecordType recordType(shared.RecordType<Type> type) {
2872+
return RecordType(
2873+
positional: type.positional,
2874+
named: type.named.map((e) => NamedType(e.name, e.type)).toList(),
2875+
);
2876+
}
2877+
28432878
@override
28442879
void setVariableType(Var variable, Type type) {
28452880
variable.type = type;
@@ -3089,6 +3124,57 @@ class _PropertyElement {
30893124
_PropertyElement(this._type);
30903125
}
30913126

3127+
class _RecordPattern extends Pattern {
3128+
final List<RecordPatternField<Pattern>> fields;
3129+
3130+
_RecordPattern(this.fields, {required super.location}) : super._();
3131+
3132+
Type computeSchema(Harness h) {
3133+
return h.typeAnalyzer.analyzeRecordPatternSchema(
3134+
fields: fields,
3135+
);
3136+
}
3137+
3138+
@override
3139+
void preVisit(
3140+
PreVisitor visitor,
3141+
VariableBinder<Node, Var, Type> variableBinder,
3142+
) {
3143+
for (var field in fields) {
3144+
field.pattern.preVisit(visitor, variableBinder);
3145+
}
3146+
}
3147+
3148+
void visit(
3149+
Harness h,
3150+
Type matchedType,
3151+
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
3152+
MatchContext<Node, Expression> context,
3153+
) {
3154+
var requiredType = h.typeAnalyzer.analyzeRecordPattern(
3155+
matchedType, typeInfos, context, this,
3156+
fields: fields);
3157+
h.irBuilder.atom(matchedType.type, Kind.type, location: location);
3158+
h.irBuilder.atom(requiredType.type, Kind.type, location: location);
3159+
h.irBuilder.apply(
3160+
'recordPattern',
3161+
[...List.filled(fields.length, Kind.pattern), Kind.type, Kind.type],
3162+
Kind.pattern,
3163+
names: ['matchedType', 'requiredType'],
3164+
location: location,
3165+
);
3166+
}
3167+
3168+
@override
3169+
String _debugString({required bool needsKeywordOrType}) {
3170+
var fieldStrings = [
3171+
for (var field in fields)
3172+
field.pattern._debugString(needsKeywordOrType: needsKeywordOrType)
3173+
];
3174+
return '(${fieldStrings.join(', ')})';
3175+
}
3176+
}
3177+
30923178
class _RelationalPattern extends Pattern {
30933179
final RelationalOperatorResolution<Type>? operator;
30943180
final Expression operand;

0 commit comments

Comments
 (0)