@@ -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.
2654class 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
0 commit comments