Skip to content

Fix various type resolution failures #384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 14, 2025
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **API:** `TypeParameterNode::getTypeParameters` method.

### Fixed

- Name resolution failures on generic routine invocations where later type parameters are constrained by earlier type parameters.
- Type resolution failures on `as` casts where the type is returned from a routine invocation.
- Inaccurate type resolution when calling a constructor on a class reference type.

## [1.16.0] - 2025-05-09

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,12 @@
package au.com.integradev.delphi.antlr.ast.node;

import au.com.integradev.delphi.antlr.ast.visitors.DelphiParserVisitor;
import au.com.integradev.delphi.type.generic.TypeParameterTypeImpl;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.stream.Collectors;
import org.antlr.runtime.Token;
import org.sonar.plugins.communitydelphi.api.ast.ConstraintNode;
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.GenericDefinitionNode;
import org.sonar.plugins.communitydelphi.api.ast.NameDeclarationNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeParameterNode;
import org.sonar.plugins.communitydelphi.api.type.Constraint;
import org.sonar.plugins.communitydelphi.api.type.Type.TypeParameterType;

public final class GenericDefinitionNodeImpl extends DelphiNodeImpl
implements GenericDefinitionNode {
Expand All @@ -53,22 +47,10 @@ public <T> T accept(DelphiParserVisitor<T> visitor, T data) {
@Override
public List<TypeParameter> getTypeParameters() {
if (typeParameters == null) {
ImmutableList.Builder<TypeParameter> builder = ImmutableList.builder();

for (TypeParameterNode parameterNode : getTypeParameterNodes()) {
List<Constraint> constraints =
parameterNode.getConstraintNodes().stream()
.map(ConstraintNode::getConstraint)
.collect(Collectors.toUnmodifiableList());

for (NameDeclarationNode name : parameterNode.getTypeParameterNameNodes()) {
TypeParameterType type = TypeParameterTypeImpl.create(name.getImage(), constraints);
TypeParameter typeParameter = new TypeParameterImpl(name, type);
builder.add(typeParameter);
}
}

typeParameters = builder.build();
typeParameters =
getTypeParameterNodes().stream()
.flatMap(node -> node.getTypeParameters().stream())
.collect(Collectors.toList());
}

return typeParameters;
Expand All @@ -92,24 +74,4 @@ public String getImage() {
}
return image;
}

private static final class TypeParameterImpl implements TypeParameter {
private final NameDeclarationNode location;
private final TypeParameterType type;

private TypeParameterImpl(NameDeclarationNode location, TypeParameterType type) {
this.location = location;
this.type = type;
}

@Override
public NameDeclarationNode getLocation() {
return location;
}

@Override
public TypeParameterType getType() {
return type;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@
package au.com.integradev.delphi.antlr.ast.node;

import au.com.integradev.delphi.antlr.ast.visitors.DelphiParserVisitor;
import au.com.integradev.delphi.type.generic.TypeParameterTypeImpl;
import java.util.List;
import java.util.stream.Collectors;
import org.antlr.runtime.Token;
import org.sonar.plugins.communitydelphi.api.ast.ConstraintNode;
import org.sonar.plugins.communitydelphi.api.ast.GenericDefinitionNode.TypeParameter;
import org.sonar.plugins.communitydelphi.api.ast.NameDeclarationNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeConstraintNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeParameterNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeReferenceNode;
import org.sonar.plugins.communitydelphi.api.type.Constraint;
import org.sonar.plugins.communitydelphi.api.type.Type.TypeParameterType;

public final class TypeParameterNodeImpl extends DelphiNodeImpl implements TypeParameterNode {
public TypeParameterNodeImpl(Token token) {
Expand Down Expand Up @@ -61,4 +65,39 @@ public List<TypeReferenceNode> getTypeConstraintNodes() {
public List<ConstraintNode> getConstraintNodes() {
return findChildrenOfType(ConstraintNode.class);
}

@Override
public List<TypeParameter> getTypeParameters() {
List<Constraint> constraints =
getConstraintNodes().stream()
.map(ConstraintNode::getConstraint)
.collect(Collectors.toUnmodifiableList());

return getTypeParameterNameNodes().stream()
.map(
name ->
new TypeParameterImpl(
name, TypeParameterTypeImpl.create(name.getImage(), constraints)))
.collect(Collectors.toList());
}

private static final class TypeParameterImpl implements TypeParameter {
private final NameDeclarationNode location;
private final TypeParameterType type;

public TypeParameterImpl(NameDeclarationNode location, TypeParameterType type) {
this.location = location;
this.type = type;
}

@Override
public NameDeclarationNode getLocation() {
return location;
}

@Override
public TypeParameterType getType() {
return type;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
import org.sonar.plugins.communitydelphi.api.ast.ForToStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.FormalParameterNode.FormalParameterData;
import org.sonar.plugins.communitydelphi.api.ast.GenericDefinitionNode;
import org.sonar.plugins.communitydelphi.api.ast.GenericDefinitionNode.TypeParameter;
import org.sonar.plugins.communitydelphi.api.ast.GotoStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.IfStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.ImplementationSectionNode;
Expand Down Expand Up @@ -402,12 +401,14 @@ private static void createTypeParameterDeclarations(
.map(TypeConstraintNodeImpl.class::cast)
.map(TypeConstraintNodeImpl::getTypeNode)
.forEach(data.nameResolutionHelper::resolve);
}

for (TypeParameter typeParameter : definition.getTypeParameters()) {
NameDeclarationNode location = typeParameter.getLocation();
var declaration = new TypeParameterNameDeclarationImpl(location, typeParameter.getType());
data.addDeclaration(declaration, location);
parameterNode
.getTypeParameters()
.forEach(
param ->
data.addDeclaration(
new TypeParameterNameDeclarationImpl(param.getLocation(), param.getType()),
param.getLocation()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,14 @@ public Type resolve(PrimaryExpressionNode expression) {
}

private static Type classReferenceValueType(Type type) {
if (type.isProcedural()) {
type = ((ProceduralType) type).returnType();
}

if (type.isClassReference()) {
return ((ClassReferenceType) type).classType();
}

return unknownType();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1103,17 +1103,27 @@ private void disambiguateArguments(List<ExpressionNode> argumentExpressions, boo
}

private void resolveReturnType(Invocable invocable, List<InvocationArgument> arguments) {
if (!isConstructor((NameDeclaration) invocable)) {
Type returnType = invocable.getReturnType();
if (returnType instanceof IntrinsicReturnType) {
List<Type> argumentTypes =
arguments.stream()
.map(InvocationArgument::getType)
.collect(Collectors.toUnmodifiableList());
returnType = ((IntrinsicReturnType) returnType).getReturnType(argumentTypes);
// Constructors are a special case - they return the type they are invoked on
if (isConstructor((NameDeclaration) invocable)) {
if (currentType.isClassReference()) {
// Calling the constructor on a class reference type returns an instance of that class
updateType(((ClassReferenceType) currentType).classType());
}
updateType(returnType);

return;
}

Type returnType = invocable.getReturnType();

if (returnType instanceof IntrinsicReturnType) {
List<Type> argumentTypes =
arguments.stream()
.map(InvocationArgument::getType)
.collect(Collectors.toUnmodifiableList());
returnType = ((IntrinsicReturnType) returnType).getReturnType(argumentTypes);
}

updateType(returnType);
}

private void createCandidates(InvocationResolver resolver) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package au.com.integradev.delphi.type.generic;

import au.com.integradev.delphi.type.generic.constraint.TypeConstraintImpl;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
Expand All @@ -28,6 +29,8 @@
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.TypedDeclaration;
import org.sonar.plugins.communitydelphi.api.type.Constraint;
import org.sonar.plugins.communitydelphi.api.type.Constraint.TypeConstraint;
import org.sonar.plugins.communitydelphi.api.type.Type;
import org.sonar.plugins.communitydelphi.api.type.Type.TypeParameterType;
import org.sonar.plugins.communitydelphi.api.type.TypeSpecializationContext;
Expand Down Expand Up @@ -66,14 +69,26 @@ public TypeSpecializationContextImpl(NameDeclaration declaration, List<Type> typ
}
}

private static boolean constraintViolated(Type parameter, Type argument) {
private boolean constraintViolated(Type parameter, Type argument) {
return parameter.isTypeParameter()
&& ((TypeParameterType) parameter)
.constraintItems().stream()
.map(this::specializeConstraint)
.map(constraint -> constraint.satisfiedBy(argument))
.anyMatch(s -> !s);
}

private Constraint specializeConstraint(Constraint constraint) {
if (constraint instanceof TypeConstraint) {
Type specializedType = getArgument(((TypeConstraint) constraint).type());
if (specializedType != null) {
return new TypeConstraintImpl(specializedType);
}
}

return constraint;
}

@Override
@Nullable
public Type getArgument(Type parameter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.sonar.plugins.communitydelphi.api.ast;

import java.util.List;
import org.sonar.plugins.communitydelphi.api.ast.GenericDefinitionNode.TypeParameter;

public interface TypeParameterNode extends DelphiNode {
List<NameDeclarationNode> getTypeParameterNameNodes();
Expand All @@ -30,4 +31,6 @@ public interface TypeParameterNode extends DelphiNode {
List<TypeReferenceNode> getTypeConstraintNodes();

List<ConstraintNode> getConstraintNodes();

List<TypeParameter> getTypeParameters();
}
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ void testClassReferenceArgumentResolution() {
@Test
void testClassReferenceConstructorTypeResolution() {
execute("classReferences/ConstructorTypeResolution.pas");
verifyUsages(15, 10, reference(22, 2));
verifyUsages(15, 10, reference(22, 2), reference(23, 2));
verifyUsages(8, 16, reference(22, 11));
}

Expand Down Expand Up @@ -517,7 +517,7 @@ void testCharTypeResolution() {
@Test
void testCastTypeResolution() {
execute("typeResolution/Casts.pas");
verifyUsages(8, 14, reference(15, 12), reference(16, 16));
verifyUsages(8, 14, reference(22, 12), reference(23, 16), reference(24, 20));
}

@Test
Expand Down Expand Up @@ -1100,7 +1100,8 @@ void testGenericImplicitSpecializations() {
@Test
void testGenericConstraints() {
execute("generics/Constraint.pas");
verifyUsages(11, 14, reference(20, 25), reference(53, 8));
verifyUsages(11, 14, reference(20, 25), reference(58, 8));
verifyUsages(31, 20, reference(68, 8));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ procedure Proc(Obj: TObject);
procedure Test(Bar: Boolean; Baz: TMetaFoo);
begin
Proc(Baz.Create(Bar));
Proc(TMetaFoo.Create(Bar));
end;

end.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ TFoo<T: ICloneable, TTest, ISerializable> = class
procedure Test;
end;

TMock = class(TObject)
public
class function Mock<I: IInterface; T: I>(out Raw: T): I;
end;

implementation

function TTest.SerializableClone: ISerializable;
Expand Down Expand Up @@ -60,4 +65,5 @@ procedure TFoo<T>.Test;
initialization
Foo := TFoo<TTest>.Create(Test);
Foo.Test;
TMock.Mock<ISerializable, TTest>(Test);
end.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ TFoo = class(TObject)
procedure Bar;
end;

TFooClass = class of TFoo;

implementation

function GetClass: TFooClass;
begin
Result := TFoo;
end;

procedure Test(Arg: TObject);
begin
TFoo(Arg).Bar;
(Arg as TFoo).Bar;
(Arg as GetClass).Bar;
end;

end.