blob: d0317d95be21121673306f9617381300e7bd692d [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/analysis/features.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.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/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/resolver/record_literal_resolver.dart';
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/codes.g.dart';
/// Helper for resolving [RecordTypeAnnotation]s.
class RecordTypeAnnotationResolver {
final TypeProviderImpl typeProvider;
final DiagnosticReporter _diagnosticReporter;
final LibraryElement libraryElement;
RecordTypeAnnotationResolver({
required this.typeProvider,
required DiagnosticReporter diagnosticReporter,
required this.libraryElement,
}) : _diagnosticReporter = diagnosticReporter;
bool get isWildCardVariablesEnabled =>
libraryElement.featureSet.isEnabled(Feature.wildcard_variables);
bool isPositionalWildCard(AstNode field, String name) =>
field is RecordTypeAnnotationPositionalField &&
name == '_' &&
isWildCardVariablesEnabled;
/// Report any named fields in the record type [node] that use a previously
/// defined name.
void reportDuplicateFieldDefinitions(RecordTypeAnnotationImpl node) {
var usedNames = <String, RecordTypeAnnotationField>{};
for (var field in node.fields) {
var name = field.name?.lexeme;
if (name != null) {
// Multiple positional `_`s are legal with wildcards.
if (isPositionalWildCard(field, name)) continue;
var previousField = usedNames[name];
if (previousField != null) {
_diagnosticReporter.reportError(
DiagnosticFactory().duplicateFieldDefinitionInType(
_diagnosticReporter.source,
field,
previousField,
),
);
} else {
usedNames[name] = field;
}
}
}
}
/// Report any fields in the record type [node] that use an invalid name.
void reportInvalidFieldNames(RecordTypeAnnotationImpl node) {
var positionalFields = node.positionalFields;
var positionalCount = positionalFields.length;
for (var field in node.fields) {
var nameToken = field.name;
if (nameToken != null) {
var name = nameToken.lexeme;
if (name.startsWith('_')) {
// Positional record fields named `_` are legal w/ wildcards.
if (!isPositionalWildCard(field, name)) {
_diagnosticReporter.atToken(
nameToken,
CompileTimeErrorCode.INVALID_FIELD_NAME_PRIVATE,
);
}
} else {
var index = RecordTypeExtension.positionalFieldIndex(name);
if (index != null) {
if (index < positionalCount &&
positionalFields.indexOf(field) != index) {
_diagnosticReporter.atToken(
nameToken,
CompileTimeErrorCode.INVALID_FIELD_NAME_POSITIONAL,
);
}
} else if (RecordLiteralResolver.isForbiddenNameForRecordField(
name,
)) {
_diagnosticReporter.atToken(
nameToken,
CompileTimeErrorCode.INVALID_FIELD_NAME_FROM_OBJECT,
);
}
}
}
}
}
void resolve(RecordTypeAnnotationImpl node) {
_buildType(node);
reportDuplicateFieldDefinitions(node);
reportInvalidFieldNames(node);
}
void _buildType(RecordTypeAnnotationImpl node) {
var positionalFields =
node.positionalFields.map((field) {
return RecordTypePositionalFieldImpl(type: field.type.typeOrThrow);
}).toList();
var namedFields =
node.namedFields?.fields.map((field) {
return RecordTypeNamedFieldImpl(
name: field.name.lexeme,
type: field.type.typeOrThrow,
);
}).toList();
node.type = RecordTypeImpl(
positionalFields: positionalFields,
namedFields: namedFields ?? const [],
nullabilitySuffix:
node.question != null
? NullabilitySuffix.question
: NullabilitySuffix.none,
);
}
}