Partial constant evaluation for record literals
Change-Id: I53db27f67ab1244b7e25a08002ae165840c7c012 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256544 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
committed by
Commit Bot
parent
a3f8672778
commit
d40f06ee2a
@@ -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<Expression> 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Expression> 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<Expression> 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);
|
||||
|
||||
@@ -1012,6 +1012,41 @@ class ConstantVisitor extends UnifyingAstVisitor<DartObjectImpl> {
|
||||
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 = <DartObjectImpl>[];
|
||||
var namedFields = <String, DartObjectImpl>{};
|
||||
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
|
||||
|
||||
@@ -397,6 +397,7 @@ class TypeProviderImpl extends TypeProviderBase {
|
||||
return _recordElement ??= _getClassElement(_coreLibrary, 'Record');
|
||||
}
|
||||
|
||||
@override
|
||||
InterfaceType get recordType {
|
||||
return _recordType ??= recordElement.instantiate(
|
||||
typeArguments: const [],
|
||||
|
||||
@@ -1029,8 +1029,10 @@ class AstBinaryReader {
|
||||
}
|
||||
|
||||
RecordLiteralImpl _readRecordLiteral() {
|
||||
final fields = _readNodeList<Expression>();
|
||||
final node = RecordLiteralImpl(
|
||||
var flags = _readByte();
|
||||
var fields = _readNodeList<Expression>();
|
||||
var node = RecordLiteralImpl(
|
||||
constKeyword: AstBinaryFlags.isConst(flags) ? Tokens.const_() : null,
|
||||
leftParenthesis: Tokens.openParenthesis(),
|
||||
fields: fields,
|
||||
rightParenthesis: Tokens.closeParenthesis(),
|
||||
|
||||
@@ -630,6 +630,11 @@ class AstBinaryWriter extends ThrowingAstVisitor<void> {
|
||||
@override
|
||||
void visitRecordLiteral(RecordLiteral node) {
|
||||
_writeByte(Tag.RecordLiteral);
|
||||
_writeByte(
|
||||
AstBinaryFlags.encode(
|
||||
isConst: node.constKeyword != null,
|
||||
),
|
||||
);
|
||||
_writeNodeList(node.fields);
|
||||
_storeExpression(node);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2299,8 +2299,7 @@ class DartObjectImplTest {
|
||||
) {
|
||||
return DartObjectImpl(
|
||||
_typeSystem,
|
||||
_typeProvider.recordElement.instantiate(
|
||||
typeArguments: [], nullabilitySuffix: NullabilitySuffix.none),
|
||||
_typeProvider.recordType,
|
||||
RecordState(positionalFields, namedFields),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user