Skip to content

Commit

Permalink
fix: Trim apostrophes from JDT synthetic factory methods (#5844)
Browse files Browse the repository at this point in the history
Co-authored-by: Hannes Greule <[email protected]>
Co-authored-by: Martin Wittlinger <[email protected]>
  • Loading branch information
3 people authored Jun 17, 2024
1 parent 1ec101c commit c7b41ca
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 2 deletions.
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);
}
}

0 comments on commit c7b41ca

Please sign in to comment.