diff --git a/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java b/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java index 17fd89007daf..c685d28e8a90 100644 --- a/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java @@ -16,6 +16,7 @@ package com.intellij.psi; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Computable; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.infos.MethodCandidateInfo; @@ -560,6 +561,37 @@ public class LambdaUtil { return true; } + public static boolean isAcceptable(PsiMethodReferenceExpression methodReferenceExpression, PsiClassType left) { + final JavaResolveResult[] results = methodReferenceExpression.multiResolve(false); + for (JavaResolveResult result : results) { + final PsiElement resolve = result.getElement(); + if (resolve instanceof PsiMethod) { + final PsiClassType.ClassResolveResult resolveResult = left.resolveGenerics(); + final PsiMethod method = getFunctionalInterfaceMethod(resolveResult); + if (method != null) { + final MethodSignature signature1 = method.getSignature(resolveResult.getSubstitutor()); + final MethodSignature signature2 = ((PsiMethod)resolve).getSignature(result.getSubstitutor()); + if (areAcceptable(signature1, signature2)) return true; + } + } + } + return false; + } + + public static boolean areAcceptable(MethodSignature signature1, MethodSignature signature2) { + final PsiType[] signatureParameterTypes1 = signature1.getParameterTypes(); + final PsiType[] signatureParameterTypes2 = signature2.getParameterTypes(); + if (signatureParameterTypes1.length != signatureParameterTypes2.length) return false; + for (int i = 0; i < signatureParameterTypes1.length; i++) { + final PsiType type1 = signatureParameterTypes1[i]; + final PsiType type2 = signatureParameterTypes2[i]; + if (!Comparing.equal(GenericsUtil.eliminateWildcards(type1), GenericsUtil.eliminateWildcards(type2))) { + return false; + } + } + return true; + } + private static class TypeParamsChecker extends PsiTypeVisitor { private PsiMethod myMethod; private final PsiClass myClass; diff --git a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java index 01e89aa28bbf..59e4665b235f 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java @@ -652,8 +652,12 @@ public class TypeConversionUtil { return !(left instanceof PsiPrimitiveType) || isNullType(left); } - // todo[r.sh] implement - if (right instanceof PsiMethodReferenceType && left instanceof PsiClassType) return true; + if (right instanceof PsiMethodReferenceType) { + final PsiMethodReferenceExpression methodReferenceExpression = ((PsiMethodReferenceType)right).getExpression(); + if (left instanceof PsiClassType) { + return LambdaUtil.isAcceptable(methodReferenceExpression, (PsiClassType)left); + } + } if (right instanceof PsiLambdaExpressionType) { final PsiLambdaExpression rLambdaExpression = ((PsiLambdaExpressionType)right).getExpression(); if (left instanceof PsiClassType) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiMethodReferenceExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiMethodReferenceExpressionImpl.java index 0458abf01ab5..d718e6c3c1c8 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiMethodReferenceExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiMethodReferenceExpressionImpl.java @@ -18,10 +18,18 @@ package com.intellij.psi.impl.source.tree.java; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; +import com.intellij.psi.impl.PsiManagerEx; +import com.intellij.psi.impl.source.resolve.ResolveCache; import com.intellij.psi.impl.source.tree.JavaElementType; +import com.intellij.psi.infos.CandidateInfo; import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.util.MethodSignature; import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase implements PsiMethodReferenceExpression { private static Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiMethodReferenceExpressionImpl"); @@ -36,6 +44,12 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase return qualifier instanceof PsiTypeElement ? (PsiTypeElement)qualifier : null; } + @Nullable + @Override + public PsiType getFunctionalInterfaceType() { + return LambdaUtil.getFunctionalInterfaceType(this, true); + } + @Override public PsiExpression getQualifierExpression() { final PsiElement qualifier = getQualifier(); @@ -61,8 +75,17 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase @NotNull @Override public JavaResolveResult[] multiResolve(final boolean incompleteCode) { - // todo[r.sh]: implement - return JavaResolveResult.EMPTY_ARRAY; + final PsiManagerEx manager = getManager(); + if (manager == null) { + LOG.error("getManager() == null!"); + return JavaResolveResult.EMPTY_ARRAY; + } + if (!isValid()) { + LOG.error("invalid!"); + return JavaResolveResult.EMPTY_ARRAY; + } + ResolveResult[] results = ResolveCache.getInstance(getProject()).resolveWithCaching(this, new MethodReferenceResolver(), true, incompleteCode); + return results.length == 0 ? JavaResolveResult.EMPTY_ARRAY : (JavaResolveResult[])results; } @Override @@ -74,9 +97,15 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase @Override public TextRange getRangeInElement() { final PsiElement element = getReferenceNameElement(); - if (element != null) return new TextRange(element.getStartOffsetInParent(), element.getTextLength()); + if (element != null) { + final int offsetInParent = element.getStartOffsetInParent(); + return new TextRange(offsetInParent, offsetInParent + element.getTextLength()); + } final PsiElement colons = findPsiChildByType(JavaTokenType.DOUBLE_COLON); - if (colons != null) return new TextRange(colons.getStartOffsetInParent(), colons.getTextLength()); + if (colons != null) { + final int offsetInParent = colons.getStartOffsetInParent(); + return new TextRange(offsetInParent, offsetInParent + colons.getTextLength()); + } LOG.error(getText()); return null; } @@ -117,4 +146,48 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase public String toString() { return "PsiMethodReferenceExpression:" + getText(); } + + private class MethodReferenceResolver implements ResolveCache.PolyVariantResolver { + @NotNull + @Override + public ResolveResult[] resolve(@NotNull PsiJavaReference reference, boolean incompleteCode) { + PsiClass containingClass = null; + final PsiExpression expression = getQualifierExpression(); + if (expression != null) { + containingClass = PsiUtil.resolveClassInType(expression.getType()); + if (containingClass == null && expression instanceof PsiReferenceExpression) { + final PsiElement resolve = ((PsiReferenceExpression)expression).resolve(); + if (resolve instanceof PsiClass) { + containingClass = (PsiClass)resolve; + } + } + } + else { + final PsiTypeElement typeElement = getQualifierType(); + if (typeElement != null) { + containingClass = PsiUtil.resolveClassInType(typeElement.getType()); + } + } + + if (containingClass != null) { + final PsiElement element = getReferenceNameElement(); + if (element instanceof PsiIdentifier) { + final PsiType functionalInterfaceType = getFunctionalInterfaceType(); + final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); + if (interfaceMethod == null) return JavaResolveResult.EMPTY_ARRAY; + final MethodSignature interfaceMethodSignature = interfaceMethod.getSignature(resolveResult.getSubstitutor()); + final PsiMethod[] psiMethods = containingClass.findMethodsByName(element.getText(), false); + List result = new ArrayList(); + for (PsiMethod method : psiMethods) { + if (LambdaUtil.areAcceptable(interfaceMethodSignature, method.getSignature(PsiSubstitutor.EMPTY))) { + result.add(new CandidateInfo(method, PsiSubstitutor.EMPTY)); + } + } + return result.toArray(new JavaResolveResult[result.size()]); + } + } + return JavaResolveResult.EMPTY_ARRAY; + } + } } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiReferenceExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiReferenceExpressionImpl.java index 77bf60684b11..d2ae28cbcc32 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiReferenceExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiReferenceExpressionImpl.java @@ -262,6 +262,9 @@ public class PsiReferenceExpressionImpl extends PsiReferenceExpressionBase imple if (parentType == JavaElementType.METHOD_CALL_EXPRESSION) { return resolveToMethod(); } + if (parentType == JavaElementType.METHOD_REF_EXPRESSION) { + return resolve(JavaElementType.REFERENCE_EXPRESSION); + } return resolveToVariable(); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/methodRef/Assignability.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/methodRef/Assignability.java new file mode 100644 index 000000000000..5a0884b17b87 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/methodRef/Assignability.java @@ -0,0 +1,35 @@ +class Test { + { + Runnable b = Test :: length; + Comparable c = Test :: length; + Comparable c1 = Test :: length; + } + + public static Integer length(String s) { + return s.length(); + } + + interface Bar { + Integer _(String s); + } +} + +class Test1 { + { + Runnable b = Test1 :: length; + Comparable c = Test1 :: length; + Comparable c1 = Test1 :: length; + } + + public static Integer length(String s) { + return s.length(); + } + + public static Integer length(Integer s) { + return s; + } + + interface Bar { + Integer _(String s); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createMethodFromUsage/afterMethodReferenceArgument.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createMethodFromUsage/afterMethodReferenceArgument.java index c5257c0cf338..27508a806385 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createMethodFromUsage/afterMethodReferenceArgument.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createMethodFromUsage/afterMethodReferenceArgument.java @@ -4,7 +4,7 @@ class A { f(A::foo); } - private void f(Object foo) { + private void f(Object p0) { //To change body of created methods use File | Settings | File Templates. } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/MethodRefHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/MethodRefHighlightingTest.java index 3a56ff1bd700..b476759fda22 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/MethodRefHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/MethodRefHighlightingTest.java @@ -25,6 +25,10 @@ public class MethodRefHighlightingTest extends LightDaemonAnalyzerTestCase { doTest(); } + public void testAssignability() throws Exception { + doTest(); + } + private void doTest() throws Exception { doTest(BASE_PATH + "/" + getTestName(false) + ".java", false, false); }