Skip to content

Commit

Permalink
feature(*): Add support for type parameters on method references (#4343)
Browse files Browse the repository at this point in the history
Add support for type parameters on method references
  • Loading branch information
SirYwell authored Dec 12, 2021
1 parent 3ebb800 commit 7c01e62
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,9 @@ public <T, E extends CtExpression<?>> void visitCtExecutableReferenceExpression(
scan(expression.getTarget());
}
printer.writeSeparator("::");
if (!expression.getExecutable().getActualTypeArguments().isEmpty()) {
elementPrinterHelper.printList(expression.getExecutable().getActualTypeArguments(), null, false, "<", false, false, ", ", false, false, ">", this::scan);
}
if (expression.getExecutable().isConstructor()) {
printer.writeKeyword("new");
} else {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
import spoon.reflect.code.CtCatch;
import spoon.reflect.code.CtConstructorCall;
import spoon.reflect.code.CtContinue;
import spoon.reflect.code.CtExecutableReferenceExpression;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLambda;
Expand Down Expand Up @@ -1641,6 +1642,9 @@ public boolean visit(SingleTypeReference singleTypeReference, BlockScope scope)
} else if (context.stack.peekFirst().element instanceof CtCatch) {
context.enter(helper.createCatchVariable(singleTypeReference, scope), singleTypeReference);
return true;
} else if (context.stack.getFirst().element instanceof CtExecutableReferenceExpression) {
context.enter(references.getTypeParameterReference(singleTypeReference.resolvedType, singleTypeReference), singleTypeReference);
return true;
}
CtTypeReference<?> typeRef = references.buildTypeReference(singleTypeReference, scope);
if (typeRef != null) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/spoon/support/compiler/jdt/ParentExiter.java
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,8 @@ public <T> void visitCtLambda(CtLambda<T> lambda) {
public <T, E extends CtExpression<?>> void visitCtExecutableReferenceExpression(CtExecutableReferenceExpression<T, E> expression) {
if (child instanceof CtExpression) {
expression.setTarget((E) child);
} else if (child instanceof CtTypeParameterReference) {
expression.getExecutable().addActualTypeArgument((CtTypeReference<?>) child);
}
super.visitCtExecutableReferenceExpression(expression);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@
import spoon.Launcher;
import spoon.reflect.CtModel;
import spoon.reflect.code.CtBlock;
import spoon.reflect.code.CtExecutableReferenceExpression;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtStatement;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtTypeReference;
import spoon.test.SpoonTestHelpers;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static spoon.test.SpoonTestHelpers.containsRegexMatch;

public class DefaultJavaPrettyPrinterTest {

Expand Down Expand Up @@ -150,4 +156,40 @@ void testPrintNewArrayWithImmediateAccess(String line) {
CtType<?> first = model.getAllTypes().iterator().next();
assertThat(first.toString(), containsString(line));
}

@Test
@SuppressWarnings({"unchecked", "rawtypes"})
void testPrintMethodReferenceTypeParameters() {
// contract: type parameters of method references are printed
Launcher launcher = new Launcher();
Factory factory = launcher.getFactory();
CtTypeReference<?> typeReference = factory.Type().createReference("Arrays");
CtExecutableReferenceExpression exeExpression = factory.createExecutableReferenceExpression();
exeExpression.setExecutable(factory.createExecutableReference());
exeExpression.getExecutable().setSimpleName("binarySearch");
exeExpression.setTarget(factory.createTypeAccess(typeReference));
// if no type parameters are given, no < and no > should be printed
assertThat(exeExpression.toString(), not(anyOf(containsString("<"), containsString(">"))));

exeExpression.getExecutable().addActualTypeArgument(factory.Type().integerType());
// with type parameters, the < and > should be printed
assertThat(exeExpression.toString(), allOf(containsString("<"), containsString(">")));
// the type parameter should appear
assertThat(exeExpression.toString(), containsString("Integer"));

exeExpression.getExecutable().addActualTypeArgument(factory.Type().integerType());
// more than one parameter type should be separated (with .* to allow imports)
assertThat(exeExpression.toString(), containsRegexMatch("<(.*)Integer, (.*)Integer>"));

// remove type arguments again, we want to try something else
exeExpression.getExecutable().setActualTypeArguments(null);
// we want to have a bit more complex type and construct a fake Comparable<Integer, Comhyperbola<Integer>>
CtTypeReference<?> complexTypeReference = factory.Type().integerType().getSuperInterfaces().stream()
.filter(t -> t.getSimpleName().equals("Comparable"))
.findAny()
.orElseThrow();
complexTypeReference.addActualTypeArgument(complexTypeReference.clone().setSimpleName("Comhyperbola"));
exeExpression.getExecutable().addActualTypeArgument(complexTypeReference);
assertThat(exeExpression.toString(), containsRegexMatch("<(.*)Comparable<(.*)Integer, (.*)Comhyperbola<(.*)Integer>>>"));
}
}
17 changes: 17 additions & 0 deletions src/test/java/spoon/test/generics/GenericsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtConstructorCall;
import spoon.reflect.code.CtExecutableReferenceExpression;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtNewClass;
Expand Down Expand Up @@ -90,6 +91,9 @@
import java.util.EnumSet;
import java.util.List;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
Expand Down Expand Up @@ -1561,4 +1565,17 @@ public void testExecutableTypeParameter() {
assertEquals(formalType, ((CtTypeReference) m1.getActualTypeArguments().get(0)).getTypeParameterDeclaration());
assertNull(m1.getType().getTypeParameterDeclaration());
}

@org.junit.jupiter.api.Test
void testGenericMethodReference() {
// contract: method references keep their generic parameters
CtClass<?> parsed = Launcher.parseClass("class X {\n" +
" BiFunction<Integer[], Integer, Integer> field = Arrays::<Integer>binarySearch;\n" +
"}");
CtField<?> field = parsed.getField("field");
assertThat(field.getDefaultExpression(), instanceOf(CtExecutableReferenceExpression.class));
CtExecutableReferenceExpression<?, ?> expression = (CtExecutableReferenceExpression<?, ?>) field.getDefaultExpression();
assertThat(expression.getExecutable().getActualTypeArguments().size(), equalTo(1));
assertThat(expression.getExecutable().getActualTypeArguments().get(0).toString(), equalTo("java.lang.Integer"));
}
}

0 comments on commit 7c01e62

Please sign in to comment.