blob: fbcf2b4a405416eb1fe7e255c3aa61fb6922c0aa [file] [log] [blame]
// 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/src/dart/analysis/file_analysis.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/listener.dart';
import 'package:analyzer/src/generated/error_verifier.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
/// Information to pass from declarations to augmentations.
class DuplicationDefinitionContext {
final Map<InstanceFragmentImpl, _InstanceElementContext>
_instanceElementContexts = {};
}
class MemberDuplicateDefinitionVerifier {
final InheritanceManager3 _inheritanceManager;
final LibraryElementImpl _currentLibrary;
final LibraryFragmentImpl _currentUnit;
final DiagnosticReporter _diagnosticReporter;
final DuplicationDefinitionContext context;
final DiagnosticFactory _diagnosticFactory = DiagnosticFactory();
MemberDuplicateDefinitionVerifier._(
this._inheritanceManager,
this._currentLibrary,
this._currentUnit,
this._diagnosticReporter,
this.context,
);
void _checkClass(ClassDeclarationImpl node) {
_checkClassMembers(
node.declaredFragment!,
node.body.members,
primaryConstructor: node.namePart.tryCast(),
);
}
/// Check that there are no members with the same name.
void _checkClassMembers(
InstanceFragmentImpl fragment,
List<ClassMemberImpl> members, {
PrimaryConstructorDeclaration? primaryConstructor,
}) {
var firstFragment = fragment.element.firstFragment;
var elementContext = _getElementContext(firstFragment);
var constructorNames = elementContext.constructorNames;
var instanceScope = elementContext.instanceScope;
var staticScope = elementContext.staticScope;
if (primaryConstructor != null) {
var element = primaryConstructor.declaredFragment!.element;
if (element.name case var primaryConstructorName?) {
elementContext.constructorNames.add(primaryConstructorName);
}
var formals = primaryConstructor.formalParameters.parameters;
for (var formalNode in formals) {
var formalFragment = formalNode.declaredFragment;
if (formalFragment is FieldFormalParameterFragmentImpl &&
formalFragment.isDeclaring) {
var fieldName = formalNode.name;
var fieldElement = formalFragment.element.field;
if (fieldName != null && fieldElement != null) {
_checkDuplicateIdentifier(
instanceScope,
fieldName,
fragment: fieldElement.getter!.firstFragment,
originFragment: formalFragment,
);
if (fieldElement.setter case var setter?) {
_checkDuplicateIdentifier(
instanceScope,
fieldName,
fragment: setter.firstFragment,
originFragment: formalFragment,
);
}
}
}
}
}
for (var member in members) {
switch (member) {
case ConstructorDeclarationImpl():
// Augmentations are not declarations, can have multiple.
if (member.augmentKeyword != null) {
continue;
}
// Skip if the typeName is wrong.
if (member.typeName case var typeName?) {
if (typeName.name != firstFragment.name) {
continue;
}
}
var name = member.name?.lexeme ?? 'new';
if (!constructorNames.add(name)) {
if (name == 'new') {
_diagnosticReporter.report(
diag.duplicateConstructorDefault.atSourceRange(
member.errorRange,
),
);
} else {
_diagnosticReporter.report(
diag.duplicateConstructorName
.withArguments(name: name)
.atSourceRange(member.errorRange),
);
}
}
case FieldDeclarationImpl():
for (var field in member.fields.variables) {
var fieldFragment = field.declaredFragment!;
fieldFragment as FieldFragmentImpl;
var fieldElement = fieldFragment.element;
_checkDuplicateIdentifier(
member.isStatic ? staticScope : instanceScope,
field.name,
fragment: fieldElement.getter!.firstFragment,
originFragment: fieldFragment,
);
if (fieldElement.setter case var setter?) {
_checkDuplicateIdentifier(
member.isStatic ? staticScope : instanceScope,
field.name,
fragment: setter.firstFragment,
originFragment: fieldFragment,
);
}
if (fragment is EnumFragmentImpl) {
_checkValuesDeclarationInEnum(field.name);
}
}
case MethodDeclarationImpl():
_checkDuplicateIdentifier(
member.isStatic ? staticScope : instanceScope,
member.name,
fragment: member.declaredFragment!,
);
if (fragment is EnumFragmentImpl) {
if (!(member.isStatic && member.isSetter)) {
_checkValuesDeclarationInEnum(member.name);
}
}
case PrimaryConstructorBodyImpl():
// Not an actual declaration.
break;
}
}
if (firstFragment is InterfaceFragmentImpl) {
_checkConflictingConstructorAndStatic(
interfaceFragment: firstFragment,
staticScope: staticScope,
);
}
}
void _checkClassStatic(
InstanceFragmentImpl fragment,
List<ClassMember> members,
) {
var firstFragment = fragment.element.firstFragment;
var elementContext = _getElementContext(firstFragment);
var instanceScope = elementContext.instanceScope;
// Check for local static members conflicting with local instance members.
// TODO(scheglov): This code is duplicated for enums. But for classes it is
// separated also into ErrorVerifier - where we check inherited.
for (ClassMember member in members) {
if (member is FieldDeclaration) {
if (member.isStatic) {
for (VariableDeclaration field in member.fields.variables) {
var identifier = field.name;
String name = identifier.lexeme;
if (instanceScope.containsKey(name)) {
if (firstFragment is InterfaceFragmentImpl) {
String className = firstFragment.name ?? '';
_diagnosticReporter.report(
diag.conflictingStaticAndInstance
.withArguments(
className: className,
memberName: name,
conflictingClassName: className,
)
.at(identifier),
);
}
}
}
}
} else if (member is MethodDeclaration) {
if (member.isStatic) {
var identifier = member.name;
String name = identifier.lexeme;
if (instanceScope.containsKey(name)) {
if (firstFragment is InterfaceFragmentImpl) {
String className = firstFragment.name ?? '';
_diagnosticReporter.report(
diag.conflictingStaticAndInstance
.withArguments(
className: className,
memberName: name,
conflictingClassName: className,
)
.at(identifier),
);
}
}
}
}
}
}
void _checkConflictingConstructorAndStatic({
required InterfaceFragmentImpl interfaceFragment,
required Map<String, _ScopeEntry> staticScope,
}) {
for (var constructor in interfaceFragment.constructors) {
var name = constructor.name;
// It is already an error to declare a member named 'new'.
if (name == 'new') {
continue;
}
var state = staticScope[name];
switch (state) {
case null:
// ok
break;
case _ScopeEntryElement(
element: PropertyAccessorElementImpl staticMember2,
):
_diagnosticReporter.report(
switch (staticMember2) {
PropertyAccessorElementImpl(isOriginVariable: true) =>
diag.conflictingConstructorAndStaticField,
GetterElementImpl() =>
diag.conflictingConstructorAndStaticGetter,
_ => diag.conflictingConstructorAndStaticSetter,
}
.withArguments(name: name)
.atSourceRange(
constructor.asElement2.diagnosticRange(_currentUnit.source),
),
);
case _ScopeEntryElement(element: MethodElementImpl()):
_diagnosticReporter.report(
diag.conflictingConstructorAndStaticMethod
.withArguments(name: name)
.atSourceRange(
constructor.asElement2.diagnosticRange(_currentUnit.source),
),
);
case _ScopeEntryGetterSetterPair():
_diagnosticReporter.report(
(state.getter.isOriginVariable
? diag.conflictingConstructorAndStaticField
: diag.conflictingConstructorAndStaticGetter)
.withArguments(name: name)
.atSourceRange(
constructor.asElement2.diagnosticRange(_currentUnit.source),
),
);
case _ScopeEntryElement(:var element):
throw StateError(
'Unexpected type in duplicate map: ${element.runtimeType}',
);
}
}
}
/// Checks whether the given [fragment] defined by the [identifier] conflicts
/// with an element already in [scope], and produces an error if it is.
void _checkDuplicateIdentifier(
Map<String, _ScopeEntry> scope,
Token identifier, {
required FragmentImpl fragment,
FragmentImpl? originFragment,
}) {
if (identifier.isSynthetic || fragment.element.isWildcardVariable) {
return;
}
if (fragment.isAugmentation) {
return;
}
var name = switch (fragment) {
MethodFragmentImpl() => fragment.element.lookupName ?? '',
_ => identifier.lexeme,
};
var scopeEntry = scope[name];
switch (scopeEntry) {
case null:
scope[name] = _ScopeEntryElement(fragment.element);
case _ScopeEntryElement(element: GetterElementImpl previous)
when fragment is SetterFragmentImpl:
scope[name] = _ScopeEntryGetterSetterPair(
getter: previous,
setter: fragment.element,
);
case _ScopeEntryElement(element: SetterElementImpl previous)
when fragment is GetterFragmentImpl:
scope[name] = _ScopeEntryGetterSetterPair(
getter: fragment.element,
setter: previous,
);
case _ScopeEntryGetterSetterPair(setter: ElementImpl previous)
when fragment is SetterFragmentImpl:
case _ScopeEntryGetterSetterPair(getter: ElementImpl previous):
case _ScopeEntryElement(element: ElementImpl previous):
if (!identical(previous, fragment.element)) {
_diagnosticReporter.report(
_diagnosticFactory.duplicateDefinition(
diag.duplicateDefinition.withArguments(name: name),
originFragment ?? fragment,
previous,
),
);
}
}
}
/// Check that there are no members with the same name.
void _checkEnum(EnumDeclarationImpl node) {
var fragment = node.declaredFragment!;
var firstFragment = fragment.element.firstFragment;
var elementContext = _getElementContext(firstFragment);
var staticScope = elementContext.staticScope;
for (var constant in node.body.constants) {
var constantFragment = constant.declaredFragment!;
var constantGetter = constantFragment.element.getter!;
_checkDuplicateIdentifier(
staticScope,
constant.name,
fragment: constantGetter.firstFragment,
originFragment: constantFragment,
);
_checkValuesDeclarationInEnum(constant.name);
}
_checkClassMembers(
fragment,
node.body.members,
primaryConstructor: node.namePart.tryCast(),
);
for (var accessor in fragment.accessors) {
if (accessor.isStatic) {
continue;
}
if (accessor.libraryFragment.source != _currentUnit.source) {
continue;
}
var baseName = accessor.displayName;
var inherited = _getInheritedMember(fragment.element, baseName);
if (inherited is InternalMethodElement) {
_diagnosticReporter.report(
diag.conflictingFieldAndMethod
.withArguments(
className: firstFragment.displayName,
fieldName: baseName,
conflictingClassName: inherited.enclosingElement!.name!,
)
.atSourceRange(
accessor.asElement2.diagnosticRange(_currentUnit.source),
),
);
}
}
for (var method in fragment.methods) {
if (method.isStatic) {
continue;
}
if (method.libraryFragment.source != _currentUnit.source) {
continue;
}
var baseName = method.displayName;
var inherited = _getInheritedMember(fragment.element, baseName);
if (inherited is InternalPropertyAccessorElement) {
_diagnosticReporter.report(
diag.conflictingMethodAndField
.withArguments(
className: firstFragment.displayName,
methodName: baseName,
conflictingClassName: inherited.enclosingElement.name!,
)
.atSourceRange(
method.asElement2.diagnosticRange(_currentUnit.source),
),
);
}
}
}
void _checkEnumStatic(EnumDeclarationImpl node) {
var fragment = node.declaredFragment!;
var firstFragment = fragment.element.firstFragment;
var declarationName = firstFragment.name;
if (declarationName == null) {
return;
}
for (var accessor in fragment.accessors) {
if (accessor.libraryFragment.source != _currentUnit.source) {
continue;
}
var baseName = accessor.displayName;
if (accessor.isStatic) {
var instance = _getInterfaceMember(fragment.element, baseName);
if (instance != null && baseName != 'values') {
_diagnosticReporter.report(
diag.conflictingStaticAndInstance
.withArguments(
className: declarationName,
memberName: baseName,
conflictingClassName: declarationName,
)
.atSourceRange(
accessor.asElement2.diagnosticRange(_currentUnit.source),
),
);
}
}
}
for (var method in fragment.methods) {
if (method.libraryFragment.source != _currentUnit.source) {
continue;
}
var baseName = method.displayName;
if (method.isStatic) {
var instance = _getInterfaceMember(fragment.element, baseName);
if (instance != null) {
_diagnosticReporter.report(
diag.conflictingStaticAndInstance
.withArguments(
className: declarationName,
memberName: baseName,
conflictingClassName: declarationName,
)
.atSourceRange(
method.asElement2.diagnosticRange(_currentUnit.source),
),
);
}
}
}
}
/// Check that there are no members with the same name.
void _checkExtension(covariant ExtensionDeclarationImpl node) {
var fragment = node.declaredFragment!;
_checkClassMembers(fragment, node.body.members);
}
void _checkExtensionStatic(covariant ExtensionDeclarationImpl node) {
var fragment = node.declaredFragment!;
var firstFragment = fragment.element.firstFragment;
var elementContext = _getElementContext(firstFragment);
var instanceScope = elementContext.instanceScope;
for (var member in node.body.members) {
if (member is FieldDeclarationImpl) {
if (member.isStatic) {
for (var field in member.fields.variables) {
var identifier = field.name;
var name = identifier.lexeme;
if (instanceScope.containsKey(name)) {
_diagnosticReporter.report(
diag.extensionConflictingStaticAndInstance
.withArguments(name: name)
.at(identifier),
);
}
}
}
} else if (member is MethodDeclarationImpl) {
if (member.isStatic) {
var identifier = member.name;
var name = identifier.lexeme;
if (instanceScope.containsKey(name)) {
_diagnosticReporter.report(
diag.extensionConflictingStaticAndInstance
.withArguments(name: name)
.at(identifier),
);
}
}
}
}
}
void _checkExtensionType(ExtensionTypeDeclarationImpl node) {
_checkClassMembers(
node.declaredFragment!,
node.body.members,
primaryConstructor: node.primaryConstructor,
);
}
void _checkMixin(MixinDeclarationImpl node) {
_checkClassMembers(node.declaredFragment!, node.body.members);
}
void _checkUnit(CompilationUnitImpl node) {
for (var node in node.declarations) {
switch (node) {
case ClassDeclarationImpl():
_checkClass(node);
case ExtensionDeclarationImpl():
_checkExtension(node);
case EnumDeclarationImpl():
_checkEnum(node);
case ExtensionTypeDeclarationImpl():
_checkExtensionType(node);
case MixinDeclarationImpl():
_checkMixin(node);
case ClassTypeAliasImpl():
case FunctionDeclarationImpl():
case FunctionTypeAliasImpl():
case GenericTypeAliasImpl():
case TopLevelVariableDeclarationImpl():
// Do nothing.
}
}
}
void _checkUnitStatic(CompilationUnitImpl node) {
for (var declaration in node.declarations) {
switch (declaration) {
case ClassDeclarationImpl():
var fragment = declaration.declaredFragment!;
_checkClassStatic(fragment, declaration.body.members);
case EnumDeclarationImpl():
_checkEnumStatic(declaration);
case ExtensionDeclarationImpl():
_checkExtensionStatic(declaration);
case ExtensionTypeDeclarationImpl():
var fragment = declaration.declaredFragment!;
_checkClassStatic(fragment, declaration.body.members);
case MixinDeclarationImpl():
var fragment = declaration.declaredFragment!;
_checkClassStatic(fragment, declaration.body.members);
case ClassTypeAliasImpl():
case FunctionDeclarationImpl():
case FunctionTypeAliasImpl():
case GenericTypeAliasImpl():
case TopLevelVariableDeclarationImpl():
// Do nothing.
}
}
}
void _checkValuesDeclarationInEnum(Token name) {
if (name.lexeme == 'values') {
_diagnosticReporter.report(diag.valuesDeclarationInEnum.at(name));
}
}
_InstanceElementContext _getElementContext(InstanceFragmentImpl fragment) {
return context._instanceElementContexts[fragment] ??=
_InstanceElementContext();
}
InternalExecutableElement? _getInheritedMember(
InterfaceElementImpl element,
String baseName,
) {
var libraryUri = _currentLibrary.uri;
var getterName = Name(libraryUri, baseName);
var getter = _inheritanceManager.getInherited(element, getterName);
if (getter != null) {
return getter;
}
var setterName = Name(libraryUri, '$baseName=');
return _inheritanceManager.getInherited(element, setterName);
}
InternalExecutableElement? _getInterfaceMember(
InterfaceElementImpl element,
String baseName,
) {
var libraryUri = _currentLibrary.uri;
var getterName = Name(libraryUri, baseName);
var getter = _inheritanceManager.getMember(element, getterName);
if (getter != null) {
return getter;
}
var setterName = Name(libraryUri, '$baseName=');
return _inheritanceManager.getMember(element, setterName);
}
static void checkLibrary({
required InheritanceManager3 inheritance,
required LibraryVerificationContext libraryVerificationContext,
required LibraryElementImpl libraryElement,
required Map<FileState, FileAnalysis> files,
}) {
MemberDuplicateDefinitionVerifier forUnit(FileAnalysis fileAnalysis) {
return MemberDuplicateDefinitionVerifier._(
inheritance,
libraryElement,
fileAnalysis.fragment,
fileAnalysis.diagnosticReporter,
libraryVerificationContext.duplicationDefinitionContext,
);
}
// Check all instance members.
for (var fileAnalysis in files.values) {
forUnit(fileAnalysis)._checkUnit(fileAnalysis.unit);
}
// Check all static members.
for (var fileAnalysis in files.values) {
forUnit(fileAnalysis)._checkUnitStatic(fileAnalysis.unit);
}
}
}
/// Information accumulated for a single declaration and its augmentations.
class _InstanceElementContext {
final Set<String> constructorNames = {};
final Map<String, _ScopeEntry> instanceScope = {};
final Map<String, _ScopeEntry> staticScope = {};
}
sealed class _ScopeEntry {}
class _ScopeEntryElement extends _ScopeEntry {
final ElementImpl element;
_ScopeEntryElement(this.element)
: assert(element is! PropertyInducingElementImpl);
}
class _ScopeEntryGetterSetterPair extends _ScopeEntry {
final GetterElementImpl getter;
final SetterElementImpl setter;
_ScopeEntryGetterSetterPair({required this.getter, required this.setter});
}