Resolve record literals

I'm reasonably confident that this is incomplete, but I'm hoping that
it's a least headed in the right direction.

There aren't any tests because I believe that our resolution tests are
all based on source code, and we can't yet parse record literals. I'm
happy to hold off on landing this CL until we have the parser work
landed and the AstBuilder updated so that I can have tests to go with
the changes.

Change-Id: I7cd2a9b3a2e496c1220d569b36497dc326ff6915
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255146
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 3cbeb05..26e9a4e 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -2180,12 +2180,39 @@
     covariant RecordLiteralImpl node, {
     DartType? contextType,
   }) {
-    // TODO(brianwilkerson) Implement resolution for record literals.
     checkUnreachableNode(node);
-    for (final field in node.fields) {
-      analyzeExpression(field, null);
+    if (contextType is RecordType) {
+      var positionalFields = contextType.positionalFields;
+      var namedFields = contextType.namedFields;
+
+      RecordTypeNamedField? getNamedField(String name) {
+        for (var field in namedFields) {
+          if (field.name == name) {
+            return field;
+          }
+        }
+        return null;
+      }
+
+      var index = 0;
+      for (var field in node.fields) {
+        DartType? fieldContextType;
+        if (field is NamedExpression) {
+          var name = field.name.label.name;
+          fieldContextType = getNamedField(name)?.type;
+        } else {
+          if (index < positionalFields.length) {
+            fieldContextType = positionalFields[index++].type;
+          }
+        }
+        analyzeExpression(field, fieldContextType);
+      }
+    } else {
+      for (var field in node.fields) {
+        analyzeExpression(field, null);
+      }
     }
-    node.staticType = typeProvider.dynamicType;
+    typeAnalyzer.visitRecordLiteral(node, contextType: contextType);
   }
 
   @override
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index 946b08f..027fbad 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -3,9 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
+import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/type_provider.dart';
 import 'package:analyzer/src/dart/element/type_system.dart';
 import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
@@ -218,6 +220,30 @@
         contextType: contextType);
   }
 
+  void visitRecordLiteral(RecordLiteralImpl node, {DartType? contextType}) {
+    var positionalFields = <RecordPositionalFieldElementImpl>[];
+    var namedFields = <RecordNamedFieldElementImpl>[];
+    for (var field in node.fields) {
+      var fieldType = field.typeOrThrow;
+      if (field is NamedExpression) {
+        var label = field.name.label;
+        namedFields.add(RecordNamedFieldElementImpl(
+            name: label.name, nameOffset: label.offset, type: fieldType));
+      } else {
+        positionalFields.add(RecordPositionalFieldElementImpl(
+            name: '', nameOffset: -1, type: fieldType));
+      }
+    }
+    var element = RecordElementImpl(
+        positionalFields: positionalFields, namedFields: namedFields);
+    element.isSynthetic = true;
+    // TODO(brianwilkerson) Figure out how to get the element for the compilation unit.
+    // element.enclosingElement = (node.root as CompilationUnit).declaredElement!;
+    _inferenceHelper.recordStaticType(
+        node, element.instantiate(nullabilitySuffix: NullabilitySuffix.none),
+        contextType: contextType);
+  }
+
   /// The Dart Language Specification, 12.9: <blockquote>The static type of a rethrow expression is
   /// bottom.</blockquote>
   void visitRethrowExpression(covariant RethrowExpressionImpl node,
diff --git a/pkg/analyzer/test/src/dart/resolution/record_literal_test.dart b/pkg/analyzer/test/src/dart/resolution/record_literal_test.dart
index 83886ac..fefc092 100644
--- a/pkg/analyzer/test/src/dart/resolution/record_literal_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/record_literal_test.dart
@@ -15,8 +15,14 @@
 @reflectiveTest
 class RecordLiteralTest extends PubPackageResolutionTest {
   test_noContext_mixed() async {
+    // Restore the code below when record types can be serialized.
+//     await assertNoErrorsInCode(r'''
+// final x = (0, f1: 1, 2, f2: 3, 4);
+// ''');
     await assertNoErrorsInCode(r'''
-final x = (0, f1: 1, 2, f2: 3, 4);
+void f() {
+  (0, f1: 1, 2, f2: 3, 4);
+}
 ''');
 
     final node = findNode.recordLiteral('(0,');
@@ -54,13 +60,19 @@
       literal: 4
       staticType: int
   rightParenthesis: )
-  staticType: dynamic
+  staticType: (int, int, int, {int f1, int f2})
 ''');
   }
 
   test_noContext_named() async {
+    // Restore the code below when record types can be serialized.
+//     await assertNoErrorsInCode(r'''
+// final x = (f1: 0, f2: true);
+// ''');
     await assertNoErrorsInCode(r'''
-final x = (f1: 0, f2: true);
+void f() {
+  (f1: 0, f2: true);
+}
 ''');
 
     final node = findNode.recordLiteral('(f1:');
@@ -89,13 +101,19 @@
         literal: true
         staticType: bool
   rightParenthesis: )
-  staticType: dynamic
+  staticType: ({int f1, bool f2})
 ''');
   }
 
   test_noContext_positional() async {
+    // Restore the code below when record types can be serialized.
+//     await assertNoErrorsInCode(r'''
+// final x = (0, true);
+// ''');
     await assertNoErrorsInCode(r'''
-final x = (0, true);
+void f() {
+  (0, true);
+}
 ''');
 
     final node = findNode.recordLiteral('(0,');
@@ -110,7 +128,7 @@
       literal: true
       staticType: bool
   rightParenthesis: )
-  staticType: dynamic
+  staticType: (int, bool)
 ''');
   }
 }