diff --git a/pkg/analyzer/lib/dart/ast/ast.dart b/pkg/analyzer/lib/dart/ast/ast.dart index f3d70ae6657..7347f472d08 100644 --- a/pkg/analyzer/lib/dart/ast/ast.dart +++ b/pkg/analyzer/lib/dart/ast/ast.dart @@ -4083,9 +4083,18 @@ abstract class PropertyAccess /// Clients may not extend, implement or mix-in this class. @experimental abstract class RecordLiteral implements Literal { + /// Return the token representing the 'const' keyword, or `null` if the + /// literal is not a constant. + Token? get constKeyword; + /// Return the syntactic elements used to compute the fields of the record. NodeList get fields; + /// Return `true` if this literal is a constant expression, either because the + /// keyword `const` was explicitly provided or because no keyword was provided + /// and this expression is in a constant context. + bool get isConst; + /// Return the left parenthesis. Token get leftParenthesis; diff --git a/pkg/analyzer/lib/dart/element/type_provider.dart b/pkg/analyzer/lib/dart/element/type_provider.dart index 2d4aeec42b7..a3d31835310 100644 --- a/pkg/analyzer/lib/dart/element/type_provider.dart +++ b/pkg/analyzer/lib/dart/element/type_provider.dart @@ -102,6 +102,9 @@ abstract class TypeProvider { /// Return the element representing the built-in class `Record`. ClassElement get recordElement; + /// Return the type representing the built-in type `Record`. + InterfaceType get recordType; + /// Return the element representing the built-in class `Set`. ClassElement get setElement; diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart index 06e42398d1a..d35be48d266 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart @@ -4206,7 +4206,8 @@ abstract class ExpressionImpl extends AstNodeImpl child is MapLiteralEntry || child is SpreadElement || child is IfElement || - child is ForElement) { + child is ForElement || + child is RecordLiteral) { var parent = child.parent; if (parent is ConstantContextForExpressionImpl) { return true; @@ -9636,6 +9637,9 @@ class PropertyAccessImpl extends CommentReferableExpressionImpl } class RecordLiteralImpl extends LiteralImpl implements RecordLiteral { + @override + Token? constKeyword; + @override Token leftParenthesis; @@ -9647,14 +9651,15 @@ class RecordLiteralImpl extends LiteralImpl implements RecordLiteral { /// Initialize a newly created record literal. RecordLiteralImpl( - {required this.leftParenthesis, + {this.constKeyword, + required this.leftParenthesis, required List fields, required this.rightParenthesis}) { _fields._initialize(this, fields); } @override - Token get beginToken => leftParenthesis; + Token get beginToken => constKeyword ?? leftParenthesis; @override Token get endToken => rightParenthesis; @@ -9662,9 +9667,13 @@ class RecordLiteralImpl extends LiteralImpl implements RecordLiteral { @override NodeList get fields => _fields; + @override + bool get isConst => constKeyword != null || inConstantContext; + @override // TODO(paulberry): add commas. ChildEntities get _childEntities => super._childEntities + ..addToken('constKeyword', constKeyword) ..addToken('leftParenthesis', leftParenthesis) ..addNodeList('fields', fields) ..addToken('rightParenthesis', rightParenthesis); diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart index d215465ba80..2526e91107d 100644 --- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart +++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart @@ -1012,6 +1012,41 @@ class ConstantVisitor extends UnifyingAstVisitor { return _getConstantValue(node, node.propertyName); } + @override + DartObjectImpl? visitRecordLiteral(RecordLiteral node) { + if (!node.isConst) { + // TODO(brianwilkerson) Merge the error codes into a single error code or + // declare a new error code specific to records. + _errorReporter.reportErrorForNode( + CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL, node); + return null; + } + var nodeType = node.staticType; + if (nodeType == null) { + return null; + } + var positionalFields = []; + var namedFields = {}; + for (var field in node.fields) { + if (field is NamedExpression) { + var name = field.name.label.name; + var value = field.expression.accept(this); + if (value == null) { + return null; + } + namedFields[name] = value; + } else { + var value = field.accept(this); + if (value == null) { + return null; + } + positionalFields.add(value); + } + } + return DartObjectImpl( + typeSystem, nodeType, RecordState(positionalFields, namedFields)); + } + @override DartObjectImpl? visitSetOrMapLiteral(SetOrMapLiteral node) { // Note: due to dartbug.com/33441, it's possible that a set/map literal diff --git a/pkg/analyzer/lib/src/dart/element/type_provider.dart b/pkg/analyzer/lib/src/dart/element/type_provider.dart index d3137546652..28033ac31f8 100644 --- a/pkg/analyzer/lib/src/dart/element/type_provider.dart +++ b/pkg/analyzer/lib/src/dart/element/type_provider.dart @@ -397,6 +397,7 @@ class TypeProviderImpl extends TypeProviderBase { return _recordElement ??= _getClassElement(_coreLibrary, 'Record'); } + @override InterfaceType get recordType { return _recordType ??= recordElement.instantiate( typeArguments: const [], diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart index 6d3e51a9efd..a3cc55b0ffa 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart @@ -1029,8 +1029,10 @@ class AstBinaryReader { } RecordLiteralImpl _readRecordLiteral() { - final fields = _readNodeList(); - final node = RecordLiteralImpl( + var flags = _readByte(); + var fields = _readNodeList(); + var node = RecordLiteralImpl( + constKeyword: AstBinaryFlags.isConst(flags) ? Tokens.const_() : null, leftParenthesis: Tokens.openParenthesis(), fields: fields, rightParenthesis: Tokens.closeParenthesis(), diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart index 31b5453274b..69cc4931c45 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart @@ -630,6 +630,11 @@ class AstBinaryWriter extends ThrowingAstVisitor { @override void visitRecordLiteral(RecordLiteral node) { _writeByte(Tag.RecordLiteral); + _writeByte( + AstBinaryFlags.encode( + isConst: node.constKeyword != null, + ), + ); _writeNodeList(node.fields); _storeExpression(node); } diff --git a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart index 04f49ccb94c..61cfd12c6d8 100644 --- a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart +++ b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart @@ -5,6 +5,7 @@ import 'package:analyzer/dart/analysis/features.dart'; 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/error/error.dart'; import 'package:analyzer/error/listener.dart'; import 'package:analyzer/src/dart/element/element.dart'; @@ -810,6 +811,25 @@ const void Function(int) g = self.C.f; _assertTypeArguments(result, ['int']); } + test_visitRecordLiteral_withoutEnvironment() async { + await resolveTestCode(r''' +const a = (1, 'b', c: false); +'''); + var result = _evaluateConstant('a'); + var type = result.type; + if (type is! RecordType) { + fail('Expected a record type'); + } + var positionalFields = type.positionalFields; + var namedFields = type.namedFields; + expect(positionalFields, hasLength(2)); + expect(positionalFields[0].type, typeProvider.intType); + expect(positionalFields[1].type, typeProvider.stringType); + expect(namedFields, hasLength(1)); + expect(namedFields[0].name, 'c'); + expect(namedFields[0].type, typeProvider.boolType); + } + test_visitSimpleIdentifier_className() async { await resolveTestCode(''' const a = C; diff --git a/pkg/analyzer/test/src/dart/constant/value_test.dart b/pkg/analyzer/test/src/dart/constant/value_test.dart index 063ca761667..08999f7ff00 100644 --- a/pkg/analyzer/test/src/dart/constant/value_test.dart +++ b/pkg/analyzer/test/src/dart/constant/value_test.dart @@ -2299,8 +2299,7 @@ class DartObjectImplTest { ) { return DartObjectImpl( _typeSystem, - _typeProvider.recordElement.instantiate( - typeArguments: [], nullabilitySuffix: NullabilitySuffix.none), + _typeProvider.recordType, RecordState(positionalFields, namedFields), ); }