diff --git a/src/main/java/org/checkerframework/specimin/PrunerVisitor.java b/src/main/java/org/checkerframework/specimin/PrunerVisitor.java index f6109c46..52a42390 100644 --- a/src/main/java/org/checkerframework/specimin/PrunerVisitor.java +++ b/src/main/java/org/checkerframework/specimin/PrunerVisitor.java @@ -335,6 +335,14 @@ public Visitable visit(MethodDeclaration methodDecl, Void p) { return super.visit(methodDecl, p); } + if (insideFunctionalInterface && usedMembers.contains(signature)) { + if (methodDecl.getBody().isPresent()) { + // avoid introducing unsolved symbols into the final output. + methodDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); + } + return methodDecl; + } + if (usedMembers.contains(signature) || isAResolvedYetStuckMethod(methodDecl)) { boolean isMethodInsideInterface = isInsideInterface(methodDecl); // do nothing if methodDecl is just a method signature in a class. @@ -348,14 +356,6 @@ public Visitable visit(MethodDeclaration methodDecl, Void p) { return methodDecl; } - if (insideFunctionalInterface) { - if (methodDecl.getBody().isPresent()) { - // avoid introducing unsolved symbols into the final output. - methodDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); - } - return methodDecl; - } - // if insideTargetMethod is true, this current method declaration belongs to an anonnymous // class inside the target method. if (!insideTargetMember) { diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index 11e6865b..ff3de534 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -26,6 +26,7 @@ import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.type.UnionType; import com.github.javaparser.ast.visitor.Visitable; +import com.github.javaparser.resolution.MethodUsage; import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; import com.github.javaparser.resolution.declarations.ResolvedEnumConstantDeclaration; @@ -470,6 +471,18 @@ public Visitable visit(MethodCallExpr call, Void p) { Expression arg = call.getArgument(i); if (arg.isLambdaExpr()) { updateUsedClassBasedOnType(decl.getParam(i).getType()); + // We should mark the abstract method for preservation as well + if (decl.getParam(i).getType().isReferenceType()) { + ResolvedReferenceType functionalInterface = + decl.getParam(i).getType().asReferenceType(); + for (MethodUsage method : functionalInterface.getDeclaredMethods()) { + if (method.getDeclaration().isAbstract()) { + preserveMethodDecl(method.getDeclaration()); + // Only one abstract method per functional interface + break; + } + } + } } } } diff --git a/src/test/java/org/checkerframework/specimin/UnusedFuncInterfaceMethodTest.java b/src/test/java/org/checkerframework/specimin/UnusedFuncInterfaceMethodTest.java new file mode 100644 index 00000000..1491d0cc --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/UnusedFuncInterfaceMethodTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** + * This test checks that if a functional interface has an unused method, it is removed, and if a + * synthetic functional interface method is generated, it is preserved. + */ +public class UnusedFuncInterfaceMethodTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "unusedfuncinterfacemethod", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#foo()"}); + } +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Bar.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Bar.java new file mode 100644 index 00000000..c49ad01c --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Bar.java @@ -0,0 +1,8 @@ +package com.example; + +public class Bar { + + public static ComExampleBarUseReturnType use(SyntheticFunction4 parameter0) { + throw new Error(); + } +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/ComExampleBarUseReturnType.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/ComExampleBarUseReturnType.java new file mode 100644 index 00000000..e2b5b035 --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/ComExampleBarUseReturnType.java @@ -0,0 +1,4 @@ +package com.example; + +public class ComExampleBarUseReturnType { +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/FuncInterfaceUnused.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/FuncInterfaceUnused.java new file mode 100644 index 00000000..6415d304 --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/FuncInterfaceUnused.java @@ -0,0 +1,4 @@ +package com.example; + +public interface FuncInterfaceUnused { +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Simple.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Simple.java new file mode 100644 index 00000000..3ab58a8b --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Simple.java @@ -0,0 +1,8 @@ +package com.example; + +public class Simple implements FuncInterfaceUnused { + + public void foo() { + Bar.use((a, b, c, d) -> 0); + } +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/SyntheticFunction4.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/SyntheticFunction4.java new file mode 100644 index 00000000..684d580d --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/SyntheticFunction4.java @@ -0,0 +1,6 @@ +package com.example; + +public interface SyntheticFunction4 { + + public T4 apply(T parameter0, T1 parameter1, T2 parameter2, T3 parameter3); +} diff --git a/src/test/resources/unusedfuncinterfacemethod/input/com/example/FuncInterfaceUnused.java b/src/test/resources/unusedfuncinterfacemethod/input/com/example/FuncInterfaceUnused.java new file mode 100644 index 00000000..d3ab6267 --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/input/com/example/FuncInterfaceUnused.java @@ -0,0 +1,6 @@ +package com.example; + +@FunctionalInterface +public interface FuncInterfaceUnused { + void shouldBeRemoved(); +} diff --git a/src/test/resources/unusedfuncinterfacemethod/input/com/example/Simple.java b/src/test/resources/unusedfuncinterfacemethod/input/com/example/Simple.java new file mode 100644 index 00000000..d8482bb6 --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/input/com/example/Simple.java @@ -0,0 +1,11 @@ +package com.example; + +public class Simple implements FuncInterfaceUnused { + public void foo() { + Bar.use((a, b, c, d) -> 0); + } + + public void shouldBeRemoved() { + + } +}