Skip to content

Commit 67cd6ea

Browse files
Use lambda parameter counts and block bodies for improved resolution
1 parent 1da32e8 commit 67cd6ea

File tree

4 files changed

+227
-1
lines changed

4 files changed

+227
-1
lines changed

javaparser-core/src/main/java/com/github/javaparser/resolution/logic/MethodResolutionLogic.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.github.javaparser.resolution.MethodUsage;
2525
import com.github.javaparser.resolution.TypeSolver;
2626
import com.github.javaparser.resolution.declarations.*;
27+
import com.github.javaparser.resolution.model.LambdaArgumentTypePlaceholder;
2728
import com.github.javaparser.resolution.model.SymbolReference;
2829
import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl;
2930
import com.github.javaparser.resolution.types.*;
@@ -67,6 +68,43 @@ public static boolean isApplicable(
6768
return isApplicable(method, name, argumentsTypes, typeSolver, false);
6869
}
6970

71+
private static boolean isConflictingLambdaType(
72+
LambdaArgumentTypePlaceholder lambdaPlaceholder, ResolvedType expectedType) {
73+
// TODO: It might be possible to use the resolved type variable here, but that would either require
74+
// a type parameters map to be passed in, or the type variable to be resolved here which could lead
75+
// to duplicated work or maybe infinite recursion.
76+
if (!expectedType.isReferenceType()) {
77+
return false;
78+
}
79+
Optional<MethodUsage> maybeFunctionalInterface = FunctionalInterfaceLogic.getFunctionalMethod(expectedType);
80+
if (maybeFunctionalInterface.isPresent()) {
81+
MethodUsage functionalInterface = maybeFunctionalInterface.get();
82+
// If the lambda expression does not have the same number of parameters as the functional interface
83+
// method, the lambda cannot implement that interface.
84+
if (lambdaPlaceholder.getParameterCount().isPresent()
85+
&& functionalInterface.getNoParams()
86+
!= lambdaPlaceholder.getParameterCount().get()) {
87+
return true;
88+
}
89+
// If the lambda method has a block body then:
90+
// 1. If the block contains a return statement with a returned value, the lambda can only implement
91+
// non-void methods.
92+
// 2. If the block contains an empty return statement, or no return statement, the lambda can only
93+
// implement void methods.
94+
if (lambdaPlaceholder.bodyBlockHasExplicitNonVoidReturn().isPresent()) {
95+
boolean lambdaReturnIsVoid =
96+
!lambdaPlaceholder.bodyBlockHasExplicitNonVoidReturn().get();
97+
if (lambdaReturnIsVoid && !functionalInterface.returnType().isVoid()) {
98+
return true;
99+
}
100+
if (!lambdaReturnIsVoid && functionalInterface.returnType().isVoid()) {
101+
return true;
102+
}
103+
}
104+
}
105+
return false;
106+
}
107+
70108
/**
71109
* Note the specific naming here -- parameters are part of the method declaration,
72110
* while arguments are the values passed when calling a method.
@@ -145,6 +183,11 @@ private static boolean isApplicable(
145183
for (int i = 0; i < countOfMethodParametersDeclared; i++) {
146184
ResolvedType expectedDeclaredType = methodDeclaration.getParam(i).getType();
147185
ResolvedType actualArgumentType = needleArgumentTypes.get(i);
186+
if (actualArgumentType instanceof LambdaArgumentTypePlaceholder
187+
&& isConflictingLambdaType(
188+
(LambdaArgumentTypePlaceholder) actualArgumentType, expectedDeclaredType)) {
189+
return false;
190+
}
148191
if ((expectedDeclaredType.isTypeVariable() && !(expectedDeclaredType.isWildcard()))
149192
&& expectedDeclaredType.asTypeParameter().declaredOnMethod()) {
150193
matchedParameters.put(expectedDeclaredType.asTypeParameter().getName(), actualArgumentType);

javaparser-core/src/main/java/com/github/javaparser/resolution/model/LambdaArgumentTypePlaceholder.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration;
2424
import com.github.javaparser.resolution.types.ResolvedType;
25+
import java.util.Optional;
2526

2627
/**
2728
* Placeholder used to represent a lambda argument type while it is being
@@ -33,10 +34,31 @@ public class LambdaArgumentTypePlaceholder implements ResolvedType {
3334

3435
private int pos;
3536

37+
private final Optional<Integer> parameterCount;
38+
39+
private final Optional<Boolean> bodyBlockHasExplicitNonVoidReturn;
40+
3641
private SymbolReference<? extends ResolvedMethodLikeDeclaration> method;
3742

3843
public LambdaArgumentTypePlaceholder(int pos) {
3944
this.pos = pos;
45+
this.parameterCount = Optional.empty();
46+
this.bodyBlockHasExplicitNonVoidReturn = Optional.empty();
47+
}
48+
49+
public LambdaArgumentTypePlaceholder(
50+
int pos, int parameterCount, Optional<Boolean> bodyBlockHasExplicitNonVoidReturn) {
51+
this.pos = pos;
52+
this.parameterCount = Optional.of(parameterCount);
53+
this.bodyBlockHasExplicitNonVoidReturn = bodyBlockHasExplicitNonVoidReturn;
54+
}
55+
56+
public Optional<Integer> getParameterCount() {
57+
return parameterCount;
58+
}
59+
60+
public Optional<Boolean> bodyBlockHasExplicitNonVoidReturn() {
61+
return bodyBlockHasExplicitNonVoidReturn;
4062
}
4163

4264
@Override

javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/JavaParserFacade.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.github.javaparser.ast.expr.*;
3434
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
3535
import com.github.javaparser.ast.stmt.ForEachStmt;
36+
import com.github.javaparser.ast.stmt.ReturnStmt;
3637
import com.github.javaparser.ast.type.Type;
3738
import com.github.javaparser.resolution.*;
3839
import com.github.javaparser.resolution.declarations.*;
@@ -264,7 +265,25 @@ private void solveArguments(
264265
while (parameterValue instanceof EnclosedExpr) {
265266
parameterValue = ((EnclosedExpr) parameterValue).getInner();
266267
}
267-
if (parameterValue.isLambdaExpr() || parameterValue.isMethodReferenceExpr()) {
268+
if (parameterValue.isLambdaExpr()) {
269+
LambdaExpr lambdaExpr = parameterValue.asLambdaExpr();
270+
Optional<Boolean> bodyBlockHasExplicitNonVoidReturn;
271+
if (!lambdaExpr.getBody().isBlockStmt()) {
272+
bodyBlockHasExplicitNonVoidReturn = Optional.empty();
273+
} else {
274+
Optional<ReturnStmt> explicitReturn = lambdaExpr.getBody().findFirst(ReturnStmt.class);
275+
if (explicitReturn.isPresent()) {
276+
bodyBlockHasExplicitNonVoidReturn =
277+
Optional.of(explicitReturn.get().getExpression().isPresent());
278+
} else {
279+
bodyBlockHasExplicitNonVoidReturn = Optional.of(false);
280+
}
281+
}
282+
LambdaArgumentTypePlaceholder placeholder = new LambdaArgumentTypePlaceholder(
283+
i, lambdaExpr.getParameters().size(), bodyBlockHasExplicitNonVoidReturn);
284+
argumentTypes.add(placeholder);
285+
placeholders.add(placeholder);
286+
} else if (parameterValue.isMethodReferenceExpr()) {
268287
LambdaArgumentTypePlaceholder placeholder = new LambdaArgumentTypePlaceholder(i);
269288
argumentTypes.add(placeholder);
270289
placeholders.add(placeholder);

javaparser-symbol-solver-testing/src/test/java/com/github/javaparser/symbolsolver/resolution/LambdaResolutionTest.java

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
package com.github.javaparser.symbolsolver.resolution;
2323

24+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2425
import static org.junit.jupiter.api.Assertions.assertEquals;
2526

2627
import com.github.javaparser.StaticJavaParser;
@@ -35,6 +36,7 @@
3536
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
3637
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
3738
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
39+
import org.junit.jupiter.api.Disabled;
3840
import org.junit.jupiter.api.Test;
3941

4042
class LambdaResolutionTest extends AbstractResolutionTest {
@@ -211,4 +213,144 @@ void lambdaAsVararg() {
211213
"java.util.function.Consumer<java.lang.String>",
212214
lambda.calculateResolvedType().describe());
213215
}
216+
217+
@Test
218+
void lambdaOverloadsWithDifferentParameterCounts1() {
219+
String source = "import java.util.function.Consumer;\n" + "class Test {\n"
220+
+ " void foo(Consumer<String> consumer) {}\n"
221+
+ " void foo(Runnable r) {}\n"
222+
+ " void test() {\n"
223+
+ " foo(input -> {});\n"
224+
+ " }\n"
225+
+ "}";
226+
227+
StaticJavaParser.getParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver()));
228+
final CompilationUnit cu = StaticJavaParser.parse(source);
229+
final MethodCallExpr call = cu.findFirst(MethodCallExpr.class).get();
230+
assertEquals(
231+
"Test.foo(java.util.function.Consumer<java.lang.String>)",
232+
call.resolve().getQualifiedSignature());
233+
assertEquals("void", call.calculateResolvedType().describe());
234+
}
235+
236+
@Test
237+
void lambdaOverloadsWithDifferentParameterCounts2() {
238+
String source = "import java.util.function.Consumer;\n" + "class Test {\n"
239+
+ " void foo(Consumer<java.lang.String> consumer) {}\n"
240+
+ " void foo(Runnable r) {}\n"
241+
+ " void test() {\n"
242+
+ " foo(() -> {});\n"
243+
+ " }\n"
244+
+ "}";
245+
246+
StaticJavaParser.getParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver()));
247+
final CompilationUnit cu = StaticJavaParser.parse(source);
248+
final MethodCallExpr call = cu.findFirst(MethodCallExpr.class).get();
249+
assertEquals("Test.foo(java.lang.Runnable)", call.resolve().getQualifiedSignature());
250+
assertEquals("void", call.calculateResolvedType().describe());
251+
}
252+
253+
@Test
254+
void lambdaOverloadsWithDifferentReturnTypes1() {
255+
String source = "import java.util.function.Consumer;\n" + "import java.util.function.Function;\n"
256+
+ "class Test {\n"
257+
+ " void foo(Consumer<String> consumer) {}\n"
258+
+ " void foo(Function<Integer, String> func) {}\n"
259+
+ " void test() {\n"
260+
+ " foo(input -> {});\n"
261+
+ " }\n"
262+
+ "}";
263+
264+
StaticJavaParser.getParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver()));
265+
final CompilationUnit cu = StaticJavaParser.parse(source);
266+
final MethodCallExpr call = cu.findFirst(MethodCallExpr.class).get();
267+
assertEquals(
268+
"Test.foo(java.util.function.Consumer<java.lang.String>)",
269+
call.resolve().getQualifiedSignature());
270+
assertEquals("void", call.calculateResolvedType().describe());
271+
}
272+
273+
@Test
274+
void lambdaOverloadsWithDifferentReturnTypes2() {
275+
String source = "import java.util.function.Consumer;\n" + "import java.util.function.Function;\n"
276+
+ "class Test {\n"
277+
+ " void foo(Consumer<String> consumer) {}\n"
278+
+ " void foo(Function<Integer, String> func) {}\n"
279+
+ " void test() {\n"
280+
+ " foo(input -> { return \"\"; });\n"
281+
+ " }\n"
282+
+ "}";
283+
284+
StaticJavaParser.getParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver()));
285+
final CompilationUnit cu = StaticJavaParser.parse(source);
286+
final MethodCallExpr call = cu.findFirst(MethodCallExpr.class).get();
287+
assertEquals(
288+
"Test.foo(java.util.function.Function<java.lang.Integer, java.lang.String>)",
289+
call.resolve().getQualifiedSignature());
290+
assertEquals("void", call.calculateResolvedType().describe());
291+
}
292+
293+
@Test
294+
void lambdaUsedAsPolymorphicArgument() {
295+
String source = "import java.util.function.Consumer;\n" + "import java.util.HashMap;"
296+
+ "class Test {\n"
297+
+ " void test() {\n"
298+
+ " HashMap<String, Consumer> map = new HashMap<>();"
299+
+ " map.put(\"\", input -> {});\n"
300+
+ " }\n"
301+
+ "}";
302+
303+
StaticJavaParser.getParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver()));
304+
final CompilationUnit cu = StaticJavaParser.parse(source);
305+
final MethodCallExpr call = cu.findFirst(MethodCallExpr.class).get();
306+
assertDoesNotThrow(() -> call.resolve().getQualifiedSignature());
307+
assertDoesNotThrow(() -> call.calculateResolvedType().describe());
308+
assertEquals("java.util.HashMap.put(K, V)", call.resolve().getQualifiedSignature());
309+
}
310+
311+
@Test
312+
void lambdaUsedAsOverloadedArrayAlternativeArgument() {
313+
String source = "import java.util.function.Consumer;\n" + "import java.util.function.Function;\n"
314+
+ "class Foo<S extends Consumer, T> {\n"
315+
+ " void foo(Object[] ts) {}\n"
316+
+ " void foo(T t) {}\n"
317+
+ "}\n"
318+
+ "class Test {\n"
319+
+ " void test() {\n"
320+
+ " Foo<Consumer<Integer>, Function<Integer, Integer>> foo = new Foo<>();\n"
321+
+ " foo.foo(value -> { return 2; });\n"
322+
+ " }\n"
323+
+ "}";
324+
325+
StaticJavaParser.getParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver()));
326+
final CompilationUnit cu = StaticJavaParser.parse(source);
327+
final MethodCallExpr call = cu.findFirst(MethodCallExpr.class).get();
328+
assertDoesNotThrow(() -> call.resolve().getQualifiedSignature());
329+
assertDoesNotThrow(() -> call.calculateResolvedType().describe());
330+
assertEquals("Foo.foo(T)", call.resolve().getQualifiedSignature());
331+
}
332+
333+
@Disabled("Disambiguation for lambdas used as polymorphic is not supported yet.")
334+
@Test
335+
void lambdaUsedAsOverloadedPolymorphicArgument1() {
336+
337+
String source = "import java.util.function.Consumer;\n" + "import java.util.function.Function;\n"
338+
+ "class Foo<S extends Consumer, T> {\n"
339+
+ " void foo(T t) {}\n"
340+
+ " void foo(S s) {}\n"
341+
+ "}\n"
342+
+ "class Test {\n"
343+
+ " void test() {\n"
344+
+ " Foo<Consumer<Integer>, Function<Integer, Integer>> foo = new Foo<>();\n"
345+
+ " foo.foo(value -> { return 2; });\n"
346+
+ " }\n"
347+
+ "}";
348+
349+
StaticJavaParser.getParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver()));
350+
final CompilationUnit cu = StaticJavaParser.parse(source);
351+
final MethodCallExpr call = cu.findFirst(MethodCallExpr.class).get();
352+
assertDoesNotThrow(() -> call.resolve().getQualifiedSignature());
353+
assertDoesNotThrow(() -> call.calculateResolvedType().describe());
354+
assertEquals("Foo.foo(java.util.function.Function)", call.resolve().getQualifiedSignature());
355+
}
214356
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy