mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Java: Navigate to proper overload of a method being accessed via reflection (IDEA-172319)
This commit is contained in:
+1
-1
@@ -309,7 +309,7 @@ public class JavaLangInvokeHandleSignatureInspection extends BaseJavaBatchLocalI
|
||||
* from arguments of MethodType.methodType(Class...) and MethodType.genericMethodType(int, boolean?)
|
||||
*/
|
||||
@Nullable
|
||||
private static ReflectiveSignature composeMethodSignature(@Nullable PsiExpression methodTypeExpression) {
|
||||
public static ReflectiveSignature composeMethodSignature(@Nullable PsiExpression methodTypeExpression) {
|
||||
final PsiExpression typeDefinition = findDefinition(methodTypeExpression);
|
||||
if (typeDefinition instanceof PsiMethodCallExpression) {
|
||||
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)typeDefinition;
|
||||
|
||||
+24
-11
@@ -20,6 +20,7 @@ import com.intellij.codeInspection.InspectionsBundle;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.ParenthesesUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -131,22 +132,34 @@ public class JavaReflectionInvocationInspection extends BaseJavaBatchLocalInspec
|
||||
if (definition instanceof PsiMethodCallExpression) {
|
||||
final PsiMethodCallExpression definitionCall = (PsiMethodCallExpression)definition;
|
||||
if (methodPredicate.test(definitionCall)) {
|
||||
final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions();
|
||||
|
||||
if (arguments.length == argumentOffset + 1) {
|
||||
final PsiExpression[] arrayElements = getVarargAsArray(arguments[argumentOffset]);
|
||||
if (arrayElements != null) {
|
||||
return Arrays.asList(arrayElements);
|
||||
}
|
||||
}
|
||||
if (arguments.length >= argumentOffset) {
|
||||
return Arrays.asList(arguments).subList(argumentOffset, arguments.length);
|
||||
}
|
||||
return getRequiredMethodArguments(definitionCall, argumentOffset);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<PsiExpression> getRequiredMethodArguments(@NotNull PsiMethodCallExpression definitionCall, int argumentOffset) {
|
||||
final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions();
|
||||
|
||||
if (arguments.length == argumentOffset + 1) {
|
||||
final PsiExpression[] arrayElements = getVarargAsArray(arguments[argumentOffset]);
|
||||
if (arrayElements != null) {
|
||||
return Arrays.asList(arrayElements);
|
||||
}
|
||||
}
|
||||
if (arguments.length >= argumentOffset) {
|
||||
return Arrays.asList(arguments).subList(argumentOffset, arguments.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<ReflectiveType> getReflectionMethodParameterTypes(@NotNull PsiMethodCallExpression definitionCall,
|
||||
int argumentOffset) {
|
||||
List<PsiExpression> arguments = getRequiredMethodArguments(definitionCall, argumentOffset);
|
||||
return arguments != null ? ContainerUtil.map(arguments, type -> getReflectiveType(type)) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Arguments getActualMethodArguments(PsiExpression[] arguments, int argumentOffset, boolean allowVarargAsArray) {
|
||||
if (allowVarargAsArray && arguments.length == argumentOffset + 1) {
|
||||
|
||||
+5
@@ -279,6 +279,11 @@ public class JavaReflectionMemberAccessInspection extends BaseJavaBatchLocalInsp
|
||||
final List<ReflectiveType> argumentTypes =
|
||||
ContainerUtil.map(methodArguments.expressions, JavaReflectionReferenceUtil::getReflectiveType);
|
||||
|
||||
return matchMethod(methods, argumentTypes);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiMethod matchMethod(@NotNull PsiMethod[] methods, @NotNull List<ReflectiveType> argumentTypes) {
|
||||
int mismatchCount = Integer.MAX_VALUE;
|
||||
PsiMethod bestGuess = null;
|
||||
for (PsiMethod method : methods) {
|
||||
|
||||
+35
-12
@@ -18,9 +18,11 @@ package com.intellij.psi.impl.source.resolve.reference.impl;
|
||||
import com.intellij.codeInsight.completion.InsertHandler;
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
import com.intellij.codeInspection.reflectiveAccess.JavaReflectionInvocationInspection;
|
||||
import com.intellij.codeInspection.reflectiveAccess.JavaReflectionMemberAccessInspection;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import gnu.trove.THashSet;
|
||||
@@ -28,10 +30,7 @@ import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*;
|
||||
|
||||
@@ -73,17 +72,29 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
|
||||
}
|
||||
|
||||
case GET_METHOD: {
|
||||
final PsiMethod[] methods = psiClass.findMethodsByName(name, true);
|
||||
final PsiMethod publicMethod = ContainerUtil.find(methods, method -> isRegularMethod(method) && isPublic(method));
|
||||
if (publicMethod != null) {
|
||||
return publicMethod;
|
||||
PsiMethod[] methods = psiClass.findMethodsByName(name, true);
|
||||
if (methods.length > 1) {
|
||||
methods =
|
||||
ContainerUtil.filter(methods, method -> isRegularMethod(method) && isPublic(method))
|
||||
.toArray(PsiMethod.EMPTY_ARRAY);
|
||||
if (methods.length > 1) {
|
||||
return findOverloadedMethod(methods);
|
||||
}
|
||||
}
|
||||
return ContainerUtil.find(methods, method -> isRegularMethod(method));
|
||||
return methods.length != 0 ? methods[0] : null;
|
||||
}
|
||||
|
||||
case GET_DECLARED_METHOD: {
|
||||
final PsiMethod[] methods = psiClass.findMethodsByName(name, false);
|
||||
return ContainerUtil.find(methods, method -> isRegularMethod(method) && isPotentiallyAccessible(method, psiClass));
|
||||
PsiMethod[] methods = psiClass.findMethodsByName(name, false);
|
||||
if (methods.length > 1) {
|
||||
methods =
|
||||
ContainerUtil.filter(methods, method -> isRegularMethod(method) && isPotentiallyAccessible(method, psiClass))
|
||||
.toArray(PsiMethod.EMPTY_ARRAY);
|
||||
if (methods.length > 1) {
|
||||
return findOverloadedMethod(methods);
|
||||
}
|
||||
}
|
||||
return methods.length != 0 ? methods[0] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,6 +166,18 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
|
||||
return member != null && (member.getContainingClass() == psiClass || isPublic(member));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement findOverloadedMethod(PsiMethod[] methods) {
|
||||
final PsiMethodCallExpression definitionCall = PsiTreeUtil.getParentOfType(myElement, PsiMethodCallExpression.class);
|
||||
if (definitionCall != null) {
|
||||
final List<ReflectiveType> parameterTypes = JavaReflectionInvocationInspection.getReflectionMethodParameterTypes(definitionCall, 1);
|
||||
if (parameterTypes != null) {
|
||||
return JavaReflectionMemberAccessInspection.matchMethod(methods, parameterTypes);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInsert(InsertionContext context, LookupElement item) {
|
||||
final Object object = item.getObject();
|
||||
|
||||
+22
-3
@@ -19,13 +19,16 @@ import com.intellij.codeInsight.completion.InsertHandler;
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.codeInsight.completion.JavaLookupElementBuilder;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInspection.reflectiveAccess.JavaLangInvokeHandleSignatureInspection;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.ParenthesesUtils;
|
||||
import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -97,9 +100,25 @@ public class JavaLangInvokeHandleReference extends PsiReferenceBase<PsiLiteralEx
|
||||
return field != null && filter.value(field) ? field : null;
|
||||
}
|
||||
|
||||
private static PsiElement resolveMethod(@NotNull String name, @NotNull PsiClass psiClass, Condition<? super PsiMethod> filter) {
|
||||
final PsiMethod[] methods = psiClass.findMethodsByName(name, true);
|
||||
return ContainerUtil.find(methods, filter);
|
||||
private PsiElement resolveMethod(@NotNull String name, @NotNull PsiClass psiClass, Condition<? super PsiMethod> filter) {
|
||||
PsiMethod[] methods = psiClass.findMethodsByName(name, true);
|
||||
if (methods.length != 0) {
|
||||
methods = ContainerUtil.filter(methods, filter).toArray(PsiMethod.EMPTY_ARRAY);
|
||||
if (methods.length > 1) {
|
||||
final PsiMethodCallExpression definitionCall = PsiTreeUtil.getParentOfType(myElement, PsiMethodCallExpression.class);
|
||||
if (definitionCall != null) {
|
||||
final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions();
|
||||
if (arguments.length > 2) {
|
||||
final PsiExpression typeExpression = ParenthesesUtils.stripParentheses(arguments[2]);
|
||||
final ReflectiveSignature expectedSignature = JavaLangInvokeHandleSignatureInspection.composeMethodSignature(typeExpression);
|
||||
if (expectedSignature != null) {
|
||||
return ContainerUtil.find(methods, method -> expectedSignature.equals(getMethodSignature(method)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return methods.length != 0 ? methods[0] : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+63
-1
@@ -16,10 +16,13 @@
|
||||
package com.intellij.java.codeInsight.navigation
|
||||
|
||||
import com.intellij.psi.PsiMember
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
||||
import junit.framework.TestCase
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.intellij.lang.annotations.MagicConstant
|
||||
import org.jetbrains.annotations.NonNls
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
@@ -74,6 +77,64 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() {
|
||||
fun testStaticSetter6() = doNegativeTest("pf1", STATIC_SETTER)
|
||||
fun testStaticSetter7() = doNegativeTest("m1", STATIC_SETTER)
|
||||
|
||||
fun testOverloadedBothPublic() = doTestOverloaded(
|
||||
"""public class Overloaded {
|
||||
public void foo(int n) {}
|
||||
public void foo(String s) {}
|
||||
}""", VIRTUAL, "java.lang.String")
|
||||
|
||||
fun testOverloadedFirstPublic() = doTestOverloaded(
|
||||
"""public class Overloaded {
|
||||
public void foo(int n) {}
|
||||
void foo(String s) {}
|
||||
}""", VIRTUAL, "int")
|
||||
|
||||
fun testOverloadedSecondPublic() = doTestOverloaded(
|
||||
"""public class Overloaded {
|
||||
void foo(int n) {}
|
||||
public void foo(String s) {}
|
||||
}""", VIRTUAL, "java.lang.String")
|
||||
|
||||
fun testOverloadedInherited() {
|
||||
myFixture.addClass("""public class OverloadedParent {
|
||||
public static void foo(String s) {}
|
||||
}""")
|
||||
|
||||
doTestOverloaded(
|
||||
"""public class Overloaded extends OverloadedParent {
|
||||
public static void foo(int n) {}
|
||||
}""", STATIC, "java.lang.String")
|
||||
}
|
||||
|
||||
fun testOverloadedStatic() = doTestOverloaded(
|
||||
"""public class Overloaded {
|
||||
public static void foo(int n) {}
|
||||
public static void foo(String s) {}
|
||||
}""", STATIC, "java.lang.String")
|
||||
|
||||
|
||||
private fun doTestOverloaded(@NonNls @Language("JAVA") classText: String, function: String, vararg expectedParameterTypes: String) {
|
||||
myFixture.addClass(classText)
|
||||
|
||||
val methodType = arrayOf("void", *expectedParameterTypes).map { "$it.class" }.joinToString(", ")
|
||||
val member = doTestImpl("foo", """
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
class Main {
|
||||
void foo() throws ReflectiveOperationException {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
lookup.$function(Overloaded.class, "<caret>foo", MethodType.methodType($methodType));
|
||||
}
|
||||
}""")
|
||||
|
||||
TestCase.assertTrue("Is method", member is PsiMethod)
|
||||
val parameters = (member as PsiMethod).parameterList.parameters
|
||||
TestCase.assertEquals("Parameter count", expectedParameterTypes.size, parameters.size)
|
||||
for (i in 0 until expectedParameterTypes.size) {
|
||||
TestCase.assertEquals("Parameter $i", expectedParameterTypes[i], parameters[i].type.canonicalText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun doTest(name: String,
|
||||
@MagicConstant(stringValues = arrayOf(VIRTUAL, STATIC, SPECIAL,
|
||||
@@ -83,7 +144,7 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() {
|
||||
doTestImpl(name, getMainClassText(name, function))
|
||||
}
|
||||
|
||||
private fun doTestImpl(name: String, mainClassText: String) {
|
||||
private fun doTestImpl(name: String, mainClassText: String): PsiMember {
|
||||
val reference = getReference(mainClassText)
|
||||
TestCase.assertEquals("Reference text", name, reference.canonicalText)
|
||||
val resolved = reference.resolve()
|
||||
@@ -91,6 +152,7 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() {
|
||||
TestCase.assertTrue("Target is a member", resolved is PsiMember)
|
||||
val member = resolved as PsiMember?
|
||||
TestCase.assertEquals("Target name", name, member!!.name)
|
||||
return member
|
||||
}
|
||||
|
||||
private fun doNegativeTest(name: String,
|
||||
|
||||
+67
-4
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package com.intellij.java.codeInsight.navigation;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiMember;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
@@ -93,6 +91,70 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas
|
||||
"}");
|
||||
}
|
||||
|
||||
public void testOverloadedMethodBothPublic() {
|
||||
doTestOverloadedMethod("foo",
|
||||
"class Overloaded {\n" +
|
||||
" public void foo() {}\n" +
|
||||
" public void foo(String s) {}\n" +
|
||||
"}", false, "java.lang.String");
|
||||
}
|
||||
|
||||
public void testOverloadedMethodPublicFirst() {
|
||||
doTestOverloadedMethod("foo",
|
||||
"class Overloaded {\n" +
|
||||
" public void foo() {}\n" +
|
||||
" void foo(String s) {}\n" +
|
||||
"}", false);
|
||||
}
|
||||
|
||||
public void testOverloadedMethodPublicSecond() {
|
||||
doTestOverloadedMethod("foo",
|
||||
"class Overloaded {\n" +
|
||||
" void foo() {}\n" +
|
||||
" public void foo(String s) {}\n" +
|
||||
"}", false, "java.lang.String");
|
||||
}
|
||||
|
||||
public void testOverloadedDeclaredMethod() {
|
||||
doTestOverloadedMethod("foo",
|
||||
"class Overloaded {\n" +
|
||||
" public void foo() {}\n" +
|
||||
" public void foo(String s) {}\n" +
|
||||
"}", true, "java.lang.String");
|
||||
}
|
||||
|
||||
public void testOverloadedInheritedMethod() {
|
||||
doTestOverloadedMethod("bar",
|
||||
"class OverloadedParent {" +
|
||||
" public void bar(String s) {}\n" +
|
||||
"}" +
|
||||
"" +
|
||||
"class Overloaded extends OverloadedParent {\n" +
|
||||
" public void bar() {}\n" +
|
||||
"}", false, "java.lang.String");
|
||||
}
|
||||
|
||||
private void doTestOverloadedMethod(String name,
|
||||
@NotNull @NonNls @Language("JAVA") String classText,
|
||||
boolean isDeclared,
|
||||
String... expectedParameterTypes) {
|
||||
myFixture.addClass(classText);
|
||||
|
||||
PsiMember member = doTestImpl(name,
|
||||
"class Main {" +
|
||||
" void main() {" +
|
||||
" Overloaded.class.get" + (isDeclared?"Declared":"") + "Method(\"<caret>"+name+"\", String.class);" +
|
||||
" }" +
|
||||
"}");
|
||||
assertTrue("Target is a method", member instanceof PsiMethod);
|
||||
PsiMethod method = (PsiMethod)member;
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
assertEquals("Parameter count", expectedParameterTypes.length, parameters.length);
|
||||
for (int i = 0; i < expectedParameterTypes.length; i++) {
|
||||
assertEquals("Parameter type " + i, expectedParameterTypes[i], parameters[0].getType().getCanonicalText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void doTest(String name,
|
||||
@MagicConstant(stringValues = {FIELD, METHOD, DF, DM}) String type) {
|
||||
@@ -104,7 +166,7 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas
|
||||
doTestImpl(name, mainClassText);
|
||||
}
|
||||
|
||||
private void doTestImpl(String name, String mainClassText) {
|
||||
private PsiMember doTestImpl(String name, @NotNull @NonNls @Language("JAVA") String mainClassText) {
|
||||
PsiReference reference = getReference(mainClassText);
|
||||
assertEquals("Reference text", name, reference.getCanonicalText());
|
||||
PsiElement resolved = reference.resolve();
|
||||
@@ -112,6 +174,7 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas
|
||||
assertTrue("Target is a member", resolved instanceof PsiMember);
|
||||
PsiMember member = (PsiMember)resolved;
|
||||
assertEquals("Target name", name, member.getName());
|
||||
return member;
|
||||
}
|
||||
|
||||
private void doNegativeTest(String name,
|
||||
|
||||
Reference in New Issue
Block a user