blob: f22a407e76c903ce1d9c35600a7b1d508fe194c6 [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// 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/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/codes.g.dart';
import 'package:analyzer/src/generated/resolver.dart';
/// Helper for resolving [RecordLiteral]s.
class RecordLiteralResolver {
final ResolverVisitor _resolver;
RecordLiteralResolver({
required ResolverVisitor resolver,
}) : _resolver = resolver;
ErrorReporter get errorReporter => _resolver.errorReporter;
/// Report any named fields in the record literal [node] that use a previously
/// defined name.
void reportDuplicateFieldDefinitions(RecordLiteralImpl node) {
var usedNames = <String, NamedExpression>{};
for (var field in node.fields) {
if (field is NamedExpression) {
var name = field.name.label.name;
var previousField = usedNames[name];
if (previousField != null) {
errorReporter.reportError(DiagnosticFactory()
.duplicateFieldDefinitionInLiteral(
errorReporter.source, field, previousField));
} else {
usedNames[name] = field;
}
}
}
}
/// Report any fields in the record literal [node] that use an invalid name.
void reportInvalidFieldNames(RecordLiteralImpl node) {
var fields = node.fields;
var positionalCount = 0;
for (var field in fields) {
if (field is! NamedExpression) {
positionalCount++;
}
}
for (var field in fields) {
if (field is NamedExpression) {
var nameNode = field.name.label;
var name = nameNode.name;
if (name.startsWith('_')) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_FIELD_NAME_PRIVATE, nameNode);
} else {
final index = RecordTypeExtension.positionalFieldIndex(name);
if (index != null) {
if (index < positionalCount) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_FIELD_NAME_POSITIONAL, nameNode);
}
} else {
var objectElement = _resolver.typeProvider.objectElement;
if (objectElement.getGetter(name) != null ||
objectElement.getMethod(name) != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_FIELD_NAME_FROM_OBJECT,
nameNode);
}
}
}
}
}
}
void resolve(
RecordLiteralImpl node, {
required DartType? contextType,
}) {
_resolveFields(node, contextType);
_buildType(node, contextType);
reportDuplicateFieldDefinitions(node);
reportInvalidFieldNames(node);
}
void _buildType(RecordLiteralImpl node, DartType? contextType) {
final positionalFields = <RecordTypePositionalFieldImpl>[];
final namedFields = <RecordTypeNamedFieldImpl>[];
for (final field in node.fields) {
final fieldType = field.typeOrThrow;
if (field is NamedExpression) {
namedFields.add(
RecordTypeNamedFieldImpl(
name: field.name.label.name,
type: fieldType,
),
);
} else {
positionalFields.add(
RecordTypePositionalFieldImpl(
type: fieldType,
),
);
}
}
_resolver.inferenceHelper.recordStaticType(
node,
RecordTypeImpl(
positionalFields: positionalFields,
namedFields: namedFields,
nullabilitySuffix: NullabilitySuffix.none,
),
contextType: contextType,
);
}
void _resolveField(Expression field, DartType? contextType) {
_resolver.analyzeExpression(field, contextType);
_resolver.popRewrite();
}
void _resolveFields(RecordLiteralImpl node, DartType? contextType) {
if (contextType is RecordType) {
var index = 0;
for (final field in node.fields) {
DartType? fieldContextType;
if (field is NamedExpression) {
final name = field.name.label.name;
fieldContextType = contextType.namedField(name)?.type;
} else {
final positionalFields = contextType.positionalFields;
if (index < positionalFields.length) {
fieldContextType = positionalFields[index++].type;
}
}
_resolveField(field, fieldContextType);
}
} else {
for (final field in node.fields) {
_resolveField(field, null);
}
}
}
}