Skip to content

Commit

Permalink
SONARJAVA-1873 Find method when using method references as initialize…
Browse files Browse the repository at this point in the history
…r of variable (#1029)

* SONARJAVA-1873 Find method when using method references as initializer of variable

* Refactor in order to keep responsability separated between resolve and typeAndRefSolver

* Complete unit test to validate correct references
  • Loading branch information
Wohops authored and benzonico committed Oct 11, 2016
1 parent cc42c11 commit 2acc22e
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 52 deletions.
59 changes: 21 additions & 38 deletions java-frontend/src/main/java/org/sonar/java/resolve/Resolve.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.sonar.java.ast.api.JavaKeyword;
import org.sonar.java.model.AbstractTypedTree;
import org.sonar.java.model.expression.ConditionalExpressionTreeImpl;
import org.sonar.java.model.expression.IdentifierTreeImpl;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.ConditionalExpressionTree;
Expand Down Expand Up @@ -623,51 +622,40 @@ private boolean isAcceptableDeferredType(Env env, DeferredType arg, JavaType for
AbstractTypedTree tree = arg.tree();
List<JavaType> samMethodArgs = findSamMethodArgs(formal);
if (tree.is(Tree.Kind.METHOD_REFERENCE)) {
return validMethodReference(env, (MethodReferenceTree) tree, formal, samMethodArgs);
return validMethodReference(env, (MethodReferenceTree) tree, samMethodArgs);
}
// we accept all deferred type as we will resolve this later, but reject lambdas with incorrect arity
return !tree.is(Tree.Kind.LAMBDA_EXPRESSION) || ((LambdaExpressionTree) tree).parameters().size() == samMethodArgs.size();
}

private boolean validMethodReference(Env env, MethodReferenceTree tree, JavaType formal, List<JavaType> samMethodArgs) {
Tree expression = tree.expression();
if (expression instanceof AbstractTypedTree) {
String searchedMethod = getMethodReferenceMethodName(tree.method().name());

JavaType expressionType = (JavaType) ((AbstractTypedTree) expression).symbolType();
if (isArrayConstructor(expressionType, searchedMethod)) {
setMethodRefType(tree, formal, expressionType);
return true;
}

Resolution resolution = findMethod(env, expressionType, searchedMethod, samMethodArgs);
// JLS §15.13.1
if (secondSearchRequired(expression, expressionType, resolution.symbol, samMethodArgs)) {
resolution = findMethod(env, expressionType, searchedMethod, samMethodArgs.stream().skip(1).collect(Collectors.toList()));
}
private boolean validMethodReference(Env env, MethodReferenceTree tree, List<JavaType> samMethodArgs) {
if (isArrayConstructor(tree)) {
return true;
}
Resolution resolution = findMethodReference(env, samMethodArgs, tree);
return !resolution.symbol.isUnknown();
}

if (!resolution.symbol.isUnknown()) {
if (tree.method().symbol().isUnknown()) {
associateReference(tree.method(), (JavaSymbol.MethodJavaSymbol) resolution.symbol);
setMethodRefType(tree, formal, resolution.type);
}
return true;
}
Resolution findMethodReference(Env env, List<JavaType> samMethodArgs, MethodReferenceTree methodRefTree) {
Tree expression = methodRefTree.expression();
JavaType expressionType = (JavaType) ((AbstractTypedTree) expression).symbolType();
String methodName = getMethodReferenceMethodName(methodRefTree.method().name());
Resolution resolution = findMethod(env, expressionType, methodName, samMethodArgs);
// JLS §15.13.1
if (secondSearchRequired(expression, expressionType, resolution.symbol, samMethodArgs)) {
resolution = findMethod(env, expressionType, methodName, samMethodArgs.stream().skip(1).collect(Collectors.toList()));
}
return false;
return resolution;
}

private static String getMethodReferenceMethodName(String methodName) {
return JavaKeyword.NEW.getValue().equals(methodName) ? CONSTRUCTOR_NAME : methodName;
}

private static boolean isArrayConstructor(JavaType expressionType, String searchedMethod) {
return expressionType.isArray() && CONSTRUCTOR_NAME.equals(searchedMethod);
}

private static void setMethodRefType(MethodReferenceTree methodRef, JavaType methodRefType, JavaType methodType) {
((AbstractTypedTree) methodRef).setType(methodRefType);
((AbstractTypedTree) methodRef.method()).setType(methodType);
private static boolean isArrayConstructor(MethodReferenceTree tree) {
JavaType expressionType = (JavaType) ((AbstractTypedTree) tree.expression()).symbolType();
String methodName = tree.method().name();
return expressionType.isArray() && JavaKeyword.NEW.getValue().equals(methodName);
}

private static boolean secondSearchRequired(Tree expression, JavaType expressionType, JavaSymbol symbol, List<JavaType> samMethodArgs) {
Expand All @@ -687,11 +675,6 @@ private static boolean firstParamSubtypeOfRefType(JavaType expressionType, List<
return samMethodArgs.isEmpty() || samMethodArgs.get(0).isSubtypeOf(expressionType.erasure());
}

private static void associateReference(IdentifierTree identifier, JavaSymbol.MethodJavaSymbol method) {
((IdentifierTreeImpl) identifier).setSymbol(method);
method.addUsage(identifier);
}

private boolean callWithRawType(JavaType arg, JavaType formal) {
return formal.isParameterized() && !arg.isParameterized() && types.isSubtype(arg, formal.erasure());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -936,21 +936,26 @@ public void visitMethodReference(MethodReferenceTree methodReferenceTree) {
MethodReferenceTreeImpl methodRefTree = (MethodReferenceTreeImpl) methodReferenceTree;
if(methodRefTree.isTypeSet()) {
JavaType methodRefType = (JavaType) methodRefTree.symbolType();
resolve.getSamMethod(methodRefType)
.map(samMethod -> (JavaType) samMethod.returnType().type())
.ifPresent(samReturnType -> {
JavaSymbol methodSymbol = (JavaSymbol) methodRefTree.method().symbol();
if (methodSymbol.isMethodSymbol()) {
JavaType capturedReturnType = resolve.resolveTypeSubstitution(samReturnType, methodRefType);
JavaType refinedReturnType = ((MethodJavaType) methodRefTree.method().symbolType()).resultType();
if ("<init>".equals(methodSymbol.name)) {
refinedReturnType = refinedTypeForConstructor(capturedReturnType, refinedReturnType);
}
refineType(methodRefTree, methodRefType, capturedReturnType, refinedReturnType);
} else {
handleNewArray(methodReferenceTree, methodRefType, samReturnType);
resolve.getSamMethod(methodRefType).ifPresent(samMethod -> {
JavaType samReturnType = (JavaType) samMethod.returnType().type();
List<JavaType> samMethodArgs = resolve.findSamMethodArgs(methodRefType);
Resolution resolution = resolve.findMethodReference(semanticModel.getEnv(methodReferenceTree), samMethodArgs, methodRefTree);
JavaSymbol methodSymbol = resolution.symbol();
if (methodSymbol.isMethodSymbol()) {
IdentifierTree methodIdentifier = methodRefTree.method();
addMethodRefReference(methodIdentifier, methodSymbol);
setMethodRefType(methodRefTree, methodRefType, resolution.type());

JavaType capturedReturnType = resolve.resolveTypeSubstitution(samReturnType, methodRefType);
JavaType refinedReturnType = ((MethodJavaType) methodIdentifier.symbolType()).resultType();
if ("<init>".equals(methodSymbol.name)) {
refinedReturnType = refinedTypeForConstructor(capturedReturnType, refinedReturnType);
}
});
refineType(methodRefTree, methodRefType, capturedReturnType, refinedReturnType);
} else {
handleNewArray(methodRefTree, methodRefType, samReturnType);
}
});
} else {
// TODO : SONARJAVA-1663 : consider type arguments for method resolution and substitution
scan(methodReferenceTree.typeArguments());
Expand All @@ -959,6 +964,16 @@ public void visitMethodReference(MethodReferenceTree methodReferenceTree) {
}
}

private static void addMethodRefReference(IdentifierTree methodIdentifier, JavaSymbol methodSymbol) {
((IdentifierTreeImpl) methodIdentifier).setSymbol(methodSymbol);
methodSymbol.addUsage(methodIdentifier);
}

private static void setMethodRefType(MethodReferenceTree methodRef, JavaType methodRefType, JavaType methodType) {
((AbstractTypedTree) methodRef).setType(methodRefType);
((AbstractTypedTree) methodRef.method()).setType(methodType);
}

private JavaType refinedTypeForConstructor(JavaType capturedReturnType, JavaType refinedReturnType) {
JavaType sanitizedCaptured = capturedReturnType;
JavaType refinedConstructorType = refinedReturnType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import java.util.function.Function;

class A {
private final Runnable invalidator = this::invalidate;
private final Function<String, Object> myFunc = A::foo;

private void invalidate() { }
private static Object foo(String s) { return null; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,19 @@ public void infer_method_invocation_return_type_on_chained_parameterized_methods
assertThat(usages).hasSize(1);
}

@Test
public void method_reference_as_variable_initializer() {
Result result = Result.createFor("MethodReferencesVariableInitializers");

JavaSymbol invalidate = result.symbol("invalidate");
assertThat(invalidate.usages()).hasSize(1);
assertThat(result.reference(4, 46)).isEqualTo(invalidate);

JavaSymbol foo = result.symbol("foo");
assertThat(foo.usages()).hasSize(1);
assertThat(result.reference(5, 54)).isEqualTo(foo);
}

@Test
public void double_lambda_type_propagation() {
Result result = Result.createFor("DoubleLambda");
Expand Down

0 comments on commit 2acc22e

Please sign in to comment.