Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Name resolution failures on invocations of methods with generic open array parameters.
- Name resolution failures around `Create` calls on types with `constructor` constraints.
- Name resolution failures on `read`, `write`, and `stored` specifiers of indexed properties.
- Name resolution failures on routine references assigned to a `var`/`const` declaration.
- Name resolution failures on routine references in constant arrays.
- Type resolution failures on array expressions nested within multidimensional constant arrays.
- Incorrect file position calculation for multiline string tokens.
- Analysis errors around `type of` type declarations.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
import javax.annotation.Nonnull;
import org.antlr.runtime.Token;
import org.sonar.plugins.communitydelphi.api.ast.ArrayExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeDeclarationNode;
import org.sonar.plugins.communitydelphi.api.type.Type;
import org.sonar.plugins.communitydelphi.api.type.Type.CollectionType;
import org.sonar.plugins.communitydelphi.api.type.TypeFactory;
import org.sonar.plugins.communitydelphi.api.type.Typed;

public final class ArrayExpressionNodeImpl extends ExpressionNodeImpl
implements ArrayExpressionNode {
Expand Down Expand Up @@ -65,11 +69,32 @@ public String getImage() {
@Override
@Nonnull
protected Type createType() {
int nestingLevel = 0;
DelphiNode parent = this;
do {
++nestingLevel;
parent = parent.getParent();
} while (parent instanceof ArrayExpressionNode);

Type elementType = TypeFactory.unknownType();
List<ExpressionNode> elements = getElements();
if (!elements.isEmpty()) {
elementType = elements.get(0).getType();
if (parent instanceof Typed && !(parent instanceof TypeDeclarationNode)) {
elementType = ((Typed) parent).getType();
for (int i = 0; i < nestingLevel; ++i) {
if (!(elementType instanceof CollectionType)) {
elementType = TypeFactory.unknownType();
break;
}
elementType = ((CollectionType) elementType).elementType();
}
}

if (elementType.isUnknown()) {
List<ExpressionNode> elements = getElements();
if (!elements.isEmpty()) {
elementType = elements.get(0).getType();
}
}

return ((TypeFactoryImpl) getTypeFactory()).array(null, elementType, Set.of(ArrayOption.FIXED));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.plugins.communitydelphi.api.ast.AnonymousMethodNode;
import org.sonar.plugins.communitydelphi.api.ast.ArrayExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.ArrayIndicesNode;
import org.sonar.plugins.communitydelphi.api.ast.ArrayTypeNode;
import org.sonar.plugins.communitydelphi.api.ast.AssignmentStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.AttributeListNode;
import org.sonar.plugins.communitydelphi.api.ast.AttributeNode;
import org.sonar.plugins.communitydelphi.api.ast.ConstDeclarationNode;
import org.sonar.plugins.communitydelphi.api.ast.ConstStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.ForInStatementNode;
Expand Down Expand Up @@ -71,6 +74,8 @@
import org.sonar.plugins.communitydelphi.api.ast.TypeNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeReferenceNode;
import org.sonar.plugins.communitydelphi.api.ast.UnaryExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.VarDeclarationNode;
import org.sonar.plugins.communitydelphi.api.ast.VarStatementNode;
import org.sonar.plugins.communitydelphi.api.operator.UnaryOperator;
import org.sonar.plugins.communitydelphi.api.symbol.Invocable;
import org.sonar.plugins.communitydelphi.api.symbol.NameOccurrence;
Expand All @@ -86,6 +91,7 @@
import org.sonar.plugins.communitydelphi.api.type.IntrinsicType;
import org.sonar.plugins.communitydelphi.api.type.Type;
import org.sonar.plugins.communitydelphi.api.type.Type.AliasType;
import org.sonar.plugins.communitydelphi.api.type.Type.CollectionType;
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.TypeParameterType;
Expand Down Expand Up @@ -517,18 +523,18 @@ private static boolean handleRoutineReference(

if (parent instanceof AssignmentStatementNode) {
ExpressionNode assignee = ((AssignmentStatementNode) parent).getAssignee();
if (expression == assignee) {
return false;
}

if (assignee.getType().isProcedural()) {
NameResolver clone = new NameResolver(resolver);
clone.disambiguateRoutineReference((ProceduralType) assignee.getType());
if (!clone.getDeclarations().isEmpty()) {
clone.addToSymbolTable();
return true;
}
}
return expression != assignee && handleRoutineReference(assignee.getType(), resolver);
} else if (parent instanceof VarDeclarationNode) {
return handleRoutineReference(((VarDeclarationNode) parent).getType(), resolver);
} else if (parent instanceof ConstDeclarationNode) {
return handleRoutineReference(((ConstDeclarationNode) parent).getTypeNode(), resolver);
} else if (parent instanceof VarStatementNode) {
return handleRoutineReference(((VarStatementNode) parent).getTypeNode(), resolver);
} else if (parent instanceof ConstStatementNode) {
return handleRoutineReference(((ConstStatementNode) parent).getTypeNode(), resolver);
} else if (parent instanceof ArrayExpressionNode) {
Type arrayType = ((ArrayExpressionNode) parent).getType();
return handleRoutineReference(((CollectionType) arrayType).elementType(), resolver);
} else if (parent instanceof RecordExpressionItemNode) {
resolver.addToSymbolTable();
return true;
Expand All @@ -537,6 +543,23 @@ private static boolean handleRoutineReference(
return false;
}

private static boolean handleRoutineReference(TypeNode typeNode, NameResolver resolver) {
// You can't assign routine references unless a procedural type is explicitly declared.
return typeNode != null && handleRoutineReference(typeNode.getType(), resolver);
}

private static boolean handleRoutineReference(Type type, NameResolver resolver) {
if (type.isProcedural()) {
NameResolver clone = new NameResolver(resolver);
clone.disambiguateRoutineReference((ProceduralType) type);
if (!clone.getDeclarations().isEmpty()) {
clone.addToSymbolTable();
return true;
}
}
return false;
}

private static boolean handlePascalReturn(
PrimaryExpressionNode expression, NameResolver resolver) {
if (expression.getChildren().size() != 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,27 @@ void testBestEffortArguments() {
verifyUsages(9, 2, reference(11, 6), reference(11, 11));
}

@Test
void testRoutineReferenceAssignedToProcedural() {
execute("RoutineReferenceAssignedToProcedural.pas");
verifyUsages(
10,
9,
reference(21, 17),
reference(25, 35),
reference(27, 5),
reference(28, 10),
reference(32, 28));
verifyUsages(
15,
9,
reference(22, 19),
reference(25, 40),
reference(27, 10),
reference(28, 5),
reference(33, 31));
}

@Test
void testClassReferenceMethodResolution() {
execute("classReferences/MethodResolution.pas");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
unit RoutineReferenceAssignedToProcedural;

interface

implementation

type
TFoo = function(const A: Integer; B: string; var C: Variant): Boolean;

function Bar(const A: Integer; B: string; var C: Variant): Boolean;
begin
Result := True;
end;

function Baz(const A: Integer; B: string; var C: Variant): Boolean;
begin
Result := False;
end;

var
VarFoo: TFoo = Bar;
ConstFoo: TFoo = Baz;

const
FooArray: array[0..1] of TFoo = (Bar, Baz);
MultiFooArray: array[0..1, 0..1] of TFoo = (
(Bar, Baz),
(Baz, Bar)
);

initialization
var InlineVarFoo: TFoo := Bar;
const InlineConstFoo: TFoo = Baz;

end.