Skip to content
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

fix: Trim apostrophes from JDT synthetic factory methods #25

Merged
merged 5 commits into from
Jun 10, 2024
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
1 change: 1 addition & 0 deletions .github/actions/setup-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ runs:
uses: DeterminateSystems/flake-checker-action@4b90f9fc724969ff153fe1803460917c84fe00a3 # v5
with:
fail-mode: true
check-outdated: false
- name: Use Maven dependency cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/spoon/support/compiler/jdt/ReferenceBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticFactoryMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding;
Expand Down Expand Up @@ -422,7 +423,11 @@ <T> CtExecutableReference<T> getExecutableReference(MethodBinding exec, int sour
if (sourceStart >= 0 && sourceEnd >= 0) {
ref.setPosition(jdtTreeBuilder.getPositionBuilder().buildPosition(sourceStart, sourceEnd));
}
if (exec.isConstructor()) {
// JDT creates synthetic <factory> methods for inference of diamond constructors (We guess they had that
// lying around). If the type can not be completely resolved, e.g. due to no classpath, this factory is
// not replaced and appears in the AST. We need to fix its name so e.g. `CtExecutableReference#isConstructor`
// works.
if (exec.isConstructor() || exec.original() instanceof SyntheticFactoryMethodBinding) {
ref.setSimpleName(CtExecutableReference.CONSTRUCTOR_NAME);

// in case of constructor of an array, it's the return type that we want
Expand Down Expand Up @@ -1023,7 +1028,15 @@ private CtTypeReference<?> getTypeReferenceFromTypeVariableBinding(
return getTypeReferenceOfBoundingType(binding).clone();
} else {
CtTypeReference<?> ref = this.jdtTreeBuilder.getFactory().Core().createTypeParameterReference();
ref.setSimpleName(new String(binding.sourceName()));
String name = new String(binding.sourceName());
if (binding.declaringElement instanceof SyntheticFactoryMethodBinding) {
// JDT uses these factory methods for type inference of diamond constructors. In no classpath mode they
// might be left around. They append a variable number of primes (') to the original name, which is not
// valid in Java. We undo this here and hope for the best.
name = name.replace("'", "");
}

ref.setSimpleName(name);
return ref;
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/test/java/spoon/test/api/NoClasspathTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
import org.junit.jupiter.api.Test;

import spoon.Launcher;
import spoon.reflect.CtModel;
import spoon.reflect.code.CtConstructorCall;
import spoon.reflect.code.CtFieldAccess;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtReturn;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
Expand All @@ -38,7 +41,11 @@
import spoon.support.SpoonClassNotFoundException;
import spoon.support.visitor.SignaturePrinter;
import spoon.test.api.testclasses.Bar;
import spoon.testing.assertions.SpoonAssertions;
import spoon.testing.utils.ModelTest;

import static java.util.function.Predicate.not;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
Expand Down Expand Up @@ -201,4 +208,19 @@ public void testInheritanceInNoClassPathWithClasses() {
assertTrue(myClassReference.isSubtypeOf(myInterfaceReference));
assertTrue(field.getType().isSubtypeOf(myInterfaceReference));
}

@ModelTest("src/test/resources/noclasspath/issue5591/DiamondConstructorCallTypeInference.java")
void testJdtFactoryMethodsForDiamond(CtModel model) {
// contract: Leftover <factory> methods from JDT's diamond constructor type inference are handled in
// no classpath mode
var constructorCalls = model.getElements(new TypeFilter<>(CtConstructorCall.class));
assertThat(constructorCalls).hasSize(1);

var executableReference = constructorCalls.get(0).getExecutable();
SpoonAssertions.assertThat(executableReference)
.isNotNull()
.nested(it -> it.getSimpleName().isEqualTo("<init>"))
.nested(it -> it.satisfies(CtExecutableReference::isConstructor))
.nested(it -> it.getParameters().hasSize(1));
}
}
7 changes: 7 additions & 0 deletions src/test/java/spoon/testing/assertions/SpoonAssert.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package spoon.testing.assertions;

import java.util.function.Consumer;

public interface SpoonAssert<SELF, ACTUAL> {

SELF self();

ACTUAL actual();

default SELF nested(Consumer<SELF> checks) {
checks.accept(self());
return self();
}

void failWithMessage(String errorMessage, Object... arguments);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.foo.bar;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class DiamondConstructorCallTypeInference.java {
// Whatever does not exist.
private final List<? extends Whatever> items;

public DiamondConstructorCallTypeInference.java(Collection<? extends Whatever> items) {
this.items = new ArrayList<>(items);
}
}
Loading