Javaparser: [Java Symbol Solver] Resolve type from functional interface

Created on 7 Apr 2020  路  11Comments  路  Source: javaparser/javaparser

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.

Bug report Reproducible testcase provided Symbol Solver

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):

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());
        }
    }

}

All 11 comments

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.

  1. I changed the javaparser dependencies to use javaparser-symbol-solver-core in version 3.15.18, but unfortunately it did not solve the issue.
  2. Using the functional interface without generic parameter/type inference does not solve this issue as well. The following source code can be used for the reproduction:
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);
    }
}
  1. What's interesting is that the following code resolves correctly!
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 )

  • anonymous inner class: works
  • lambda with no parameter: fails
  • lambda with one parameter that is implicitly typed: fails
  • lambda with one parameter that is explicitly typed: works

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.

    • This is seemingly correct as it might be that the method is defined several layers deep (e.g. implements NoOpInterface, which extends ...).
    • This means that use of getDeclaredMethods() (which returns only the methods declared directly, not inherited) seemingly isn't appropriate.
    • _Note: I say "seemingly" because it might be these assumptions aren't correct._
  • Assuming the above is correct and stands up to scrutiny, this means that methods containing 0..* items (as opposed to 0..1 is appropriate

    • In this case (where 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):

    • > Return a list of all the methods declared of this type declaration, either declared or inherited. Note that it should not include overridden methods.
  • Given that the declared type of methods is (effectively) Function<String, Integer>...

    • ... It seems that the root cause might _actually_ be that the method from Function is showing as apply(Object c):T rather than apply(String c):Integer
    • Similarly, #apply(String c):T rather than #apply(String c):Integer

  • Given this...

    • Either we need to fix the filtering on the methods that are returned,

    • OR we need to do some additional resolution to determine the types of the generic parameters to assist with disambiguation.

_(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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

matozoid picture matozoid  路  5Comments

lingbozhang picture lingbozhang  路  4Comments

nerzid picture nerzid  路  4Comments

qingdujun picture qingdujun  路  4Comments

matozoid picture matozoid  路  3Comments