From 979fa7c32221c38aec0c43a0023ea494e32f073c Mon Sep 17 00:00:00 2001 From: I-Al-Istannen Date: Tue, 16 Aug 2022 23:27:56 +0200 Subject: [PATCH 1/3] feat: Use inferred generic type in invocations If no explicit type arguments are given, the type arguments JDT inferred are now used for getActualTypeArguments. --- .../visitor/DefaultJavaPrettyPrinter.java | 11 +++-- .../reflect/visitor/ElementPrinterHelper.java | 43 +++++++++++++------ .../support/compiler/jdt/JDTTreeBuilder.java | 11 +++++ .../spoon/test/generics/GenericsTest.java | 22 ++++++++-- 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java index e5ee2751b8f..ec532940fc9 100644 --- a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java +++ b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java @@ -132,6 +132,9 @@ import java.util.Set; import java.util.stream.Collectors; +import static spoon.reflect.visitor.ElementPrinterHelper.PrintTypeArguments.INCLUDE_IF_IMPLICIT; +import static spoon.reflect.visitor.ElementPrinterHelper.PrintTypeArguments.OMIT_IF_IMPLICIT; + /** * A visitor for generating Java code from the program compile-time model. */ @@ -1356,7 +1359,7 @@ public void visitCtInvocation(CtInvocation invocation) { enterCtExpression(invocation); if (invocation.getExecutable().isConstructor()) { // It's a constructor (super or this) - elementPrinterHelper.writeActualTypeArguments(invocation.getExecutable()); + elementPrinterHelper.writeActualTypeArguments(invocation.getExecutable(), OMIT_IF_IMPLICIT); CtType parentType = invocation.getParent(CtType.class); if (parentType == null || parentType.getQualifiedName() != null && parentType.getQualifiedName().equals(invocation.getExecutable().getDeclaringType().getQualifiedName())) { printer.writeKeyword("this"); @@ -1381,7 +1384,7 @@ public void visitCtInvocation(CtInvocation invocation) { } } - elementPrinterHelper.writeActualTypeArguments(invocation); + elementPrinterHelper.writeActualTypeArguments(invocation, OMIT_IF_IMPLICIT); if (env.isPreserveLineNumbers()) { getPrinterHelper().adjustStartPosition(invocation); } @@ -1605,7 +1608,7 @@ private void printConstructorCall(CtConstructorCall ctConstructorCall) { printer.writeKeyword("new").writeSpace(); if (!ctConstructorCall.getActualTypeArguments().isEmpty()) { - elementPrinterHelper.writeActualTypeArguments(ctConstructorCall); + elementPrinterHelper.writeActualTypeArguments(ctConstructorCall, INCLUDE_IF_IMPLICIT); } scan(ctConstructorCall.getType()); @@ -1990,7 +1993,7 @@ private void visitCtTypeReference(CtTypeReference ref, boolean withGenerics) } if (withGenerics && !context.ignoreGenerics()) { try (Writable _context = context.modify().ignoreEnclosingClass(false)) { - elementPrinterHelper.writeActualTypeArguments(ref); + elementPrinterHelper.writeActualTypeArguments(ref, INCLUDE_IF_IMPLICIT); } } } diff --git a/src/main/java/spoon/reflect/visitor/ElementPrinterHelper.java b/src/main/java/spoon/reflect/visitor/ElementPrinterHelper.java index 17bc05386d4..e436ca3c4e8 100644 --- a/src/main/java/spoon/reflect/visitor/ElementPrinterHelper.java +++ b/src/main/java/spoon/reflect/visitor/ElementPrinterHelper.java @@ -241,22 +241,32 @@ public void writeFormalTypeParameters(CtFormalTypeDeclarer ctFormalTypeDeclarer) /** * Writes actual type arguments in a {@link CtActualTypeContainer} element. * - * @param ctGenericElementReference - * Reference with actual type arguments. + * @param ctGenericElementReference Reference with actual type arguments. + * @param handleImplicit Whether to print type arguments if they are all implicit */ - public void writeActualTypeArguments(CtActualTypeContainer ctGenericElementReference) { - final Collection> arguments = ctGenericElementReference.getActualTypeArguments(); - if (arguments != null && !arguments.isEmpty()) { - printList(arguments.stream().filter(a -> !a.isImplicit())::iterator, - null, false, "<", false, false, ",", true, false, ">", - argument -> { - if (prettyPrinter.getContext().forceWildcardGenerics()) { - printer.writeSeparator("?"); - } else { - prettyPrinter.scan(argument); - } - }); + public void writeActualTypeArguments( + CtActualTypeContainer ctGenericElementReference, + PrintTypeArguments handleImplicit + ) { + Collection> arguments = ctGenericElementReference.getActualTypeArguments(); + if (arguments == null || arguments.isEmpty()) { + return; + } + + boolean allImplicit = arguments.stream().allMatch(CtElement::isImplicit); + if (allImplicit && handleImplicit == PrintTypeArguments.OMIT_IF_IMPLICIT) { + return; } + + printList(arguments.stream().filter(a -> !a.isImplicit())::iterator, + null, false, "<", false, false, ",", true, false, ">", + argument -> { + if (prettyPrinter.getContext().forceWildcardGenerics()) { + printer.writeSeparator("?"); + } else { + prettyPrinter.scan(argument); + } + }); } private boolean isJavaLangClasses(String importType) { @@ -565,4 +575,9 @@ protected void printPermits(CtSealable sealable) { printList(sealable.getPermittedTypes(), null, false, null, false, false, ",", true, false, null, prettyPrinter::scan); printer.decTab(); } + + public enum PrintTypeArguments { + OMIT_IF_IMPLICIT, + INCLUDE_IF_IMPLICIT + } } diff --git a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java index c3717fb5e89..4b834a76094 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java @@ -8,6 +8,8 @@ package spoon.support.compiler.jdt; import java.util.Set; + +import org.eclipse.jdt.internal.compiler.lookup.ParameterizedGenericMethodBinding; import org.slf4j.Logger; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ASTVisitor; @@ -1425,6 +1427,15 @@ public boolean visit(MessageSend messageSend, BlockScope scope) { inv.getExecutable().setType(references.getTypeReference(messageSend.expectedType())); } } + // No explicit type arguments given for call, but JDT inferred them (e.g. in a List.of("foo") call) + if (messageSend.binding instanceof ParameterizedGenericMethodBinding && messageSend.typeArguments == null) { + ParameterizedGenericMethodBinding binding = (ParameterizedGenericMethodBinding) messageSend.binding; + for (TypeBinding argument : binding.typeArguments) { + CtTypeReference reference = getReferencesBuilder().getTypeReference(argument); + reference.setImplicit(true); + inv.addActualTypeArgument(reference); + } + } context.enter(inv, messageSend); return true; } diff --git a/src/test/java/spoon/test/generics/GenericsTest.java b/src/test/java/spoon/test/generics/GenericsTest.java index 93dac5b3cd5..103966193d0 100644 --- a/src/test/java/spoon/test/generics/GenericsTest.java +++ b/src/test/java/spoon/test/generics/GenericsTest.java @@ -36,6 +36,7 @@ import spoon.reflect.code.CtLocalVariable; import spoon.reflect.code.CtNewClass; import spoon.reflect.code.CtReturn; +import spoon.reflect.code.CtStatement; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtElement; @@ -467,7 +468,7 @@ public void testInvocationGenerics() { assertEquals("this.makeTacos(null)", invocation1.toString()); final CtInvocation invocation2 = m.getBody().getStatement(1).getElements(new TypeFilter<>(CtInvocation.class)).get(0); - assertEquals(0, invocation2.getExecutable().getActualTypeArguments().size()); + assertEquals(1, invocation2.getExecutable().getActualTypeArguments().size()); assertEquals("this.makeTacos(null)", invocation2.toString()); canBeBuilt("./target/spooned/spoon/test/generics/testclasses/", 8); @@ -513,7 +514,7 @@ public void testMethodsWithGenericsWhoExtendsObject() { assertEquals("spoon.test.generics.testclasses.Tacos.makeTacos()", invocation1.toString()); final CtInvocation invocation2 = m.getBody().getStatement(1).getElements(new TypeFilter<>(CtInvocation.class)).get(0); - assertEquals(0, invocation2.getExecutable().getActualTypeArguments().size()); + assertEquals(2, invocation2.getExecutable().getActualTypeArguments().size()); assertEquals("spoon.test.generics.testclasses.Tacos.makeTacos()", invocation2.toString()); } @@ -1563,7 +1564,7 @@ public void testExecutableTypeParameter() { assertNull(m1.getType().getTypeParameterDeclaration()); } - @org.junit.jupiter.api.Test + @Test void testGenericMethodReference() { // contract: method references keep their generic parameters CtClass parsed = Launcher.parseClass("class X {\n" + @@ -1575,4 +1576,19 @@ void testGenericMethodReference() { assertThat(expression.getExecutable().getActualTypeArguments().size(), equalTo(1)); assertThat(expression.getExecutable().getActualTypeArguments().get(0).toString(), equalTo("java.lang.Integer")); } + + @Test + void testInferredTypesReturnedInInvocation() { + // contract: Inferred generic types for invocations are returned by getActualTypeArguments + CtClass ctClass = Launcher.parseClass("class Foo {\n" + + " T reflect(T t) { return t; }\n" + + " String user() { return reflect(\"hey\"); }\n" + + "}"); + CtMethod user = ctClass.getMethodsByName("user").get(0); + CtReturn ret = user.getBody().getStatement(0); + CtInvocation invocation = (CtInvocation) ret.getReturnedExpression(); + + assertEquals(1, invocation.getActualTypeArguments().size()); + assertEquals("java.lang.String", invocation.getActualTypeArguments().get(0).getQualifiedName()); + } } From 49df0996a07332aeef877e42f25a2142f01d4ab4 Mon Sep 17 00:00:00 2001 From: I-Al-Istannen Date: Thu, 18 Aug 2022 22:14:45 +0200 Subject: [PATCH 2/3] Use more expressive names for settings enum and overload method --- .../visitor/DefaultJavaPrettyPrinter.java | 12 +++---- .../reflect/visitor/ElementPrinterHelper.java | 34 +++++++++++++++++-- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java index ec532940fc9..9337bd82a7b 100644 --- a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java +++ b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java @@ -132,8 +132,8 @@ import java.util.Set; import java.util.stream.Collectors; -import static spoon.reflect.visitor.ElementPrinterHelper.PrintTypeArguments.INCLUDE_IF_IMPLICIT; -import static spoon.reflect.visitor.ElementPrinterHelper.PrintTypeArguments.OMIT_IF_IMPLICIT; +import static spoon.reflect.visitor.ElementPrinterHelper.PrintTypeArguments.ALSO_PRINT_DIAMOND_OPERATOR; +import static spoon.reflect.visitor.ElementPrinterHelper.PrintTypeArguments.ONLY_PRINT_EXPLICIT_TYPES; /** * A visitor for generating Java code from the program compile-time model. @@ -1359,7 +1359,7 @@ public void visitCtInvocation(CtInvocation invocation) { enterCtExpression(invocation); if (invocation.getExecutable().isConstructor()) { // It's a constructor (super or this) - elementPrinterHelper.writeActualTypeArguments(invocation.getExecutable(), OMIT_IF_IMPLICIT); + elementPrinterHelper.writeActualTypeArguments(invocation.getExecutable(), ONLY_PRINT_EXPLICIT_TYPES); CtType parentType = invocation.getParent(CtType.class); if (parentType == null || parentType.getQualifiedName() != null && parentType.getQualifiedName().equals(invocation.getExecutable().getDeclaringType().getQualifiedName())) { printer.writeKeyword("this"); @@ -1384,7 +1384,7 @@ public void visitCtInvocation(CtInvocation invocation) { } } - elementPrinterHelper.writeActualTypeArguments(invocation, OMIT_IF_IMPLICIT); + elementPrinterHelper.writeActualTypeArguments(invocation, ONLY_PRINT_EXPLICIT_TYPES); if (env.isPreserveLineNumbers()) { getPrinterHelper().adjustStartPosition(invocation); } @@ -1608,7 +1608,7 @@ private void printConstructorCall(CtConstructorCall ctConstructorCall) { printer.writeKeyword("new").writeSpace(); if (!ctConstructorCall.getActualTypeArguments().isEmpty()) { - elementPrinterHelper.writeActualTypeArguments(ctConstructorCall, INCLUDE_IF_IMPLICIT); + elementPrinterHelper.writeActualTypeArguments(ctConstructorCall, ALSO_PRINT_DIAMOND_OPERATOR); } scan(ctConstructorCall.getType()); @@ -1993,7 +1993,7 @@ private void visitCtTypeReference(CtTypeReference ref, boolean withGenerics) } if (withGenerics && !context.ignoreGenerics()) { try (Writable _context = context.modify().ignoreEnclosingClass(false)) { - elementPrinterHelper.writeActualTypeArguments(ref, INCLUDE_IF_IMPLICIT); + elementPrinterHelper.writeActualTypeArguments(ref, ALSO_PRINT_DIAMOND_OPERATOR); } } } diff --git a/src/main/java/spoon/reflect/visitor/ElementPrinterHelper.java b/src/main/java/spoon/reflect/visitor/ElementPrinterHelper.java index e436ca3c4e8..b8c11cf6ccc 100644 --- a/src/main/java/spoon/reflect/visitor/ElementPrinterHelper.java +++ b/src/main/java/spoon/reflect/visitor/ElementPrinterHelper.java @@ -238,6 +238,20 @@ public void writeFormalTypeParameters(CtFormalTypeDeclarer ctFormalTypeDeclarer) } } + /** + * Writes actual type arguments in a {@link CtActualTypeContainer} element. + * Passes {@link PrintTypeArguments#ONLY_PRINT_EXPLICIT_TYPES}. + * + * @param ctGenericElementReference Reference with actual type arguments. + * @see #writeActualTypeArguments(CtActualTypeContainer, PrintTypeArguments) + * @deprecated use {@link #writeActualTypeArguments(CtActualTypeContainer, PrintTypeArguments)}. This method is + * only kept for backwards compatibility. + */ + @Deprecated + public void writeActualTypeArguments(CtActualTypeContainer ctGenericElementReference) { + writeActualTypeArguments(ctGenericElementReference, PrintTypeArguments.ONLY_PRINT_EXPLICIT_TYPES); + } + /** * Writes actual type arguments in a {@link CtActualTypeContainer} element. * @@ -254,7 +268,7 @@ public void writeActualTypeArguments( } boolean allImplicit = arguments.stream().allMatch(CtElement::isImplicit); - if (allImplicit && handleImplicit == PrintTypeArguments.OMIT_IF_IMPLICIT) { + if (allImplicit && handleImplicit == PrintTypeArguments.ONLY_PRINT_EXPLICIT_TYPES) { return; } @@ -576,8 +590,22 @@ protected void printPermits(CtSealable sealable) { printer.decTab(); } + /** + * Whether to print generic types for references. This affects e.g. explicit type arguments for constructor + * or method calls. + * + * A diamond operator is only valid in some places. This enum controls whether they can and should be printed at + * a given location. + */ public enum PrintTypeArguments { - OMIT_IF_IMPLICIT, - INCLUDE_IF_IMPLICIT + /** + * Only print explicit type argument. Implicit (i.e. inferred types) are not printed. Consequently, this will + * also not print a diamond operator. + */ + ONLY_PRINT_EXPLICIT_TYPES, + /** + * Print explicit type arguments, but also print a diamond operator if implicit type arguments were used. + */ + ALSO_PRINT_DIAMOND_OPERATOR } } From 9f0be623d039eaa37c0fc191c7b17f8c0c2d96f4 Mon Sep 17 00:00:00 2001 From: I-Al-Istannen Date: Thu, 18 Aug 2022 22:21:02 +0200 Subject: [PATCH 3/3] Remove unnecessary isEmpty check --- .../java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java index 9337bd82a7b..3058bbb5c47 100644 --- a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java +++ b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java @@ -1607,9 +1607,7 @@ private void printConstructorCall(CtConstructorCall ctConstructorCall) { printer.writeKeyword("new").writeSpace(); - if (!ctConstructorCall.getActualTypeArguments().isEmpty()) { - elementPrinterHelper.writeActualTypeArguments(ctConstructorCall, ALSO_PRINT_DIAMOND_OPERATOR); - } + elementPrinterHelper.writeActualTypeArguments(ctConstructorCall, ALSO_PRINT_DIAMOND_OPERATOR); scan(ctConstructorCall.getType()); }