au.com.integradev.delphi.symbol.resolve.NameResolver Maven / Gradle / Ivy
/*
* Sonar Delphi Plugin
* Copyright (C) 2019 Integrated Application Development
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package au.com.integradev.delphi.symbol.resolve;
import static au.com.integradev.delphi.symbol.resolve.EqualityType.INCOMPATIBLE_TYPES;
import static java.util.function.Predicate.not;
import static org.sonar.plugins.communitydelphi.api.symbol.scope.DelphiScope.unknownScope;
import static org.sonar.plugins.communitydelphi.api.type.TypeFactory.unknownType;
import static org.sonar.plugins.communitydelphi.api.type.TypeFactory.voidType;
import au.com.integradev.delphi.antlr.ast.node.ArrayAccessorNodeImpl;
import au.com.integradev.delphi.antlr.ast.node.DelphiNodeImpl;
import au.com.integradev.delphi.antlr.ast.node.NameReferenceNodeImpl;
import au.com.integradev.delphi.symbol.Search;
import au.com.integradev.delphi.symbol.SearchMode;
import au.com.integradev.delphi.symbol.SymbolicNode;
import au.com.integradev.delphi.symbol.declaration.TypeParameterNameDeclarationImpl;
import au.com.integradev.delphi.symbol.occurrence.AttributeNameOccurrenceImpl;
import au.com.integradev.delphi.symbol.occurrence.NameOccurrenceImpl;
import au.com.integradev.delphi.symbol.scope.DelphiScopeImpl;
import au.com.integradev.delphi.symbol.scope.FileScopeImpl;
import au.com.integradev.delphi.type.TypeUtils;
import au.com.integradev.delphi.type.UnresolvedTypeImpl;
import au.com.integradev.delphi.type.factory.StructTypeImpl;
import au.com.integradev.delphi.type.generic.TypeParameterTypeImpl;
import au.com.integradev.delphi.type.generic.TypeSpecializationContextImpl;
import au.com.integradev.delphi.type.intrinsic.IntrinsicReturnType;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.plugins.communitydelphi.api.ast.ArgumentListNode;
import org.sonar.plugins.communitydelphi.api.ast.ArgumentNode;
import org.sonar.plugins.communitydelphi.api.ast.ArrayAccessorNode;
import org.sonar.plugins.communitydelphi.api.ast.AttributeNode;
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.GenericArgumentsNode;
import org.sonar.plugins.communitydelphi.api.ast.IdentifierNode;
import org.sonar.plugins.communitydelphi.api.ast.NameReferenceNode;
import org.sonar.plugins.communitydelphi.api.ast.Node;
import org.sonar.plugins.communitydelphi.api.ast.ParenthesizedExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.PrimaryExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.RoutineImplementationNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeReferenceNode;
import org.sonar.plugins.communitydelphi.api.ast.utils.ExpressionNodeUtils;
import org.sonar.plugins.communitydelphi.api.symbol.Invocable;
import org.sonar.plugins.communitydelphi.api.symbol.NameOccurrence;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.GenerifiableDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.NameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.PropertyNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.QualifiedNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.RoutineKind;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.RoutineNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypeNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypeParameterNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypedDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.UnitImportNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.UnitNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.VariableNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.scope.DelphiScope;
import org.sonar.plugins.communitydelphi.api.symbol.scope.FileScope;
import org.sonar.plugins.communitydelphi.api.symbol.scope.RoutineScope;
import org.sonar.plugins.communitydelphi.api.symbol.scope.TypeScope;
import org.sonar.plugins.communitydelphi.api.symbol.scope.UnknownScope;
import org.sonar.plugins.communitydelphi.api.type.IntrinsicType;
import org.sonar.plugins.communitydelphi.api.type.Parameter;
import org.sonar.plugins.communitydelphi.api.type.Type;
import org.sonar.plugins.communitydelphi.api.type.Type.ClassReferenceType;
import org.sonar.plugins.communitydelphi.api.type.Type.CollectionType;
import org.sonar.plugins.communitydelphi.api.type.Type.HelperType;
import org.sonar.plugins.communitydelphi.api.type.Type.PointerType;
import org.sonar.plugins.communitydelphi.api.type.Type.ProceduralType;
import org.sonar.plugins.communitydelphi.api.type.Type.ScopedType;
import org.sonar.plugins.communitydelphi.api.type.Type.StringType;
import org.sonar.plugins.communitydelphi.api.type.Type.StructType;
import org.sonar.plugins.communitydelphi.api.type.Type.TypeParameterType;
import org.sonar.plugins.communitydelphi.api.type.TypeFactory;
import org.sonar.plugins.communitydelphi.api.type.Typed;
public class NameResolver {
private static final Logger LOG = LoggerFactory.getLogger(NameResolver.class);
private final TypeFactory typeFactory;
private final List names = new ArrayList<>();
private final List resolvedDeclarations = new ArrayList<>();
private final SearchMode searchMode;
private final Supplier nameResolutionHelper;
private Set declarations = new HashSet<>();
private DelphiScope currentScope;
private Type currentType = unknownType();
NameResolver(TypeFactory typeFactory) {
this(typeFactory, SearchMode.DEFAULT);
}
NameResolver(TypeFactory typeFactory, SearchMode searchMode) {
this.typeFactory = typeFactory;
this.searchMode = searchMode;
this.nameResolutionHelper =
Suppliers.memoize(() -> new NameResolutionHelper(getTypeFactory(), searchMode));
}
NameResolver(NameResolver resolver) {
this(resolver.typeFactory, resolver.searchMode);
declarations.addAll(resolver.declarations);
currentScope = resolver.currentScope;
currentType = resolver.currentType;
names.addAll(resolver.names);
resolvedDeclarations.addAll(resolver.resolvedDeclarations);
}
private TypeFactory getTypeFactory() {
return typeFactory;
}
private NameResolutionHelper getNameResolutionHelper() {
return nameResolutionHelper.get();
}
public Type getApproximateType() {
if (declarations.size() + resolvedDeclarations.size() < names.size()) {
return unknownType();
}
if (!declarations.isEmpty()) {
NameDeclaration declaration = Iterables.getLast(declarations);
if (declaration instanceof TypedDeclaration) {
return findTypeForTypedDeclaration((TypedDeclaration) declaration);
}
return unknownType();
}
return currentType;
}
public Set getDeclarations() {
return declarations;
}
public List getResolvedDeclarations() {
return resolvedDeclarations;
}
NameDeclaration addResolvedDeclaration() {
if (declarations.isEmpty()) {
return null;
}
NameDeclaration resolved = Iterables.getLast(declarations);
checkAmbiguity();
currentScope = unknownScope();
if (declarations.size() == 1) {
NameDeclaration declaration = Iterables.getLast(declarations);
if (declaration instanceof TypedDeclaration) {
updateType(findTypeForTypedDeclaration((TypedDeclaration) declaration));
} else if (declaration instanceof UnitImportNameDeclaration) {
FileScope unitScope = ((UnitImportNameDeclaration) declaration).getUnitScope();
if (unitScope != null) {
currentScope = unitScope;
}
} else if (declaration instanceof UnitNameDeclaration) {
currentScope = ((UnitNameDeclaration) declaration).getFileScope();
}
}
resolvedDeclarations.addAll(declarations);
declarations.clear();
return resolved;
}
public void checkAmbiguity() {
if (declarations.size() > 1) {
if (LOG.isWarnEnabled()) {
LOG.warn(
"Ambiguous declarations could not be resolved\n[Occurrence] {}\n{}",
Iterables.getLast(names),
declarations.stream()
.map(declaration -> "[Declaration] " + declaration)
.collect(Collectors.joining("\n")));
}
declarations.clear();
}
}
void updateType(Type type) {
currentType = type;
ScopedType scopedType = extractScopedType(currentType);
DelphiScope newScope = unknownScope();
if (scopedType != null) {
newScope = scopedType.typeScope();
}
currentScope = newScope;
}
public boolean isExplicitInvocation() {
return !names.isEmpty() && Iterables.getLast(names).isExplicitInvocation();
}
public void addToSymbolTable() {
addResolvedDeclaration();
for (int i = 0; i < resolvedDeclarations.size(); ++i) {
NameOccurrenceImpl name = names.get(i);
NameDeclaration declaration = resolvedDeclarations.get(i);
name.setNameDeclaration(declaration);
((DelphiScopeImpl) declaration.getScope()).addNameOccurrence(name);
registerOccurrence(name);
}
}
private Type findTypeForTypedDeclaration(TypedDeclaration declaration) {
Type result;
if (isConstructor(declaration)) {
result = currentType;
if (result.isClassReference()) {
result = ((ClassReferenceType) result).classType();
}
if (result.isUnknown() && names.size() == 1) {
DelphiScope scope = Iterables.getLast(names).getLocation().getScope();
result = findCurrentType(scope);
}
} else {
result = declaration.getType();
if (isTypeIdentifier(declaration)) {
result = typeFactory.classOf(null, result);
}
}
return result;
}
private static Type findCurrentType(DelphiScope scope) {
scope = Objects.requireNonNullElse(scope, unknownScope());
RoutineScope routineScope = scope.getEnclosingScope(RoutineScope.class);
if (routineScope != null) {
DelphiScope typeScope = routineScope.getTypeScope();
if (typeScope instanceof TypeScope) {
return ((TypeScope) typeScope).getType();
}
return findCurrentType(routineScope.getParent());
}
return unknownType();
}
private static boolean isConstructor(NameDeclaration declaration) {
return declaration instanceof RoutineNameDeclaration
&& ((RoutineNameDeclaration) declaration).getRoutineKind() == RoutineKind.CONSTRUCTOR;
}
void readPrimaryExpression(PrimaryExpressionNode node) {
if (handleInheritedExpression(node)) {
return;
}
for (DelphiNode child : node.getChildren()) {
if (!readPrimaryExpressionPart(child)) {
break;
}
}
}
/**
* Reads part of a primary expression
*
* @param node part of a primary expression
* @return false if a name resolution failure occurs
*/
private boolean readPrimaryExpressionPart(Node node) {
if (node instanceof NameReferenceNode) {
readNameReference((NameReferenceNode) node);
} else if (node instanceof ArgumentListNode) {
handleArgumentList((ArgumentListNode) node);
} else if (node instanceof ArrayAccessorNode) {
handleArrayAccessor(((ArrayAccessorNode) node));
} else if (node instanceof ParenthesizedExpressionNode) {
handleParenthesizedExpression((ParenthesizedExpressionNode) node);
} else if (node instanceof Typed) {
updateType(((Typed) node).getType());
} else {
handlePrimaryExpressionToken(node);
}
return !nameResolutionFailed();
}
private void handlePrimaryExpressionToken(Node node) {
switch (node.getTokenType()) {
case DEREFERENCE:
Type dereferenced = TypeUtils.dereference(getApproximateType());
addResolvedDeclaration();
updateType(dereferenced);
break;
case STRING:
updateType(
typeFactory.classOf(null, typeFactory.getIntrinsic(IntrinsicType.UNICODESTRING)));
break;
case FILE:
updateType(typeFactory.classOf(null, typeFactory.untypedFile()));
break;
default:
// Do nothing
}
}
private boolean handleInheritedExpression(PrimaryExpressionNode node) {
if (!ExpressionNodeUtils.isInherited(node)) {
return false;
}
moveToInheritedScope(node);
if (ExpressionNodeUtils.isBareInherited(node)) {
RoutineImplementationNode routine =
node.getFirstParentOfType(RoutineImplementationNode.class);
DelphiNode inheritedNode = node.getChild(0);
NameOccurrenceImpl occurrence = new NameOccurrenceImpl(inheritedNode, routine.simpleName());
occurrence.setIsExplicitInvocation(true);
addName(occurrence);
searchForDeclaration(occurrence);
disambiguateIsCallable();
disambiguateVisibility();
disambiguateParameters(routine.getParameterTypes());
addResolvedDeclaration();
} else {
NameReferenceNode routineName = (NameReferenceNode) node.getChild(1);
NameOccurrenceImpl occurrence = new NameOccurrenceImpl(routineName.getIdentifier());
addName(occurrence);
declarations = currentScope.findDeclaration(occurrence);
if (declarations.isEmpty() && currentScope instanceof TypeScope) {
currentScope = ((TypeScope) currentScope).getParentTypeScope();
searchForDeclaration(occurrence);
}
disambiguateIsCallable();
disambiguateVisibility();
NameReferenceNode nextName = routineName.nextName();
if (!declarations.isEmpty()) {
((NameReferenceNodeImpl) routineName).setNameOccurrence(occurrence);
if (nextName != null) {
disambiguateImplicitEmptyArgumentList();
addResolvedDeclaration();
readNameReference(nextName);
}
}
}
int nextChild = ExpressionNodeUtils.isBareInherited(node) ? 1 : 2;
for (int i = nextChild; i < node.getChildren().size(); ++i) {
if (!readPrimaryExpressionPart(node.getChild(i))) {
break;
}
}
return true;
}
private void moveToInheritedScope(PrimaryExpressionNode node) {
RoutineImplementationNode routine = node.getFirstParentOfType(RoutineImplementationNode.class);
Preconditions.checkNotNull(routine);
currentScope = node.getScope();
TypeNameDeclaration typeDeclaration = routine.getTypeDeclaration();
if (typeDeclaration != null) {
Type type = typeDeclaration.getType();
Type newType = type.parent();
// Rules for inherited statements in helper methods are a bit unintuitive.
// Inheritance in helpers doesn't work in the same way as classes. It's more like aggregation.
//
// For starters, we ignore any helper ancestors and go straight into the extended type.
// If this is a bare inherited call with no specified method signature, then we even skip the
// extended type and move into its parent.
// See: https://wiki.freepascal.org/Helper_types#Inherited_with_function_name
if (type.isHelper()) {
newType = ((HelperType) type).extendedType();
if (ExpressionNodeUtils.isBareInherited(node)) {
newType = newType.parent();
}
}
if (newType instanceof ScopedType) {
currentScope = ((ScopedType) newType).typeScope();
} else {
currentScope = unknownScope();
}
}
}
void readRoutineNameInterfaceReference(NameReferenceNode node) {
currentScope = node.getScope().getParent();
for (NameReferenceNode reference : node.flatten()) {
IdentifierNode identifier = reference.getIdentifier();
NameOccurrenceImpl occurrence = new NameOccurrenceImpl(identifier);
GenericArgumentsNode genericArguments = reference.getGenericArguments();
List typeArguments = Collections.emptyList();
if (genericArguments != null) {
typeArguments = genericArguments.findChildrenOfType(TypeReferenceNode.class);
occurrence.setIsGeneric();
// Unresolved type arguments are created and added to the name occurrence so we can find
// declarations properly based on the number of type arguments.
// These temporary type arguments are overwritten with their actual types once the
// declaration has been found.
occurrence.setTypeArguments(
genericArguments.findChildrenOfType(TypeReferenceNode.class).stream()
.map(TypeReferenceNode::getNameNode)
.map(Node::getImage)
.map(UnresolvedTypeImpl::referenceTo)
.collect(Collectors.toUnmodifiableList()));
}
addName(occurrence);
// Routine name interface references should be resolved quite literally.
// If we allow it to go through the more general name resolution steps and scope traversals,
// we could end up inside a class helper or parent type or something.
declarations = currentScope.findDeclaration(occurrence);
declarations.removeIf(declaration -> declaration.getScope() != currentScope);
if (occurrence.isGeneric()) {
if (reference.nextName() != null) {
// If we're in the middle of the name, then we assume that we have already properly
// disambiguated the reference, and resolve the type parameter references back to their
// declarations on that type reference
handleTypeParameterReferences(typeArguments);
} else {
// If we're on the last name, then it's the routine reference.
// It's impossible for us to know which routine we're referring to before parameter
// resolution occurs.
// Instead, we create temporary type parameter declarations that will live in the routine
// scope as "forward" declarations. Once parameter resolution has occurred, we can come
// back and complete the type parameter declaration/type.
RoutineScope routineScope = node.getScope().getEnclosingScope(RoutineScope.class);
handleTypeParameterForwardReferences(routineScope, typeArguments);
}
occurrence.setTypeArguments(
typeArguments.stream()
.map(TypeReferenceNode::getType)
.collect(Collectors.toUnmodifiableList()));
} else {
disambiguateNotGeneric();
}
String unitName = occurrence.getLocation().getUnitName();
disambiguateWithinUnit(unitName);
if (declarations.isEmpty()) {
break;
}
if (reference.nextName() != null) {
addResolvedDeclaration();
}
((NameReferenceNodeImpl) reference).setNameOccurrence(occurrence);
}
}
private void handleTypeParameterReferences(List typeReferences) {
NameDeclaration currentDeclaration = Iterables.getLast(declarations, null);
if (!(currentDeclaration instanceof GenerifiableDeclaration)) {
return;
}
GenerifiableDeclaration generifiable = (GenerifiableDeclaration) currentDeclaration;
List typeParameters = generifiable.getTypeParameters();
for (int i = 0; i < typeParameters.size(); ++i) {
TypedDeclaration declaration = typeParameters.get(i);
NameReferenceNode typeReference = typeReferences.get(i).getNameNode();
NameOccurrenceImpl occurrence = new NameOccurrenceImpl(typeReference);
occurrence.setNameDeclaration(declaration);
((NameReferenceNodeImpl) typeReference).setNameOccurrence(occurrence);
NameResolver resolver =
new NameResolver(((DelphiNodeImpl) typeReference).getTypeFactory(), searchMode);
resolver.resolvedDeclarations.add(declaration);
resolver.names.add(occurrence);
resolver.addToSymbolTable();
}
}
private void handleTypeParameterForwardReferences(
RoutineScope routineScope, List typeReferences) {
for (TypeReferenceNode typeNode : typeReferences) {
NameReferenceNode typeReference = typeNode.getNameNode();
TypeParameterType type = TypeParameterTypeImpl.create(typeReference.getImage());
NameDeclaration declaration = new TypeParameterNameDeclarationImpl(typeReference, type);
((DelphiScopeImpl) routineScope).addDeclaration(declaration);
NameOccurrenceImpl occurrence = new NameOccurrenceImpl(typeReference);
occurrence.setNameDeclaration(declaration);
((NameReferenceNodeImpl) typeReference).setNameOccurrence(occurrence);
NameResolver resolver =
new NameResolver(((DelphiNodeImpl) typeNode).getTypeFactory(), searchMode);
resolver.resolvedDeclarations.add(declaration);
resolver.names.add(occurrence);
resolver.addToSymbolTable();
}
}
void readAttribute(AttributeNode node) {
readNameReference(node.getNameReference(), true);
addResolvedDeclaration();
List references = node.getNameReference().flatten();
NameReferenceNode lastReference = references.get(references.size() - 1);
var attributeNameOccurrence = (AttributeNameOccurrenceImpl) lastReference.getNameOccurrence();
if (attributeNameOccurrence == null) {
return;
}
// Attributes are a shorthand for an invocation of a constructor named 'Create'
SymbolicNode imaginaryConstructor = SymbolicNode.imaginary("Create", node.getScope());
NameOccurrenceImpl occurrence = new NameOccurrenceImpl(imaginaryConstructor);
attributeNameOccurrence.setImplicitConstructorNameOccurrence(occurrence);
resolveNameReferenceOccurrence(occurrence, true);
ArgumentListNode argumentList = node.getArgumentList();
if (argumentList != null) {
handleArgumentList(argumentList);
} else {
disambiguateArguments(Collections.emptyList(), true);
}
}
private void readNameReference(NameReferenceNode node, boolean inAttribute) {
boolean couldBeUnitNameReference =
currentScope == null
|| (!(currentScope instanceof UnknownScope) && currentScope.equals(node.getScope()));
for (NameReferenceNode reference : node.flatten()) {
if (isExplicitArrayConstructorInvocation(reference)) {
return;
}
NameOccurrence occurrence = createNameOccurrence(reference, inAttribute);
resolveNameReferenceOccurrence(occurrence, reference.nextName() == null);
if (nameResolutionFailed()) {
if (couldBeUnitNameReference) {
readPossibleUnitNameReference(node, inAttribute);
}
return;
}
((NameReferenceNodeImpl) reference).setNameOccurrence(occurrence);
}
}
private void resolveNameReferenceOccurrence(NameOccurrence occurrence, boolean isLastName) {
addName((NameOccurrenceImpl) occurrence);
searchForDeclaration(occurrence);
if (occurrence.isGeneric()) {
specializeDeclarations(occurrence);
}
disambiguateIsCallable();
disambiguateVisibility();
disambiguateUnitImport();
if (!isLastName) {
disambiguateImplicitEmptyArgumentList();
addResolvedDeclaration();
}
}
void readNameReference(NameReferenceNode node) {
readNameReference(node, false);
}
private NameOccurrence createNameOccurrence(NameReferenceNode reference, boolean attribute) {
DelphiNode node = reference.getIdentifier();
NameOccurrenceImpl occurrence =
attribute && reference.nextName() == null
? new AttributeNameOccurrenceImpl(node)
: new NameOccurrenceImpl(node);
GenericArgumentsNode genericArguments = reference.getGenericArguments();
if (genericArguments != null) {
List typeArgumentNodes = genericArguments.getTypeArguments();
typeArgumentNodes.forEach(getNameResolutionHelper()::resolve);
occurrence.setIsGeneric();
occurrence.setTypeArguments(
typeArgumentNodes.stream().map(TypeNode::getType).collect(Collectors.toList()));
}
return occurrence;
}
private boolean nameResolutionFailed() {
return names.size() != resolvedDeclarations.size() + Math.min(1, declarations.size());
}
private void specializeDeclarations(NameOccurrence occurrence) {
declarations =
declarations.stream()
.map(NameDeclaration.class::cast)
.map(
declaration -> {
List typeArguments = occurrence.getTypeArguments();
var context = new TypeSpecializationContextImpl(declaration, typeArguments);
return declaration.specialize(context);
})
.collect(Collectors.toSet());
}
private boolean isExplicitArrayConstructorInvocation(NameReferenceNode reference) {
return Iterables.getLast(resolvedDeclarations, null) instanceof TypeNameDeclaration
&& isDynamicArrayReference(currentType)
&& reference.getLastName().getImage().equalsIgnoreCase("Create");
}
private static boolean isDynamicArrayReference(Type type) {
return type.isClassReference() && ((ClassReferenceType) type).classType().isDynamicArray();
}
private void readPossibleUnitNameReference(NameReferenceNode node, boolean inAttribute) {
NameResolver unitNameResolver = new NameResolver(((DelphiNodeImpl) node).getTypeFactory());
if (unitNameResolver.readUnitNameReference(node, inAttribute)) {
this.currentType = unknownType();
this.names.clear();
this.resolvedDeclarations.clear();
this.currentScope = unitNameResolver.currentScope;
this.declarations = unitNameResolver.declarations;
this.names.addAll(unitNameResolver.names);
this.resolvedDeclarations.addAll(unitNameResolver.resolvedDeclarations);
}
}
private boolean readUnitNameReference(NameReferenceNode node, boolean inAttribute) {
FileScope fileScope = node.getScope().getEnclosingScope(FileScope.class);
List unitDeclarations = new ArrayList<>();
unitDeclarations.addAll(fileScope.getUnitDeclarations());
unitDeclarations.addAll(fileScope.getImportDeclarations());
unitDeclarations.sort(Comparator.comparing(NameDeclaration::getImage).reversed());
for (QualifiedNameDeclaration declaration : unitDeclarations) {
if (matchReferenceToUnitNameDeclaration(node, declaration, inAttribute)) {
return true;
}
}
return false;
}
private boolean matchReferenceToUnitNameDeclaration(
NameReferenceNode node, QualifiedNameDeclaration declaration, boolean inAttribute) {
List declarationParts = declaration.getQualifiedName().parts();
List references = node.flatten();
if (declarationParts.size() > references.size()) {
return false;
}
for (int i = 0; i < declarationParts.size(); ++i) {
if (!references.get(i).getIdentifier().getImage().equalsIgnoreCase(declarationParts.get(i))) {
return false;
}
}
StringBuilder referenceImage = new StringBuilder();
for (int i = 0; i < declarationParts.size(); ++i) {
if (i > 0) {
referenceImage.append('.');
}
referenceImage.append(references.get(i).getIdentifier().getImage());
}
SymbolicNode symbolicNode =
SymbolicNode.fromRange(
referenceImage.toString(),
node,
references.get(declarationParts.size() - 1).getIdentifier());
NameOccurrenceImpl occurrence = new NameOccurrenceImpl(symbolicNode);
((NameReferenceNodeImpl) node).setNameOccurrence(occurrence);
addName(occurrence);
declarations.add(declaration);
addResolvedDeclaration();
if (references.size() > declarationParts.size()) {
readNameReference(references.get(declarationParts.size()), inAttribute);
}
return true;
}
private void handleArrayAccessor(ArrayAccessorNode accessor) {
Type type = TypeUtils.findBaseType(getApproximateType());
if (type.isPointer()) {
Type dereferenced = TypeUtils.dereference(type);
addResolvedDeclaration();
updateType(dereferenced);
}
if (handleDefaultArrayProperties(accessor)) {
return;
}
if (declarations.stream().anyMatch(NameResolver::isArrayProperty)) {
disambiguateArguments(accessor.getExpressions(), true);
addResolvedDeclaration();
return;
}
addResolvedDeclaration();
if (currentType.isProcedural()) {
updateType(TypeUtils.dereference(((ProceduralType) currentType).returnType()));
}
accessor.getExpressions().forEach(getNameResolutionHelper()::resolve);
for (int i = 0; i < accessor.getExpressions().size(); ++i) {
if (currentType.isArray()) {
updateType(((CollectionType) currentType).elementType());
} else if (currentType.isString()) {
updateType(((StringType) currentType).characterType());
}
}
}
private void handleParenthesizedExpression(ParenthesizedExpressionNode parenthesized) {
getNameResolutionHelper().resolve(parenthesized);
updateType(parenthesized.getType());
ScopedType type = extractScopedType(currentType);
if (type != null) {
currentScope = type.typeScope();
} else {
currentScope = unknownScope();
}
}
private void addExplicitDefaultArrayPropertyDeclarations() {
if (!declarations.stream().allMatch(NameResolver::isDefaultArrayProperty)) {
return;
}
Type type = currentType;
if (type.isClassReference()) {
type = ((ClassReferenceType) type).classType();
} else if (type.isProcedural()) {
type = ((ProceduralType) type).returnType();
}
if (type.isStruct()) {
StructType structType = (StructType) TypeUtils.findBaseType(type);
NameDeclaration declaration = Iterables.getLast(declarations);
((StructTypeImpl) structType)
.findDefaultArrayProperties().stream()
.filter(property -> property.getName().equalsIgnoreCase(declaration.getName()))
.forEach(declarations::add);
}
}
private boolean handleDefaultArrayProperties(ArrayAccessorNode accessor) {
if (!declarations.isEmpty() && isArrayProperty(Iterables.getLast(declarations))) {
addExplicitDefaultArrayPropertyDeclarations();
return false;
}
addResolvedDeclaration();
Type type = currentType;
if (type.isClassReference()) {
type = ((ClassReferenceType) type).classType();
} else if (type.isProcedural()) {
type = ((ProceduralType) type).returnType();
}
if (type.isStruct()) {
StructType structType = (StructType) TypeUtils.findBaseType(type);
var defaultArrayProperties = ((StructTypeImpl) structType).findDefaultArrayProperties();
if (!defaultArrayProperties.isEmpty()) {
NameResolver propertyResolver = new NameResolver(this);
propertyResolver.declarations = defaultArrayProperties;
propertyResolver.disambiguateArguments(accessor.getExpressions(), true);
NameDeclaration propertyDeclaration =
Iterables.getLast(propertyResolver.resolvedDeclarations);
if (propertyDeclaration instanceof PropertyNameDeclaration) {
NameOccurrenceImpl implicitOccurrence = new NameOccurrenceImpl(accessor);
implicitOccurrence.setNameDeclaration(propertyDeclaration);
((ArrayAccessorNodeImpl) accessor).setImplicitNameOccurrence(implicitOccurrence);
((DelphiScopeImpl) propertyDeclaration.getScope()).addNameOccurrence(implicitOccurrence);
registerOccurrence(implicitOccurrence);
}
currentScope = propertyResolver.currentScope;
currentType = propertyResolver.currentType;
return true;
}
}
return false;
}
private static boolean isArrayProperty(NameDeclaration declaration) {
return declaration instanceof PropertyNameDeclaration
&& ((PropertyNameDeclaration) declaration).isArrayProperty();
}
private static boolean isDefaultArrayProperty(NameDeclaration declaration) {
return isArrayProperty(declaration)
&& ((PropertyNameDeclaration) declaration).isDefaultProperty();
}
void disambiguateImplicitEmptyArgumentList() {
if (declarations.stream().noneMatch(RoutineNameDeclaration.class::isInstance)) {
return;
}
disambiguateArguments(Collections.emptyList(), false);
}
private void handleArgumentList(ArgumentListNode node) {
for (ArgumentNode argument : node.getArgumentNodes()) {
ExpressionNode width = argument.getWidth();
if (width != null) {
getNameResolutionHelper().resolve(width);
}
ExpressionNode decimals = argument.getDecimals();
if (decimals != null) {
getNameResolutionHelper().resolve(decimals);
}
}
if (handleExplicitArrayConstructorInvocation(node)) {
return;
}
disambiguateArguments(
node.getArgumentNodes().stream()
.map(ArgumentNode::getExpression)
.collect(Collectors.toUnmodifiableList()),
true);
}
private boolean handleExplicitArrayConstructorInvocation(ArgumentListNode node) {
Node previous = node.getParent().getChild(node.getChildIndex() - 1);
if (previous instanceof NameReferenceNode
&& isExplicitArrayConstructorInvocation(((NameReferenceNode) previous))) {
updateType(((ClassReferenceType) currentType).classType());
node.getArgumentNodes().stream()
.map(ArgumentNode::getExpression)
.forEach(getNameResolutionHelper()::resolve);
return true;
}
return false;
}
private boolean handleHardTypeCast(List argumentExpressions) {
if (declarations.size() <= 1 && argumentExpressions.size() == 1) {
if (declarations.size() == 1 && isTypeIdentifier(Iterables.getLast(declarations))) {
addResolvedDeclaration();
}
if (declarations.isEmpty() && currentType.isClassReference()) {
updateType(((ClassReferenceType) currentType).classType());
getNameResolutionHelper().resolve(argumentExpressions.get(0));
return true;
}
}
return false;
}
private static boolean isTypeIdentifier(NameDeclaration declaration) {
return declaration instanceof TypeNameDeclaration
|| declaration instanceof TypeParameterNameDeclaration;
}
private boolean handleProcVarInvocation(List argumentExpressions) {
if (declarations.size() > 1) {
return false;
}
ProceduralType proceduralType;
if (declarations.isEmpty()) {
if (!currentType.isProcedural()) {
return false;
}
proceduralType = (ProceduralType) currentType;
} else {
NameDeclaration declaration = Iterables.getLast(declarations);
if (!(declaration instanceof VariableNameDeclaration
|| declaration instanceof PropertyNameDeclaration)) {
return false;
}
Type variableType = ((Typed) Iterables.getLast(declarations)).getType();
if (!variableType.isProcedural()) {
return false;
}
proceduralType = (ProceduralType) variableType;
addResolvedDeclaration();
}
List parameters = proceduralType.parameters();
int count = Math.min(argumentExpressions.size(), parameters.size());
for (int i = 0; i < count; ++i) {
ExpressionNode argument = argumentExpressions.get(i);
getNameResolutionHelper().resolveSubExpressions(argument);
InvocationArgument invocationArgument = new InvocationArgument(argument);
invocationArgument.resolve(parameters.get(i).getType());
}
updateType(proceduralType.returnType());
return true;
}
private void disambiguateArguments(List argumentExpressions, boolean explicit) {
if (handleHardTypeCast(argumentExpressions) || handleProcVarInvocation(argumentExpressions)) {
return;
}
if (declarations.isEmpty()) {
return;
}
disambiguateInvocable();
disambiguateArity(argumentExpressions.size());
argumentExpressions.forEach(getNameResolutionHelper()::resolveSubExpressions);
InvocationResolver resolver = new InvocationResolver();
argumentExpressions.stream().map(InvocationArgument::new).forEach(resolver::addArgument);
createCandidates(resolver);
resolver.processCandidates();
Set bestCandidate = resolver.chooseBest();
declarations =
bestCandidate.stream()
.map(InvocationCandidate::getData)
.map(NameDeclaration.class::cast)
.collect(Collectors.toSet());
disambiguateDistanceFromCallSite();
disambiguateRegularRoutineOverImplicitSpecializations();
if (!names.isEmpty()) {
Iterables.getLast(names).setIsExplicitInvocation(explicit);
}
NameDeclaration resolved = addResolvedDeclaration();
if (resolved == null) {
// we can't find it, so just give up
return;
}
Invocable invocable = ((Invocable) resolved);
for (int i = 0; i < resolver.getArguments().size(); ++i) {
InvocationArgument argument = resolver.getArguments().get(i);
Parameter parameter = invocable.getParameter(i);
argument.resolve(parameter.getType());
}
resolveReturnType(invocable, resolver.getArguments());
}
private void resolveReturnType(Invocable invocable, List arguments) {
if (!isConstructor((NameDeclaration) invocable)) {
Type returnType = invocable.getReturnType();
if (returnType instanceof IntrinsicReturnType) {
List argumentTypes =
arguments.stream()
.map(InvocationArgument::getType)
.collect(Collectors.toUnmodifiableList());
returnType = ((IntrinsicReturnType) returnType).getReturnType(argumentTypes);
}
updateType(returnType);
}
}
private void createCandidates(InvocationResolver resolver) {
declarations.stream()
.map(Invocable.class::cast)
.forEach(invocable -> createCandidate(invocable, resolver));
}
private void createCandidate(Invocable invocable, InvocationResolver resolver) {
InvocationCandidate candidate = null;
boolean couldBeImplicitSpecialization =
names.isEmpty() || !Iterables.getLast(names).isGeneric();
if (couldBeImplicitSpecialization) {
List argumentTypes =
resolver.getArguments().stream()
.map(NameResolver::getArgumentTypeForImplicitSpecialization)
.collect(Collectors.toList());
candidate = InvocationCandidate.implicitSpecialization(invocable, argumentTypes);
}
if (candidate == null) {
candidate = new InvocationCandidate(invocable);
}
resolver.addCandidate(candidate);
}
private static Type getArgumentTypeForImplicitSpecialization(InvocationArgument argument) {
Type type = argument.getType();
if (argument.looksLikeProceduralReference()) {
Type returnType = ((ProceduralType) type).returnType();
if (!returnType.is(voidType())) {
type = returnType;
}
}
return type;
}
void disambiguateRoutineReference(ProceduralType procedure) {
disambiguateInvocable();
disambiguateIsCallable();
EqualityType bestEquality = INCOMPATIBLE_TYPES;
NameDeclaration bestDeclaration = null;
for (NameDeclaration declaration : declarations) {
if (declaration instanceof Typed) {
EqualityType equality = TypeComparer.compare(((Typed) declaration).getType(), procedure);
if (equality != INCOMPATIBLE_TYPES && equality.ordinal() >= bestEquality.ordinal()) {
bestEquality = equality;
bestDeclaration = declaration;
}
}
}
declarations.clear();
if (bestDeclaration != null) {
declarations.add(bestDeclaration);
}
}
void disambiguateAddressOfRoutineReference() {
disambiguateInvocable();
disambiguateIsCallable();
NameDeclaration first = Iterables.getFirst(declarations, null);
if (first != null) {
declarations.clear();
declarations.add(first);
}
}
void disambiguateParameters(List parameterTypes) {
disambiguateInvocable();
disambiguateArity(parameterTypes.size());
declarations.removeIf(
declaration -> {
List parameters = ((Invocable) declaration).getParameters();
if (parameterTypes.size() != parameters.size()) {
return true;
}
for (int i = 0; i < parameters.size(); ++i) {
if (!parameters.get(i).getType().is(parameterTypes.get(i))) {
return true;
}
}
return false;
});
}
void disambiguateReturnType(Type returnType) {
if (!returnType.isUnknown()) {
disambiguateInvocable();
declarations.removeIf(
declaration -> !((Invocable) declaration).getReturnType().is(returnType));
}
}
void disambiguateIsClassInvocable(boolean isClassInvocable) {
disambiguateInvocable();
declarations.removeIf(
declaration -> ((Invocable) declaration).isClassInvocable() != isClassInvocable);
}
private void disambiguateInvocable() {
declarations.removeIf(not(Invocable.class::isInstance));
}
private void disambiguateArity(int parameterCount) {
declarations.removeIf(
declaration -> {
Invocable invocable = (Invocable) declaration;
return invocable.getParametersCount() < parameterCount
|| invocable.getRequiredParametersCount() > parameterCount;
});
}
private void disambiguateIsCallable() {
declarations.removeIf(
invocable -> invocable instanceof Invocable && !((Invocable) invocable).isCallable());
}
private void disambiguateVisibility() {
declarations.removeIf(not(this::isVisibleDeclaration));
}
private void disambiguateUnitImport() {
Set unitImports =
declarations.stream()
.filter(UnitImportNameDeclaration.class::isInstance)
.map(UnitImportNameDeclaration.class::cast)
.collect(Collectors.toSet());
if (unitImports.isEmpty()) {
return;
}
if (unitImports.stream().anyMatch(NameDeclaration::isImplementationDeclaration)) {
// The implementation unit import is chosen over anything in the interface
declarations.removeIf(not(NameDeclaration::isImplementationDeclaration));
}
if (unitImports.size() < declarations.size()) {
// The unit import is shadowed by anything below it
declarations.removeAll(unitImports);
}
}
private void disambiguateNotGeneric() {
declarations.removeIf(
declaration -> {
if (declaration instanceof GenerifiableDeclaration) {
GenerifiableDeclaration generifiable = (GenerifiableDeclaration) declaration;
return generifiable.isGeneric();
}
return false;
});
}
/**
* If overload selection between a generic method and a non-generic method would otherwise be
* ambiguous, the non-generic method is selected.
*
* @see Overloads and Type Compatibility in
* Generics
*/
private void disambiguateRegularRoutineOverImplicitSpecializations() {
if (declarations.size() > 1
&& declarations.stream().allMatch(RoutineNameDeclaration.class::isInstance)) {
Set nonGenericDeclarations =
declarations.stream()
.map(RoutineNameDeclaration.class::cast)
.filter(not(RoutineNameDeclaration::isGeneric))
.collect(Collectors.toSet());
if (nonGenericDeclarations.size() == 1) {
declarations = nonGenericDeclarations;
}
}
}
private void disambiguateDistanceFromCallSite() {
if (declarations.size() > 1
&& declarations.stream().allMatch(RoutineNameDeclaration.class::isInstance)) {
Set methodTypes =
declarations.stream()
.map(RoutineNameDeclaration.class::cast)
.map(RoutineNameDeclaration::getTypeDeclaration)
.collect(Collectors.toSet());
if (methodTypes.contains(null)) {
disambiguateDistanceFromUnit();
} else {
disambiguateDistanceFromType();
}
}
}
private void disambiguateDistanceFromUnit() {
String currentUnit = Iterables.getLast(names).getLocation().getUnitName();
Set routineDeclarations =
declarations.stream().map(RoutineNameDeclaration.class::cast).collect(Collectors.toSet());
if (routineDeclarations.stream()
.map(NameDeclaration::getNode)
.map(Node::getUnitName)
.anyMatch(currentUnit::equals)) {
declarations =
routineDeclarations.stream()
.filter(declaration -> declaration.getNode().getUnitName().equals(currentUnit))
.collect(Collectors.toSet());
}
}
private void disambiguateDistanceFromType() {
Set routineDeclarations =
declarations.stream().map(RoutineNameDeclaration.class::cast).collect(Collectors.toSet());
Type closestType =
routineDeclarations.stream()
.map(RoutineNameDeclaration::getTypeDeclaration)
.filter(Objects::nonNull)
.map(TypeNameDeclaration::getType)
.max(NameResolver::compareTypeSpecificity)
.orElseThrow();
declarations =
routineDeclarations.stream()
.filter(
declaration -> {
TypeNameDeclaration typeDeclaration = declaration.getTypeDeclaration();
return Objects.requireNonNull(typeDeclaration).getType().is(closestType);
})
.collect(Collectors.toSet());
}
private static int compareTypeSpecificity(Type typeA, Type typeB) {
if (typeA.is(typeB)) {
return 0;
} else if (typeA.isDescendantOf(typeB) || hasHelperRelationship(typeA, typeB)) {
return 1;
} else {
return -1;
}
}
private static boolean hasHelperRelationship(Type typeA, Type typeB) {
if (typeA.isHelper()) {
Type extended = ((HelperType) typeA).extendedType();
return extended.is(typeB) || extended.isDescendantOf(typeB);
}
return false;
}
private void disambiguateWithinUnit(String unitName) {
declarations.removeIf(declaration -> !declaration.getNode().getUnitName().equals(unitName));
}
private boolean isVisibleDeclaration(NameDeclaration declaration) {
if (declaration instanceof RoutineNameDeclaration) {
NameOccurrence name = Iterables.getLast(names);
RoutineScope fromScope = name.getLocation().getScope().getEnclosingScope(RoutineScope.class);
if (fromScope != null) {
RoutineNameDeclaration routine = (RoutineNameDeclaration) declaration;
Type fromType = extractType(fromScope);
Type toType = extractType(routine);
FileScope currentFile = fromScope.getEnclosingScope(FileScope.class);
FileScope routineFile = routine.getScope().getEnclosingScope(FileScope.class);
FileScope typeFile = extractFileScope(currentType);
boolean isSameUnit = currentFile == routineFile || currentFile == typeFile;
return isRoutineVisibleFrom(fromType, toType, routine, isSameUnit);
}
}
return true;
}
private static Type extractType(RoutineScope scope) {
DelphiScope routineScope = scope;
DelphiScope typeScope = scope.getTypeScope();
while (!(typeScope instanceof TypeScope) && routineScope instanceof RoutineScope) {
typeScope = ((RoutineScope) routineScope).getTypeScope();
routineScope = routineScope.getParent();
}
if (typeScope instanceof TypeScope) {
return ((TypeScope) typeScope).getType();
}
return unknownType();
}
private static Type extractType(RoutineNameDeclaration routine) {
TypeNameDeclaration typeDeclaration = routine.getTypeDeclaration();
if (typeDeclaration == null) {
return unknownType();
}
return typeDeclaration.getType();
}
@Nullable
private static FileScope extractFileScope(Type type) {
ScopedType scopedType = extractScopedType(type);
if (scopedType != null) {
return scopedType.typeScope().getEnclosingScope(FileScope.class);
}
return null;
}
private static boolean isRoutineVisibleFrom(
Type fromType, Type toType, RoutineNameDeclaration routine, boolean isSameUnit) {
if (!fromType.isUnknown() && !toType.isUnknown()) {
if (fromType.is(toType)) {
return true;
} else if (fromType.isDescendantOf(toType)) {
return isParentTypeMethodVisible(routine, isSameUnit);
} else if (isHelperTypeAccessingExtendedType(fromType, toType)) {
return !routine.isPrivate();
}
}
return isOtherTypeMethodVisible(routine, isSameUnit);
}
private static boolean isHelperTypeAccessingExtendedType(Type fromType, Type toType) {
if (fromType.isHelper()) {
Type extendedType = ((HelperType) fromType).extendedType();
return extendedType.is(toType) || extendedType.isDescendantOf(toType);
}
return false;
}
private static boolean isParentTypeMethodVisible(
RoutineNameDeclaration method, boolean sameUnit) {
return !(sameUnit ? method.isStrictPrivate() : method.isPrivate());
}
private static boolean isOtherTypeMethodVisible(RoutineNameDeclaration method, boolean sameUnit) {
if (sameUnit) {
return !method.isStrictPrivate() && !method.isStrictProtected();
} else {
return method.isPublic() || method.isPublished();
}
}
void addName(NameOccurrenceImpl name) {
names.add(name);
}
void searchForDeclaration(NameOccurrence occurrence) {
if (currentType.isTypeParameter()) {
searchForDeclarationInConstraintTypes(occurrence);
return;
}
DelphiScope occurrenceScope = occurrence.getLocation().getScope();
if (currentScope == null) {
currentScope = occurrenceScope;
}
checkForRecordHelperScope(occurrenceScope);
Search search = new Search(occurrence, searchMode);
search.execute(currentScope);
declarations = search.getResult();
}
/**
* Embarcadero claims that finding declarations in constrained types works by creating a "union"
* of declarations from the constraint types and then performing a "variation of overload
* resolution" on that union.
*
* Oops, turns out that was a lie. The compiler actually does an individual search through each
* constraint type, and stops once it finds any declaration that might match the name reference.
* Constraints are searched in the order that they were declared on the type parameter.
*
*
This is an objectively horrible way for the compiler to do the search, especially when you
* consider that they clearly had a better implementation in mind.
*
* @param occurrence The name occurrence we're currently searching for
* @see Constraints in Generics: Type
* Inferencing
*/
private void searchForDeclarationInConstraintTypes(NameOccurrence occurrence) {
TypeParameterType type = (TypeParameterType) currentType;
for (Type constraint : type.constraints()) {
if (constraint instanceof ScopedType) {
updateType(constraint);
searchForDeclaration(occurrence);
if (!declarations.isEmpty()) {
break;
}
}
}
}
/**
* This will try to find a record helper scope if our current scope isn't a type scope but our
* current type is valid. Our current type is probably an intrinsic in this case, meaning
* declarations can only be found if it has a record helper.
*
*
If a record helper is found, we move into the record helper scope.
*
* @param scope The scope to search for the helper type, if we aren't already in a type scope
*/
private void checkForRecordHelperScope(DelphiScope scope) {
if (!currentType.isUnknown() && !(currentScope instanceof TypeScope)) {
Type type = currentType;
if (type.isClassReference()) {
type = ((ClassReferenceType) type).classType();
}
HelperType helper = scope.getHelperForType(type);
if (helper != null) {
currentScope = helper.typeScope();
}
}
}
@Nullable
private static ScopedType extractScopedType(Type type) {
while (!(type instanceof ScopedType)) {
if (type instanceof ProceduralType) {
type = ((ProceduralType) type).returnType();
} else if (type instanceof PointerType) {
type = ((PointerType) type).dereferencedType();
} else {
break;
}
}
return (type instanceof ScopedType) ? (ScopedType) type : null;
}
private static void registerOccurrence(NameOccurrenceImpl occurrence) {
FileScope scope = occurrence.getLocation().getScope().getEnclosingScope(FileScope.class);
((FileScopeImpl) scope).registerOccurrence(occurrence.getLocation(), occurrence);
}
}