@@ -22,6 +22,34 @@ class CaseHeadOrDefaultInfo<Node extends Object, Expression extends Node> {
22
22
CaseHeadOrDefaultInfo ({required this .pattern, this .guard});
23
23
}
24
24
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
+
25
53
/// Information about a relational operator.
26
54
class RelationalOperatorResolution <Type extends Object > {
27
55
/// Is `true` when the operator is `==` or `!=` .
@@ -533,6 +561,110 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
533
561
}
534
562
}
535
563
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
+
536
668
/// Analyzes a relational pattern. [node] is the pattern itself, [operator]
537
669
/// is the resolution of the used relational operator, and [operand] is a
538
670
/// constant expression.
@@ -836,6 +968,9 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
836
968
Type analyzeVariablePatternSchema (Type ? declaredType) =>
837
969
declaredType ?? unknownType;
838
970
971
+ /// If [type] is a record type, returns it.
972
+ RecordType <Type >? asRecordType (Type type);
973
+
839
974
/// Calls the appropriate `analyze` method according to the form of
840
975
/// [expression] , and then adjusts the stack as needed to combine any
841
976
/// sub-structures into a single expression.
@@ -1006,6 +1141,9 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
1006
1141
/// Returns the type `List` , with type parameter [elementType] .
1007
1142
Type listType (Type elementType);
1008
1143
1144
+ /// Builds the client specific record type.
1145
+ Type recordType (RecordType <Type > type);
1146
+
1009
1147
/// Records that type inference has assigned a [type] to a [variable] . This
1010
1148
/// is called once per variable, regardless of whether the variable's type is
1011
1149
/// explicit or inferred.
@@ -1046,6 +1184,49 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
1046
1184
}
1047
1185
}
1048
1186
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
+
1049
1230
/// Records in [typeInfos] that a [pattern] binds a [variable] with a given
1050
1231
/// [staticType] , and reports any errors caused by type inconsistency.
1051
1232
/// [isImplicitlyTyped] indicates whether the variable is implicitly typed in
0 commit comments