blob: 1e8b170b32d61d845587a497bbab9193bf9e2b55 [file]
// Copyright (c) 2019, 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/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/error/listener.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
class DuplicateDefinitionVerifier {
final LibraryElementImpl _currentLibrary;
final DiagnosticReporter _diagnosticReporter;
final DiagnosticFactory _diagnosticFactory = DiagnosticFactory();
final Set<Token> _reportedTokens = Set.identity();
DuplicateDefinitionVerifier(this._currentLibrary, this._diagnosticReporter);
/// Check that the exception and stack trace parameters have different names.
void checkCatchClause(CatchClause node) {
var exceptionParameter = node.exceptionParameter;
var stackTraceParameter = node.stackTraceParameter;
if (exceptionParameter != null && stackTraceParameter != null) {
var element = exceptionParameter.declaredFragment?.element;
if (element != null && element.isWildcardVariable) return;
String exceptionName = exceptionParameter.name.lexeme;
if (exceptionName == stackTraceParameter.name.lexeme) {
_diagnosticReporter.report(
_diagnosticFactory.duplicateDefinitionForNodes(
_diagnosticReporter.source,
diag.duplicateDefinition.withArguments(name: exceptionName),
stackTraceParameter,
exceptionParameter,
),
);
}
}
}
/// Check that the given list of variable declarations does not define
/// multiple variables of the same name.
void checkForVariables(VariableDeclarationListImpl node) {
var scope = _DuplicateIdentifierScope(this);
for (var variable in node.variables) {
scope.add(variable.name, node: variable);
}
}
/// Check that all of the parameters have unique names.
void checkParameters(FormalParameterListImpl node) {
var scope = _FormalParameterDuplicateIdentifierScope(this);
for (var parameter in node.parameters) {
// The identifier can be null if this is a parameter list for a generic
// function type.
var identifier = parameter.name;
if (identifier == null) {
continue;
}
scope.add(identifier, node: parameter);
}
// For private named parameters, also look for collisions with their public
// name and other parameters.
for (var parameter in node.parameters) {
if (parameter.declaredFragment
case FieldFormalParameterFragment fragment) {
if (fragment.privateName != null) {
scope.checkPublicName(
privateName: parameter.name!,
publicName: fragment.name!,
);
}
}
}
}
/// Check that all of the variables have unique names.
void checkStatements(List<StatementImpl> statements) {
var scope = _DuplicateIdentifierScope(this);
for (var statement in statements) {
if (statement is VariableDeclarationStatementImpl) {
for (var variable in statement.variables.variables) {
scope.add(variable.name, node: variable);
}
} else if (statement is FunctionDeclarationStatementImpl) {
if (!_isWildCardFunction(statement)) {
scope.add(
statement.functionDeclaration.name,
node: statement.functionDeclaration,
);
}
} else if (statement is PatternVariableDeclarationStatementImpl) {
for (var variable in statement.declaration.elements) {
scope.add(variable.node.name, node: variable.node);
}
}
}
}
/// Check that all of the parameters have unique names.
void checkTypeParameters(TypeParameterListImpl node) {
var scope = _DuplicateIdentifierScope(this);
for (var parameter in node.typeParameters) {
scope.add(parameter.name, node: parameter);
}
}
/// Check that there are no members with the same name.
void checkUnit(CompilationUnitImpl node) {
var fragment = node.declaredFragment!;
var definedGetters = <String, ElementImpl>{};
var definedSetters = <String, ElementImpl>{};
void addWithoutChecking(LibraryFragmentImpl libraryFragment) {
for (var fragment in libraryFragment.getters) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedGetters[name] = element;
}
}
for (var fragment in libraryFragment.setters) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedSetters[name] = element;
}
}
for (var fragment in libraryFragment.classes) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedGetters[name] = element;
}
}
for (var fragment in libraryFragment.enums) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedGetters[name] = element;
}
}
for (var fragment in libraryFragment.extensions) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedGetters[name] = element;
}
}
for (var fragment in libraryFragment.extensionTypes) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedGetters[name] = element;
}
}
for (var fragment in libraryFragment.functions) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedGetters[name] = element;
}
}
for (var fragment in libraryFragment.mixins) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedGetters[name] = element;
}
}
for (var fragment in libraryFragment.typeAliases) {
var element = fragment.element;
if (element.lookupName case var name?) {
definedGetters[name] = element;
}
}
}
var libraryDeclarations = _currentLibrary.libraryDeclarations;
for (var importPrefix in fragment.prefixes) {
var name = importPrefix.name;
if (name != null) {
if (libraryDeclarations.withName(name) case var existing?) {
_diagnosticReporter.report(
_diagnosticFactory.duplicateDefinition(
diag.prefixCollidesWithTopLevelMember.withArguments(name: name),
importPrefix.firstFragment,
existing as ElementImpl,
),
);
}
}
}
// TODO(scheglov): carry across resolved units
var currentLibraryFragment = node.declaredFragment!;
for (var libraryFragment in _currentLibrary.fragments) {
if (libraryFragment == currentLibraryFragment) {
break;
}
addWithoutChecking(libraryFragment);
}
for (var member in node.declarations) {
switch (member) {
case ClassDeclarationImpl():
var declaredFragment = member.declaredFragment!;
if (!declaredFragment.isAugmentation) {
_checkDuplicateFragmentIdentifier(
definedGetters,
member.namePart.typeName,
fragment: declaredFragment,
);
}
case EnumDeclarationImpl():
var declaredFragment = member.declaredFragment!;
if (!declaredFragment.isAugmentation) {
_checkDuplicateFragmentIdentifier(
definedGetters,
member.namePart.typeName,
fragment: declaredFragment,
);
}
case ExtensionDeclarationImpl():
var identifier = member.name;
if (identifier != null) {
var declaredFragment = member.declaredFragment!;
if (!declaredFragment.isAugmentation) {
_checkDuplicateFragmentIdentifier(
definedGetters,
identifier,
fragment: declaredFragment,
);
}
}
case ExtensionTypeDeclarationImpl():
var declaredFragment = member.declaredFragment!;
if (!declaredFragment.isAugmentation) {
_checkDuplicateFragmentIdentifier(
definedGetters,
member.primaryConstructor.typeName,
fragment: declaredFragment,
);
}
case FunctionDeclarationImpl():
var declaredFragment = member.declaredFragment!;
if (!declaredFragment.isAugmentation) {
if (declaredFragment is SetterFragment) {
_checkDuplicateFragmentIdentifier(
definedSetters,
member.name,
fragment: declaredFragment,
);
} else {
_checkDuplicateFragmentIdentifier(
definedGetters,
member.name,
fragment: declaredFragment,
);
}
}
case MixinDeclarationImpl():
var declaredFragment = member.declaredFragment!;
if (!declaredFragment.isAugmentation) {
_checkDuplicateFragmentIdentifier(
definedGetters,
member.name,
fragment: declaredFragment,
);
}
case TopLevelVariableDeclarationImpl():
for (var variable in member.variables.variables) {
var declaredFragment = variable.declaredFragment;
declaredFragment as TopLevelVariableFragmentImpl;
if (!declaredFragment.isAugmentation) {
var declaredElement = declaredFragment.element;
if (declaredElement.getter?.firstFragment case var getter?) {
_checkDuplicateFragmentIdentifier(
definedGetters,
variable.name,
originFragment: declaredFragment,
fragment: getter,
);
}
if (declaredElement.definesSetter) {
if (declaredElement.setter?.firstFragment case var setter?) {
_checkDuplicateFragmentIdentifier(
definedSetters,
variable.name,
originFragment: declaredFragment,
fragment: setter,
);
}
}
}
}
case TypeAliasImpl():
var declaredFragment = member.declaredFragment!;
if (!declaredFragment.isAugmentation) {
_checkDuplicateFragmentIdentifier(
definedGetters,
member.name,
fragment: declaredFragment,
);
}
}
}
}
/// Check whether the given [fragment] defined by the [identifier] is already
/// in [scope], and produce an error if it is.
void _checkDuplicateFragmentIdentifier(
Map<String, ElementImpl> scope,
Token identifier, {
FragmentImpl? originFragment,
required FragmentImpl fragment,
}) {
if (identifier.isSynthetic) {
return;
}
var lookupName = fragment.element.lookupName;
if (lookupName == null) {
return;
}
if (_reportedTokens.contains(identifier)) {
return;
}
if (scope[lookupName] case var previous?) {
_reportedTokens.add(identifier);
_diagnosticReporter.report(
_diagnosticFactory.duplicateDefinition(
diag.duplicateDefinition.withArguments(name: lookupName),
originFragment ?? fragment,
previous,
),
);
} else {
scope[lookupName] = fragment.element;
}
}
bool _isWildCardFunction(FunctionDeclarationStatement statement) =>
statement.functionDeclaration.name.lexeme == '_' &&
_currentLibrary.hasWildcardVariablesFeatureEnabled;
}
/// A single scope where colliding definitions with the same name is an error.
class _DuplicateIdentifierScope<T extends AstNode> {
final DuplicateDefinitionVerifier _verifier;
final Map<String, (T, Token)> _scope = {};
_DuplicateIdentifierScope(this._verifier);
/// Reports an error if there is already a node in this scope with the given
/// [identifier] name.
///
/// Otherwise, adds [node] to the scope.
void add(Token identifier, {required T node}) {
if (identifier.isSynthetic) {
return;
}
if (_verifier._reportedTokens.contains(identifier)) {
return;
}
// Wildcards do not collide.
if (isWildcard(identifier, node)) {
return;
}
if (_scope[identifier.lexeme] case (var previousNode, var previousToken)) {
_verifier._reportedTokens.add(identifier);
_verifier._diagnosticReporter.report(
_verifier._diagnosticFactory.duplicateDefinitionForNodes(
_verifier._diagnosticReporter.source,
getDiagnostic(
previousNode,
node,
).withArguments(name: identifier.lexeme),
identifier,
previousToken,
),
);
} else {
_scope[identifier.lexeme] = (node, identifier);
}
}
/// The diagnostic code to use when [previous] and [current] have the same
/// name.
DiagnosticWithArguments<LocatableDiagnostic Function({required String name})>
getDiagnostic(T previous, T current) => diag.duplicateDefinition;
/// Whether [node] named [identifier] acts as a wildcard.
bool isWildcard(Token identifier, T node) {
return identifier.lexeme == '_' &&
_verifier._currentLibrary.hasWildcardVariablesFeatureEnabled;
}
}
/// A [_DuplicateIdentifierScope] for formal parameters.
///
/// Handles private named parameters and initializing formals.
class _FormalParameterDuplicateIdentifierScope
extends _DuplicateIdentifierScope<FormalParameter> {
_FormalParameterDuplicateIdentifierScope(super.verifier);
/// Given a private named parameter with [privateName] and corresponding
/// [publicName], checks that the public name doesn't collide with any other
/// parameter.
void checkPublicName({
required Token privateName,
required String publicName,
}) {
if (_scope[publicName] case (var _, var previousToken)) {
_verifier._diagnosticReporter.report(
_verifier._diagnosticFactory.duplicateDefinitionForNodes(
_verifier._diagnosticReporter.source,
diag.privateNamedParameterDuplicatePublicName.withArguments(
name: publicName,
),
privateName,
previousToken,
),
);
}
}
@override
DiagnosticWithArguments<LocatableDiagnostic Function({required String name})>
getDiagnostic(FormalParameter previous, FormalParameter current) {
// When two initializing formals collide, tell the user they can't
// initialize the same field twice.
if (previous is FieldFormalParameter && current is FieldFormalParameter) {
return diag.duplicateFieldFormalParameter;
}
return diag.duplicateDefinition;
}
@override
bool isWildcard(Token identifier, FormalParameter node) {
if (!super.isWildcard(identifier, node)) {
return false;
}
// Since fields can be named `_`, initializing formals are not
// considered wildcards.
var element = node.declaredFragment!.element;
if (element is FieldFormalParameterElement) {
return false;
}
return true;
}
}