new overload resolution: integrate isPotentiallyCompatible in isApplicable checks

This commit is contained in:
Anna Kozlova
2014-11-26 17:35:45 +01:00
parent 68cc2ec0a2
commit 145f701bc1
16 changed files with 140 additions and 139 deletions

View File

@@ -18,6 +18,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.openapi.util.registry.Registry;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.*;
@@ -493,6 +494,31 @@ public class LambdaUtil {
return expression;
}
// http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1
// A lambda expression or a method reference expression is potentially compatible with a type variable
// if the type variable is a type parameter of the candidate method.
public static boolean isPotentiallyCompatibleWithTypeParameter(PsiFunctionalExpression expression,
PsiExpressionList argsList,
PsiMethod method) {
if (!Registry.is("JDK8042508.bug.fixed", false)) {
final PsiCallExpression callExpression = PsiTreeUtil.getParentOfType(argsList, PsiCallExpression.class);
if (callExpression == null || callExpression.getTypeArguments().length > 0) {
return false;
}
}
final int lambdaIdx = getLambdaIdx(argsList, expression);
if (lambdaIdx >= 0) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
final PsiParameter lambdaParameter = parameters[Math.min(lambdaIdx, parameters.length - 1)];
final PsiClass paramClass = PsiUtil.resolveClassInType(lambdaParameter.getType());
if (paramClass instanceof PsiTypeParameter && ((PsiTypeParameter)paramClass).getOwner() == method) {
return true;
}
}
return false;
}
public static class TypeParamsChecker extends PsiTypeVisitor<Boolean> {
private PsiMethod myMethod;
private final PsiClass myClass;

View File

@@ -46,6 +46,4 @@ public interface PsiLambdaExpression extends PsiFunctionalExpression {
* @return true when lambda declares parameter types explicitly
*/
boolean hasFormalParameterTypes();
boolean isAcceptable(PsiType leftType, boolean checkReturnType);
}

View File

@@ -731,7 +731,7 @@ public class TypeConversionUtil {
final PsiType lType = lLambdaExpression.getFunctionalInterfaceType();
return Comparing.equal(rType, lType);
}
return !(left instanceof PsiArrayType) && rLambdaExpression.isAcceptable(left, false);
return !(left instanceof PsiArrayType) && rLambdaExpression.isAcceptable(left);
}
if (left instanceof PsiIntersectionType) {

View File

@@ -18,7 +18,6 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.icons.AllIcons;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
@@ -34,6 +33,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.HashMap;
import java.util.Map;
public class PsiLambdaExpressionImpl extends ExpressionPsiElement implements PsiLambdaExpression {
@@ -168,35 +169,35 @@ public class PsiLambdaExpressionImpl extends ExpressionPsiElement implements Psi
}
@Override
public boolean isAcceptable(PsiType left) {
return isAcceptable(left, false);
}
@Override
public boolean isAcceptable(PsiType leftType, boolean checkReturnType) {
public boolean isAcceptable(PsiType leftType) {
if (leftType instanceof PsiIntersectionType) {
for (PsiType conjunctType : ((PsiIntersectionType)leftType).getConjuncts()) {
if (isAcceptable(conjunctType, checkReturnType)) return true;
if (isAcceptable(conjunctType)) return true;
}
return false;
}
final PsiElement argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
if (!hasFormalParameterTypes()) {
return true;
}
final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argsList);
if (candidateProperties != null && !InferenceSession.isPertinentToApplicability(this, candidateProperties.getMethod())) {
return true;
}
}
final PsiExpressionList argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
leftType = FunctionalInterfaceParameterizationUtil.getGroundTargetType(leftType, this);
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(leftType);
final PsiClass psiClass = resolveResult.getElement();
if (psiClass instanceof PsiAnonymousClass) {
return isAcceptable(((PsiAnonymousClass)psiClass).getBaseClassType(), checkReturnType);
return isAcceptable(((PsiAnonymousClass)psiClass).getBaseClassType());
}
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argsList);
if (candidateProperties != null) {
final PsiMethod method = candidateProperties.getMethod();
if (!InferenceSession.isPertinentToApplicability(this, method) && hasFormalParameterTypes()) {
return true;
}
if (LambdaUtil.isPotentiallyCompatibleWithTypeParameter(this, argsList, method)) {
return true;
}
}
}
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
@@ -206,56 +207,77 @@ public class PsiLambdaExpressionImpl extends ExpressionPsiElement implements Psi
final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, resolveResult);
assert leftType != null;
final PsiParameter[] lambdaParameters = getParameterList().getParameters();
final PsiType[] parameterTypes = interfaceMethod.getSignature(substitutor).getParameterTypes();
if (lambdaParameters.length != parameterTypes.length) return false;
if (!isPotentiallyCompatible(leftType)) {
return false;
}
for (int lambdaParamIdx = 0, length = lambdaParameters.length; lambdaParamIdx < length; lambdaParamIdx++) {
PsiParameter parameter = lambdaParameters[lambdaParamIdx];
final PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
final PsiType lambdaFormalType = toArray(typeElement.getType());
final PsiType methodParameterType = toArray(parameterTypes[lambdaParamIdx]);
if (!lambdaFormalType.equals(methodParameterType)) {
return false;
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList) && !hasFormalParameterTypes()) {
return true;
}
if (hasFormalParameterTypes()) {
final PsiParameter[] lambdaParameters = getParameterList().getParameters();
final PsiType[] parameterTypes = interfaceMethod.getSignature(substitutor).getParameterTypes();
for (int lambdaParamIdx = 0, length = lambdaParameters.length; lambdaParamIdx < length; lambdaParamIdx++) {
PsiParameter parameter = lambdaParameters[lambdaParamIdx];
final PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
final PsiType lambdaFormalType = toArray(typeElement.getType());
final PsiType methodParameterType = toArray(parameterTypes[lambdaParamIdx]);
if (!lambdaFormalType.equals(methodParameterType)) {
return false;
}
}
}
}
//A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:
// The arity of the target type's function type is the same as the arity of the lambda expression.
// If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).
// If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).
PsiType methodReturnType = interfaceMethod.getReturnType();
if (checkReturnType) {
final String uniqueVarName = JavaCodeStyleManager.getInstance(getProject()).suggestUniqueVariableName("l", this, true);
final String canonicalText = toArray(leftType).getCanonicalText();
final PsiStatement assignmentFromText = JavaPsiFacade.getElementFactory(getProject())
.createStatementFromText(canonicalText + " " + uniqueVarName + " = " + getText(), this);
final PsiLocalVariable localVariable = (PsiLocalVariable)((PsiDeclarationStatement)assignmentFromText).getDeclaredElements()[0];
if (methodReturnType != null) {
return LambdaHighlightingUtil.checkReturnTypeCompatible((PsiLambdaExpression)localVariable.getInitializer(),
substitutor.substitute(methodReturnType)) == null;
if (methodReturnType != null) {
Map<PsiElement, PsiType> map = LambdaUtil.ourFunctionTypes.get();
if (map == null) {
map = new HashMap<PsiElement, PsiType>();
LambdaUtil.ourFunctionTypes.set(map);
}
} else {
final PsiElement body = getBody();
if (methodReturnType == PsiType.VOID) {
if (body instanceof PsiCodeBlock) {
return isVoidCompatible();
} else {
return LambdaUtil.isExpressionStatementExpression(body);
try {
if (map.put(this, leftType) != null) {
return false;
}
} else {
if (body instanceof PsiCodeBlock) {
return isValueCompatible();
}
return body instanceof PsiExpression;
return LambdaHighlightingUtil.checkReturnTypeCompatible(this, substitutor.substitute(methodReturnType)) == null;
}
finally {
map.remove(this);
}
}
return true;
}
//A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:
// The arity of the target type's function type is the same as the arity of the lambda expression.
// If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).
// If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).
private boolean isPotentiallyCompatible(PsiType left) {
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(left);
if (interfaceMethod == null) return false;
if (getParameterList().getParametersCount() != interfaceMethod.getParameterList().getParametersCount()) {
return false;
}
final PsiType methodReturnType = interfaceMethod.getReturnType();
final PsiElement body = getBody();
if (methodReturnType == PsiType.VOID) {
if (body instanceof PsiCodeBlock) {
return isVoidCompatible();
} else {
return LambdaUtil.isExpressionStatementExpression(body);
}
}
else {
return body instanceof PsiCodeBlock && isValueCompatible() || body instanceof PsiExpression;
}
}
private static PsiType toArray(PsiType paramType) {
if (paramType instanceof PsiEllipsisType) {
return ((PsiEllipsisType)paramType).toArrayType();

View File

@@ -20,6 +20,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
@@ -373,12 +374,19 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase
return false;
}
final PsiElement argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
final PsiExpressionList argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
final boolean isExact = isExact();
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList) && isExact) {
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argsList);
if (candidateProperties != null && !InferenceSession.isPertinentToApplicability(this, candidateProperties.getMethod())) {
return true;
if (candidateProperties != null) {
final PsiMethod method = candidateProperties.getMethod();
if (isExact && !InferenceSession.isPertinentToApplicability(this, method)) {
return true;
}
if (LambdaUtil.isPotentiallyCompatibleWithTypeParameter(this, argsList, method)) {
return true;
}
}
}

View File

@@ -33,6 +33,7 @@ import com.intellij.util.containers.HashSet;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TIntArrayList;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -91,9 +92,6 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
// then noone can be more specific
if (!atLeastOneMatch) return null;
checkLambdaApplicable(conflicts, myLanguageLevel);
if (conflicts.size() == 1) return conflicts.get(0);
checkSpecifics(conflicts, applicabilityLevel, myLanguageLevel);
if (conflicts.size() == 1) return conflicts.get(0);
@@ -108,63 +106,6 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
return null;
}
private void checkLambdaApplicable(@NotNull List<CandidateInfo> conflicts, @NotNull LanguageLevel languageLevel) {
if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) return;
for (int i = 0; i < getActualParametersLength(); i++) {
PsiExpression expression;
if (myArgumentsList instanceof PsiExpressionList) {
expression = ((PsiExpressionList)myArgumentsList).getExpressions()[i];
}
else {
final PsiType argType = getActualParameterTypes()[i];
expression = argType instanceof PsiLambdaExpressionType ? ((PsiLambdaExpressionType)argType).getExpression() : null;
}
final PsiLambdaExpression lambdaExpression = findNestedLambdaExpression(expression);
if (lambdaExpression != null) {
checkLambdaApplicable(conflicts, i, lambdaExpression);
}
}
}
private static PsiLambdaExpression findNestedLambdaExpression(PsiExpression expression) {
if (expression instanceof PsiLambdaExpression) {
return (PsiLambdaExpression)expression;
}
else if (expression instanceof PsiParenthesizedExpression) {
return findNestedLambdaExpression(((PsiParenthesizedExpression)expression).getExpression());
}
else if (expression instanceof PsiConditionalExpression) {
PsiLambdaExpression lambdaExpression = findNestedLambdaExpression(((PsiConditionalExpression)expression).getThenExpression());
if (lambdaExpression != null) {
return lambdaExpression;
}
return findNestedLambdaExpression(((PsiConditionalExpression)expression).getElseExpression());
}
return null;
}
private static void checkLambdaApplicable(@NotNull List<CandidateInfo> conflicts, int i, @NotNull PsiLambdaExpression lambdaExpression) {
for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext(); ) {
ProgressManager.checkCanceled();
final CandidateInfo conflict = iterator.next();
final PsiMethod method = (PsiMethod)conflict.getElement();
final PsiParameter[] methodParameters = method.getParameterList().getParameters();
if (methodParameters.length == 0) continue;
final PsiParameter param = i < methodParameters.length ? methodParameters[i] : methodParameters[methodParameters.length - 1];
final PsiType paramType = param.getType();
// http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1
// A lambda expression or a method reference expression is potentially compatible with a type variable if the type variable is a type parameter of the candidate method.
final PsiClass paramClass = PsiUtil.resolveClassInType(paramType);
if (paramClass instanceof PsiTypeParameter && ((PsiTypeParameter)paramClass).getOwner() == method) continue;
if (!lambdaExpression.isAcceptable(((MethodCandidateInfo)conflict).getSubstitutor(false).substitute(paramType),
InferenceSession.isPertinentToApplicability(lambdaExpression, method))) {
iterator.remove();
}
}
}
public void checkSpecifics(@NotNull List<CandidateInfo> conflicts,
@MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel,
@NotNull LanguageLevel languageLevel) {

View File

@@ -20,7 +20,7 @@ public class Test<A, B extends Number> {
public static void main(String[] args) {
Inner<Number, Double> inn = new Inner<>();
inn.m8<error descr="Ambiguous method call: both 'Inner.m8(IO<? extends Number>)' and 'Inner.m8(IN<? extends Double>)' match">(p -> 1.0)</error>;
new Test<Number, Integer>().foo<error descr="Ambiguous method call: both 'Test.foo(IO<? extends Number>)' and 'Test.foo(IN<? extends Integer>)' match">(p -> 1.0)</error>;
new Test<Number, Integer>().foo<error descr="Cannot resolve method 'foo(<lambda expression>)'">(p -> 1.0)</error>;
}
}

View File

@@ -1,7 +1,7 @@
class Demo {
public void f1() {
f2(2, <error descr="Target type of a lambda conversion must be an interface">input -> input</error>);
f2<error descr="Cannot resolve method 'f2(int, <lambda expression>)'">(2, input -> input)</error>;
}
public void f2() {

View File

@@ -25,7 +25,7 @@ class ReturnTypeIncompatibility {
}
public static void main(String[] args) {
call<error descr="Ambiguous method call: both 'ReturnTypeIncompatibility.call(I1<Integer>)' and 'ReturnTypeIncompatibility.call(I2<P>)' match">(i-> {return i;})</error>;
call<error descr="Cannot resolve method 'call(<lambda expression>)'">(i-> {return i;})</error>;
}
}
@@ -57,7 +57,7 @@ class ReturnTypeCompatibility {
}
public static void main(String[] args) {
call<error descr="Ambiguous method call: both 'ReturnTypeCompatibility.call(I1<Number>)' and 'ReturnTypeCompatibility.call(I2<String>)' match">(i-> {return i;})</error>;
call<error descr="Cannot resolve method 'call(<lambda expression>)'">(i-> {return i;})</error>;
}
}

View File

@@ -21,9 +21,9 @@ class Test {
}
void foo(Foo<String> as, final Foo<Character> ac) {
boolean b1 = as.forAll(s -> ac.forAll<error descr="Ambiguous method call: both 'Foo.forAll(I<Character, Boolean>)' and 'Foo.forAll(II<Character, String>)' match">(c -> false)</error>);
String s1 = as.forAll(s -> ac.forAll<error descr="Ambiguous method call: both 'Foo.forAll(I<Character, Boolean>)' and 'Foo.forAll(II<Character, String>)' match">(c -> "")</error>);
boolean b2 = as.forAll(s -> ac.forAll<error descr="Ambiguous method call: both 'Foo.forAll(I<Character, Boolean>)' and 'Foo.forAll(II<Character, String>)' match">(c -> "")</error>);
boolean b1 = as.forAll(s -> ac.forAll<error descr="Cannot resolve method 'forAll(<lambda expression>)'">(c -> false)</error>);
String s1 = as.forAll(s -> ac.forAll<error descr="Cannot resolve method 'forAll(<lambda expression>)'">(c -> "")</error>);
boolean b2 = as.forAll(s -> ac.forAll<error descr="Cannot resolve method 'forAll(<lambda expression>)'">(c -> "")</error>);
String s2 = as.forAll2(s -> ac.forAll2(<error descr="Incompatible return type boolean in lambda expression">c -> false</error>));
boolean b3 = as.forAll((I<String, Boolean>)s -> ac.forAll((I<Character, Boolean>)<error descr="Incompatible return type String in lambda expression">c -> ""</error>));
String s3 = as.forAll((II<String, String>)s -> ac.forAll((II<Character, String>)<error descr="Incompatible return type boolean in lambda expression">c -> false</error>));

View File

@@ -14,7 +14,7 @@ class Test {
}
void fooBar(IntStream1 instr){
Supplier<Stream<Integer>> si = () -> instr.map ((i) -> (( <error descr="Operator '%' cannot be applied to '<lambda parameter>', 'int'">i % 2</error>) == 0) ? i : <error descr="Incompatible types. Found: '<lambda parameter>', required: '<lambda parameter>'">-i</error>).boxed();
Supplier<Stream<Integer>> si = () -> instr.map ((i) -> (( <error descr="Operator '%' cannot be applied to '<lambda parameter>', 'int'">i % 2</error>) == 0) ? i : -i).boxed();
System.out.println(si);
Supplier<Stream<Integer>> si1 = () -> instr.map <error descr="Ambiguous method call: both 'IntStream1.map(IntFunction<Integer>)' and 'IntStream1.map(IntUnaryOperator)' match">(null)</error>.boxed();
System.out.println(si1);

View File

@@ -12,6 +12,6 @@ abstract class PertinentToApplicabilityOfExplicitlyTypedLambdaTest {
abstract void foo(B b);
{
foo<error descr="Ambiguous method call: both 'PertinentToApplicabilityOfExplicitlyTypedLambdaTest.foo(A)' and 'PertinentToApplicabilityOfExplicitlyTypedLambdaTest.foo(B)' match">(x -> y -> 42)</error>;
foo<error descr="Cannot resolve method 'foo(<lambda expression>)'">(x -> y -> 42)</error>;
}
}

View File

@@ -14,10 +14,10 @@ abstract class Test {
foo(x -> {
return x += 1;
});
foo(x -> <error descr="Incompatible types. Found: 'int', required: '<lambda parameter>'">x += 1</error>);
foo<error descr="Ambiguous method call: both 'Test.foo(A)' and 'Test.foo(B)' match">(x -> x += 1)</error>;
foo(x -> 1);
foo(x -> <error descr="Operator '!' cannot be applied to 'int'">!x</error>);
foo(x -> <error descr="Operator '++' cannot be applied to '<lambda parameter>'">++x</error>);
foo<error descr="Ambiguous method call: both 'Test.foo(A)' and 'Test.foo(B)' match">(x -> ++x)</error>;
foo(x -> o instanceof String ? 1 : 0);
}
}

View File

@@ -26,9 +26,9 @@ class Foo {
System.out.println(s);
});
<error descr="Cannot resolve method 'foo(<lambda expression>)'">foo</error>((String p, String k) -> {
foo<error descr="Cannot resolve method 'foo(<lambda expression>)'">((String p, String k) -> {
System.out.println(p);
});
})</error>;
}
}
@@ -62,7 +62,7 @@ class WithTypeParams {
System.out.println(p);
});
<error descr="Cannot resolve method 'foo(<lambda expression>)'">foo</error>((int k) -> {System.out.println(k);});
foo<error descr="Cannot resolve method 'foo(<lambda expression>)'">((int k) -> {System.out.println(k);})</error>;
}
}
}

View File

@@ -19,6 +19,7 @@ import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.testFramework.IdeaTestUtil;
import org.jetbrains.annotations.NonNls;
@@ -100,7 +101,9 @@ public class MostSpecificResolutionTest extends LightDaemonAnalyzerTestCase {
}
public void testJDK8042508() throws Exception {
doTest(false);
if (Registry.is("JDK8042508.bug.fixed", false)) {
doTest(false);
}
}
public void testIDEA125855() throws Exception {

View File

@@ -495,4 +495,7 @@ editor.xcode.like.scrollbar=false
editor.xcode.like.scrollbar.description=Enables auto-hideable Xcode-like editor stripes
editor.config.stop.at.project.root=true
editor.config.stop.at.project.root.description=Stops searching for .editorconfig at project root (requires project reopening)
editor.config.stop.at.project.root.description=Stops searching for .editorconfig at project root (requires project reopening)
JDK8042508.bug.fixed=false
JDK8042508.bug.fixed.description=Disable check for type variable until javac bug is fixed