QUESTION: does javaparser support the resolution of types from functional interfaces? Or the java.lang.UnsupportedOperationException addresses the lack of this functionality (see description below for more information)?
PROBLEM:
I have the following code which I try to resolve (be careful, javaparser types are coincidental, I am parsing a project that utilizes the usage of javaparser):
@Override
public List<Metric<Integer>> compute(CompilationUnit compilationUnit) {
Set<String> foreignProviders = new HashSet<>();
ClassMetric<Integer> fdp = c -> {
List<String> classFieldNames = getAllClassFieldNames(c);
// DELETED FOR SAKE OF BREVITY
return foreignProviders.size();
};
return getMetricForClass(fdp, compilationUnit);
}
There seems to be a problem with resolution of getAllClassFieldNames(c);. The parameter c is of type ClassOrInterfaceDeclaration (be careful, javaparser types are coincidental, I am parsing a project that utilizes the usage of javaparser) and it comes from the following functional interface:
@FunctionalInterface
public interface ClassMetric<T> extends Function<ClassOrInterfaceDeclaration, T> {
T apply(ClassOrInterfaceDeclaration c);
}
I get the following exception. There seems to be a problem with resolution of the type from the interface.
Exception in thread "main" java.lang.RuntimeException: Unable to calculate the type of a parameter of a method call. Method call: getAllClassFieldNames(c), Parameter: c
at com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solveArguments(JavaParserFacade.java:225)
at com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solve(JavaParserFacade.java:240)
at com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solve(JavaParserFacade.java:125)
at com.github.javaparser.symbolsolver.JavaSymbolSolver.resolveDeclaration(JavaSymbolSolver.java:140)
at com.github.javaparser.ast.expr.MethodCallExpr.resolve(MethodCallExpr.java:313)
at Scratch.lambda$main$1(scratch_4.java:36)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
at Scratch.main(scratch_4.java:36)
Caused by: java.lang.UnsupportedOperationException
at com.github.javaparser.symbolsolver.javaparsermodel.contexts.LambdaExprContext.solveSymbolAsValue(LambdaExprContext.java:127)
<CUT FOR SAKE OF BREVITY>
I added the required TypeSolvers (JarTypeSovler for javaparser library jar and JavaParserTypeSolver for the project itself) to my JavaParser configuration. I can post this code as well.
Thank you for the report @arekziobrowski !
I've replicated with this minimal self-contained test case based on the provided code.
Additional work/tests will be needed to figure out exactly what is going on --- _e.g. is the issue/fix related to #1962 in any way? Is the error because it's a lambda / inferred parameter types? or because of the functional interface?_
package com.github.javaparser.symbolsolver;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ParseStart;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import org.junit.jupiter.api.Test;
import java.util.List;
import static com.github.javaparser.Providers.provider;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class Issue2595Test {
@Test
public void issue2595Test() {
String sourceCode = "" +
"import java.util.ArrayList;\n" +
"import java.util.List;\n" +
"import java.util.function.Function;\n" +
"\n" +
"public class Test {\n" +
"\n" +
" ClassMetric<Integer> fdp = c -> {\n" +
" List<String> classFieldNames = getAllClassFieldNames(c);\n" +
" return classFieldNames.size();\n" +
" };\n" +
"\n" +
"\n" +
" private List<String> getAllClassFieldNames(final String c) {\n" +
" return new ArrayList<>();\n" +
" }\n" +
"\n" +
"\n" +
" @FunctionalInterface\n" +
" public interface ClassMetric<T> extends Function<String, T> {\n" +
" @Override\n" +
" T apply(String c);\n" +
" }\n" +
"\n" +
"}\n";
TypeSolver typeSolver = new CombinedTypeSolver(new ReflectionTypeSolver());
ParserConfiguration configuration = new ParserConfiguration().setSymbolResolver(new JavaSymbolSolver(typeSolver));
JavaParser javaParser = new JavaParser(configuration);
ParseResult<CompilationUnit> result = javaParser.parse(ParseStart.COMPILATION_UNIT, provider(sourceCode));
assumeTrue(result.isSuccessful());
assumeTrue(result.getResult().isPresent());
CompilationUnit cu = result.getResult().get();
// System.out.println(cu);
List<MethodCallExpr> methodCalls = cu.findAll(MethodCallExpr.class);
assumeFalse(methodCalls.isEmpty());
for (int i = methodCalls.size() - 1; i >= 0; i--) {
MethodCallExpr methodCallExpr = methodCalls.get(i);
System.out.println();
System.out.println("methodCallExpr = " + methodCallExpr);
System.out.println("methodCallExpr.resolve() = " + methodCallExpr.resolve());
System.out.println("methodCallExpr.calculateResolvedType() = " + methodCallExpr.calculateResolvedType());
}
}
}
Output:
```
methodCallExpr = classFieldNames.size()
methodCallExpr.resolve() = ReflectionMethodDeclaration{method=public abstract int java.util.List.size()}
methodCallExpr.calculateResolvedType() = PrimitiveTypeUsage{name='int'}
methodCallExpr = getAllClassFieldNames(c)
java.lang.RuntimeException: Unable to calculate the type of a parameter of a method call. Method call: getAllClassFieldNames(c), Parameter: c
at com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solveArguments(JavaParserFacade.java:252)
at com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solve(JavaParserFacade.java:267)
at com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solve(JavaParserFacade.java:131)
at com.github.javaparser.symbolsolver.JavaSymbolSolver.resolveDeclaration(JavaSymbolSolver.java:161)
at com.github.javaparser.ast.expr.MethodCallExpr.resolve(MethodCallExpr.java:313)
at com.github.javaparser.symbolsolver.Issue2595Test.test(Issue2595Test.java:68)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:212)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:208)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)`
Thank you @MysterAitch for quickly picking up this issue! :)
I did some additional testing.
javaparser dependencies to use javaparser-symbol-solver-core in version 3.15.18, but unfortunately it did not solve the issue.import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class TestIssue2595 {
ClassMetric fdp = c -> {
List<String> classFieldNames = getAllClassFieldNames(c);
return classFieldNames.size();
};
private List<String> getAllClassFieldNames(final String c) {
return new ArrayList<>();
}
@FunctionalInterface
public interface ClassMetric extends Function<String, Integer> {
@Override
Integer apply(String c);
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class TestIssue2595 {
ClassMetric fdp = new ClassMetric() {
@Override
public Integer apply(String c) {
List<String> classFieldNames = getAllClassFieldNames(c);
return classFieldNames.size();
}
};
private List<String> getAllClassFieldNames(final String c) {
return new ArrayList<>();
}
@FunctionalInterface
public interface ClassMetric extends Function<String, Integer> {
@Override
Integer apply(String c);
}
}
I believe there is a problem with type resolution from the lambda expression.
Brill, really appreciate that you followed up and have done some additional testing!!
If you still have it in front of you, could you check is there any difference when the lambda parameter c is given an explicit type please?
Similarly if you could explore whether removing the parameter completely helps move things along, then that could be helpful/an interesting direction to explore too!
_(Assuming I understood correctly, that'll help with figuring out if it is resolution of the parameter/argument blocking resolution of the method.)_
Great insight!
The following code resolves correctly:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class TestIssue2595 {
ClassMetric fdp = (String c) -> {
List<String> classFieldNames = getAllClassFieldNames(c);
return classFieldNames.size();
};
private List<String> getAllClassFieldNames(final String c) {
return new ArrayList<>();
}
@FunctionalInterface
public interface ClassMetric extends Function<String, Integer> {
@Override
Integer apply(String c);
}
}
So there seems it is the inference of lambda parameter c type that is problematic?
I did some additional follow-up.
The following test sample does not resolve, but the interesting part is that it fails with other exception.
import java.util.ArrayList;
import java.util.List;
public class TestIssue2595 {
ClassMetric fdp = () -> {
List<String> classFieldNames = getAllClassFieldNames();
return classFieldNames.size();
};
private List<String> getAllClassFieldNames() {
return new ArrayList<>();
}
@FunctionalInterface
public interface ClassMetric {
Integer apply();
}
}
It generates the following exception:
Exception in thread "main" UnsolvedSymbolException{context='null', name='We are unable to find the method declaration corresponding to getAllClassFieldNames()', cause='null'}
at com.github.javaparser.symbolsolver.JavaSymbolSolver.resolveDeclaration(JavaSymbolSolver.java:146)
at com.github.javaparser.ast.expr.MethodCallExpr.resolve(MethodCallExpr.java:313)
<CUT FOR SAKE OF BREVITY>
Hmm, bizarre! The zero-parameter lambda has somewhat thrown my theories about what it might be.
I'll not be able to get to this properly again until tomorrow -- could I be extremely lazy and ask you to package the testcases up neatly as JUnit tests, so that I can just copy paste them? (yup, super lazy of me :P )
Anything I've missed there in that summary?
You've got it all covered in the summary.
Here are the JUnit tests (I am pasting them here, instead of linking my fork so the future readers could have the reference):
package com.github.javaparser.symbolsolver;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ParseStart;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import org.junit.jupiter.api.Test;
import java.util.List;
import static com.github.javaparser.Providers.provider;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class Issue2595Test {
@Test
public void issue2595ImplicitTypeLambdaTest() {
String sourceCode = "" +
"import java.util.ArrayList;\n" +
"import java.util.List;\n" +
"import java.util.function.Function;\n" +
"\n" +
"public class Test {\n" +
"\n" +
" ClassMetric<Integer> fdp = c -> {\n" +
" List<String> classFieldNames = getAllClassFieldNames(c);\n" +
" return classFieldNames.size();\n" +
" };\n" +
"\n" +
"\n" +
" private List<String> getAllClassFieldNames(final String c) {\n" +
" return new ArrayList<>();\n" +
" }\n" +
"\n" +
"\n" +
" @FunctionalInterface\n" +
" public interface ClassMetric<T> extends Function<String, T> {\n" +
" @Override\n" +
" T apply(String c);\n" +
" }\n" +
"\n" +
"}\n";
parse(sourceCode);
}
@Test
public void issue2595ExplicitTypeLambdaTest() {
String sourceCode = "import java.util.ArrayList;\n" +
"import java.util.List;\n" +
"import java.util.function.Function;\n" +
"\n" +
"public class TestIssue2595 {\n" +
" ClassMetric fdp = (String c) -> {\n" +
" List<String> classFieldNames = getAllClassFieldNames(c);\n" +
" return classFieldNames.size();\n" +
" };\n" +
" \n" +
"\n" +
" private List<String> getAllClassFieldNames(final String c) {\n" +
" return new ArrayList<>();\n" +
" }\n" +
"\n" +
" @FunctionalInterface\n" +
" public interface ClassMetric extends Function<String, Integer> {\n" +
" @Override\n" +
" Integer apply(String c);\n" +
" }\n" +
"}";
parse(sourceCode);
}
@Test
public void issue2595NoParameterLambdaTest() {
String sourceCode = "import java.util.ArrayList;\n" +
"import java.util.List;\n" +
"\n" +
"public class TestIssue2595 {\n" +
" ClassMetric fdp = () -> {\n" +
" List<String> classFieldNames = getAllClassFieldNames();\n" +
" return classFieldNames.size();\n" +
" };\n" +
"\n" +
"\n" +
" private List<String> getAllClassFieldNames() {\n" +
" return new ArrayList<>();\n" +
" }\n" +
"\n" +
" @FunctionalInterface\n" +
" public interface ClassMetric {\n" +
" Integer apply();\n" +
" }\n" +
"}";
parse(sourceCode);
}
@Test
public void issue2595AnonymousInnerClassTest() {
String sourceCode = "import java.util.ArrayList;\n" +
"import java.util.List;\n" +
"import java.util.function.Function;\n" +
"\n" +
"public class TestIssue2595 {\n" +
" ClassMetric fdp = new ClassMetric() {\n" +
" @Override\n" +
" public Integer apply(String c) {\n" +
" List<String> classFieldNames = getAllClassFieldNames(c);\n" +
" return classFieldNames.size();\n" +
" }\n" +
" };\n" +
"\n" +
" private List<String> getAllClassFieldNames(final String c) {\n" +
" return new ArrayList<>();\n" +
" }\n" +
"\n" +
" @FunctionalInterface\n" +
" public interface ClassMetric extends Function<String, Integer> {\n" +
" @Override\n" +
" Integer apply(String c);\n" +
" }\n" +
"}";
parse(sourceCode);
}
private void parse(String sourceCode) {
TypeSolver typeSolver = new CombinedTypeSolver(new ReflectionTypeSolver());
ParserConfiguration configuration = new ParserConfiguration().setSymbolResolver(new JavaSymbolSolver(typeSolver));
JavaParser javaParser = new JavaParser(configuration);
ParseResult<CompilationUnit> result = javaParser.parse(ParseStart.COMPILATION_UNIT, provider(sourceCode));
assumeTrue(result.isSuccessful());
assumeTrue(result.getResult().isPresent());
CompilationUnit cu = result.getResult().get();
// System.out.println(cu);
List<MethodCallExpr> methodCalls = cu.findAll(MethodCallExpr.class);
assumeFalse(methodCalls.isEmpty());
for (int i = methodCalls.size() - 1; i >= 0; i--) {
MethodCallExpr methodCallExpr = methodCalls.get(i);
System.out.println();
System.out.println("methodCallExpr = " + methodCallExpr);
System.out.println("methodCallExpr.resolve() = " + methodCallExpr.resolve());
System.out.println("methodCallExpr.calculateResolvedType() = " + methodCallExpr.calculateResolvedType());
}
}
}
I had a similar problem some time ago, I thought the bug was located in the com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration#getAllMethods() method which can returns duplicates for overridden methods with different generic types.
This causes the com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic#getFunctionalMethod() methods to return an empty Optional because it thinks there is more than 1 method (which is not the case in this situation).
I didn't know how to fix it and didn't had much time to investigate it any further at that time.
Maarten
Thanks @maartenc -- really appreciate that note!
Edit: Yes, I've just been debugging and have come to the same conclusion as @maartenc . I've just pushed a commit to the work-in-progress PR #2604 with the following (where we can attach a breakpoint to the exception line).
/**
* Get the functional method defined by the type, if any.
*/
public static Optional<MethodUsage> getFunctionalMethod(ResolvedReferenceTypeDeclaration typeDeclaration) {
//We need to find all abstract methods
Set<MethodUsage> methods = typeDeclaration.getAllMethods().stream()
.filter(m -> m.getDeclaration().isAbstract())
// Remove methods inherited by Object:
// Consider the case of Comparator which define equals. It would be considered a functional method.
.filter(m -> !declaredOnObject(m))
.collect(Collectors.toSet());
if (methods.size() == 1) {
// Only one match - return that
return Optional.of(methods.iterator().next());
} else if (methods.size() > 1) {
// Multiple matches - must disambiguate / select the "most appropriate" per JLS ....
throw new UnsupportedOperationException("TODO: Not yet implemented.");
} else {
// No matches - return empty
return Optional.empty();
}
}
I need to step away for a short while to mull things over and possibly gain some new perspectives, but before I do here are my (unstructured) thoughts and assumptions so far. If it makes sense to you and you'd like to comment, please do! :)
methods contains all abstract methods that are in the type declaration AND its ancestors.
getDeclaredMethods() (which returns only the methods declared directly, not inherited) seemingly isn't appropriate.Assuming the above is correct and stands up to scrutiny, this means that methods containing 0..* items (as opposed to 0..1 is appropriate
interface ClassMetric<T> extends Function<String, T>):ClassMetric<T>#apply(String c):T (from ClassMetric) AND Function<T>#apply(Object c):T (from Function),... This would mean that FunctionalInterfaceLogic#getFunctionalMethod() is written correctly, but is being given "bad input" in that the matched methods put into / coming out of the stream needs to be fixed.
This means that we must search for the "most specific" method that matches -- in this case we want the one from ClassMetric, not Function.
This brings me, somewhat clumsily, to the javadoc on getAllMethods() (emphasis on mine):
Given that the declared type of methods is (effectively) Function<String, Integer>...
Function is showing as apply(Object c):T rather than apply(String c):Integer#apply(String c):T rather than #apply(String c):Integer_(my current thinking is the latter)_
Hello @MysterAitch , thanks for looking into this issue in more details.
I believe the bug is in the getAllMethods() which should only return the apply method from ClassMetric, and not the applymethod from Function as well because that method is overridden.
So I think the fix here is to change the getAllMethods()so that it does what the javadoc says. But that means we have to take these generics into account when filtering away the overridden methods (and it was at this point I got stuck some time ago when trying to fix the issue).
Maarten
Most helpful comment
You've got it all covered in the summary.
Here are the JUnit tests (I am pasting them here, instead of linking my fork so the future readers could have the reference):