more specific: lambda extended initial fix (IDEA-113357)

This commit is contained in:
Anna Kozlova
2013-09-09 21:40:00 +04:00
parent 0195034704
commit 4cae15713b
6 changed files with 198 additions and 142 deletions

View File

@@ -23,6 +23,7 @@ import com.intellij.openapi.util.Pair;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.infos.ClassCandidateInfo;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.NonNls;
@@ -648,144 +649,6 @@ public class LambdaUtil {
return result;
}
public static void checkMoreSpecificReturnType(List<CandidateInfo> conflicts, PsiType[] actualParameterTypes) {
final CandidateInfo[] newConflictsArray = conflicts.toArray(new CandidateInfo[conflicts.size()]);
for (int i = 1; i < newConflictsArray.length; i++) {
final CandidateInfo method = newConflictsArray[i];
for (int j = 0; j < i; j++) {
final CandidateInfo conflict = newConflictsArray[j];
assert conflict != method;
int moreSpecific = 0;
final PsiMethod methodElement = (PsiMethod)method.getElement();
final PsiMethod conflictElement = (PsiMethod)conflict.getElement();
if (methodElement.isVarArgs() == conflictElement.isVarArgs()) {
for (int functionalInterfaceIdx = 0; functionalInterfaceIdx < actualParameterTypes.length; functionalInterfaceIdx++) {
final PsiType interfaceReturnType = getReturnType(functionalInterfaceIdx, method);
final PsiType interfaceReturnType1 = getReturnType(functionalInterfaceIdx, conflict);
if (actualParameterTypes[functionalInterfaceIdx] instanceof PsiLambdaExpressionType || actualParameterTypes[functionalInterfaceIdx] instanceof PsiMethodReferenceType) {
if (interfaceReturnType != null && interfaceReturnType1 != null && !Comparing.equal(interfaceReturnType, interfaceReturnType1)) {
int moreSpecific1 = isMoreSpecific(interfaceReturnType, interfaceReturnType1, actualParameterTypes[functionalInterfaceIdx]);
if (moreSpecific < 0 && moreSpecific1 > 0 || moreSpecific > 0 && moreSpecific1 < 0) {
moreSpecific = 0;
break;
}
moreSpecific = moreSpecific1;
}
} else if (interfaceReturnType != null && interfaceReturnType1 != null) {
moreSpecific = 0;
break;
}
}
if (moreSpecific > 0 && conflictElement.getParameterList().getParametersCount() <= actualParameterTypes.length) {
conflicts.remove(method);
break;
}
else if (moreSpecific < 0 && methodElement.getParameterList().getParametersCount() <= actualParameterTypes.length) {
conflicts.remove(conflict);
}
}
}
}
}
enum TypeKind {
PRIMITIVE, REFERENCE, NONE_DETERMINED
}
private static int isMoreSpecific(PsiType returnType, PsiType returnType1, PsiType lambdaType) {
TypeKind typeKind = TypeKind.PRIMITIVE;
if (lambdaType instanceof PsiLambdaExpressionType) {
typeKind = areLambdaReturnExpressionsPrimitive((PsiLambdaExpressionType)lambdaType);
} else if (lambdaType instanceof PsiMethodReferenceType) {
final PsiElement referencedElement = ((PsiMethodReferenceType)lambdaType).getExpression().resolve();
if (referencedElement instanceof PsiMethod && !(((PsiMethod)referencedElement).getReturnType() instanceof PsiPrimitiveType)) {
typeKind = TypeKind.REFERENCE;
}
}
if (typeKind != TypeKind.NONE_DETERMINED) {
if (returnType instanceof PsiPrimitiveType) {
final int moreSpecific = typeKind == TypeKind.PRIMITIVE ? 1 : -1;
if (!(returnType1 instanceof PsiPrimitiveType)) {
return -moreSpecific;
} else {
return TypeConversionUtil.isAssignable(returnType, returnType1) ? moreSpecific : -moreSpecific;
}
}
if (returnType1 instanceof PsiPrimitiveType) {
return typeKind == TypeKind.PRIMITIVE ? 1 : -1;
}
}
final PsiClassType.ClassResolveResult r = PsiUtil.resolveGenericsClassInType(GenericsUtil.eliminateWildcards(returnType, false));
final PsiClass rClass = r.getElement();
final PsiClassType.ClassResolveResult r1 = PsiUtil.resolveGenericsClassInType(GenericsUtil.eliminateWildcards(returnType1, false));
final PsiClass rClass1 = r1.getElement();
if (rClass != null && rClass1 != null) {
if (rClass == rClass1) {
int moreSpecific = 0;
for (PsiTypeParameter parameter : rClass.getTypeParameters()) {
final PsiType t = r.getSubstitutor().substituteWithBoundsPromotion(parameter);
final PsiType t1 = r1.getSubstitutor().substituteWithBoundsPromotion(parameter);
if (t == null || t1 == null) continue;
if (t1.isAssignableFrom(t)) {
if (moreSpecific == 1) {
return 0;
}
moreSpecific = -1;
}
else if (t.isAssignableFrom(t1)) {
if (moreSpecific == -1) {
return 0;
}
moreSpecific = 1;
}
else {
return 0;
}
}
return moreSpecific;
}
else if (rClass1.isInheritor(rClass, true)) {
return 1;
}
else if (rClass.isInheritor(rClass1, true)) {
return -1;
}
}
return 0;
}
private static TypeKind areLambdaReturnExpressionsPrimitive(PsiLambdaExpressionType lambdaType) {
final List<PsiExpression> returnExpressions = getReturnExpressions(lambdaType.getExpression());
TypeKind typeKind = TypeKind.NONE_DETERMINED;
for (PsiExpression expression : returnExpressions) {
final PsiType returnExprType = expression.getType();
if (returnExprType instanceof PsiPrimitiveType) {
if (typeKind == TypeKind.REFERENCE) {
typeKind = TypeKind.NONE_DETERMINED;
break;
}
typeKind = TypeKind.PRIMITIVE;
} else {
if (typeKind == TypeKind.PRIMITIVE) {
typeKind = TypeKind.NONE_DETERMINED;
break;
}
typeKind = TypeKind.REFERENCE;
}
}
return typeKind;
}
@Nullable
private static PsiType getReturnType(int functionalTypeIdx, CandidateInfo method) {
final PsiParameter[] methodParameters = ((PsiMethod)method.getElement()).getParameterList().getParameters();
if (methodParameters.length == 0) return null;
final PsiParameter param = functionalTypeIdx < methodParameters.length ? methodParameters[functionalTypeIdx] : methodParameters[methodParameters.length - 1];
final PsiType functionalInterfaceType = method.getSubstitutor().substitute(param.getType());
return getFunctionalInterfaceReturnType(functionalInterfaceType);
}
@Nullable
public static String checkFunctionalInterface(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel languageLevel) {
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && Comparing.strEqual(annotation.getQualifiedName(), JAVA_LANG_FUNCTIONAL_INTERFACE)) {

View File

@@ -33,6 +33,7 @@ import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import java.util.List;
@@ -140,7 +141,7 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
}
}
}
LambdaUtil.checkMoreSpecificReturnType(conflicts, myActualParameterTypes);
checkMoreSpecificReturnType(conflicts, myActualParameterTypes, languageLevel);
}
public void checkSpecifics(@NotNull List<CandidateInfo> conflicts,
@@ -647,4 +648,150 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
}
}
}
enum TypeKind {
PRIMITIVE, REFERENCE, NONE_DETERMINED
}
public void checkMoreSpecificReturnType(List<CandidateInfo> conflicts, PsiType[] actualParameterTypes, LanguageLevel languageLevel) {
final CandidateInfo[] newConflictsArray = conflicts.toArray(new CandidateInfo[conflicts.size()]);
next:
for (int i = 1; i < newConflictsArray.length; i++) {
final CandidateInfo method = newConflictsArray[i];
for (int j = 0; j < i; j++) {
final CandidateInfo conflict = newConflictsArray[j];
assert conflict != method;
switch (isMoreSpecific(method, conflict, actualParameterTypes, languageLevel)) {
case FIRST:
conflicts.remove(conflict);
break;
case SECOND:
conflicts.remove(method);
continue next;
default:
break;
}
}
}
}
private static Specifics isMoreSpecific(CandidateInfo method,
CandidateInfo conflict,
PsiType[] actualParameterTypes,
LanguageLevel languageLevel) {
Specifics moreSpecific = Specifics.NEITHER;
final PsiMethod methodElement = (PsiMethod)method.getElement();
final PsiMethod conflictElement = (PsiMethod)conflict.getElement();
if (methodElement != null &&
conflictElement != null &&
methodElement.isVarArgs() == conflictElement.isVarArgs() &&
methodElement.getParameterList().getParametersCount() <= actualParameterTypes.length &&
conflictElement.getParameterList().getParametersCount() <= actualParameterTypes.length) {
for (int functionalInterfaceIdx = 0; functionalInterfaceIdx < actualParameterTypes.length; functionalInterfaceIdx++) {
final PsiType interfaceReturnType = getReturnType(functionalInterfaceIdx, method);
final PsiType interfaceReturnType1 = getReturnType(functionalInterfaceIdx, conflict);
if (actualParameterTypes[functionalInterfaceIdx] instanceof PsiLambdaExpressionType || actualParameterTypes[functionalInterfaceIdx] instanceof PsiMethodReferenceType) {
if (interfaceReturnType != null && interfaceReturnType1 != null && !Comparing.equal(interfaceReturnType, interfaceReturnType1)) {
final TypeKind typeKind = getKind(actualParameterTypes[functionalInterfaceIdx]);
Specifics moreSpecific1 = Specifics.NEITHER;
if (typeKind != TypeKind.NONE_DETERMINED) {
final boolean isPrimitive = typeKind == TypeKind.PRIMITIVE;
if (interfaceReturnType instanceof PsiPrimitiveType) {
if (interfaceReturnType1 instanceof PsiPrimitiveType &&
TypeConversionUtil.isAssignable(interfaceReturnType, interfaceReturnType1)) {
moreSpecific1 = isPrimitive ? Specifics.SECOND : Specifics.FIRST;
} else {
moreSpecific1 = isPrimitive ? Specifics.FIRST : Specifics.SECOND;
}
} else if (interfaceReturnType1 instanceof PsiPrimitiveType) {
moreSpecific1 = isPrimitive ? Specifics.SECOND : Specifics.FIRST;
}
}
if (moreSpecific1 == Specifics.NEITHER && (interfaceReturnType != PsiType.VOID && interfaceReturnType1 != PsiType.VOID)) {
final PsiSubstitutor siteSubstitutor1 = ((MethodCandidateInfo)method).getSiteSubstitutor();
final PsiSubstitutor siteSubstitutor2 = ((MethodCandidateInfo)conflict).getSiteSubstitutor();
final PsiTypeParameter[] typeParameters1 = methodElement.getTypeParameters();
final PsiTypeParameter[] typeParameters2 = conflictElement.getTypeParameters();
final PsiType[] types1AtSite = {interfaceReturnType1};
final PsiType[] types2AtSite = {interfaceReturnType};
final PsiSubstitutor methodSubstitutor1 = calculateMethodSubstitutor(typeParameters1, methodElement, siteSubstitutor1, types2AtSite, types1AtSite, languageLevel);
final PsiSubstitutor methodSubstitutor2 = calculateMethodSubstitutor(typeParameters2, conflictElement, siteSubstitutor2, types1AtSite, types2AtSite,languageLevel);
final boolean applicable12 = TypeConversionUtil.isAssignable(interfaceReturnType1, methodSubstitutor1.substitute(interfaceReturnType));
final boolean applicable21 = TypeConversionUtil.isAssignable(interfaceReturnType, methodSubstitutor2.substitute(interfaceReturnType1));
if (applicable12 || applicable21) {
if (!applicable21) {
moreSpecific1 = Specifics.FIRST;
}
if (!applicable12) {
moreSpecific1 = Specifics.SECOND;
}
}
}
if (moreSpecific != Specifics.NEITHER && moreSpecific != moreSpecific1) {
return Specifics.NEITHER;
}
moreSpecific = moreSpecific1;
}
} else if (interfaceReturnType != null && interfaceReturnType1 != null) {
return Specifics.NEITHER;
}
}
}
return moreSpecific;
}
@Nullable
private static PsiType getReturnType(int functionalTypeIdx, CandidateInfo method) {
final PsiParameter[] methodParameters = ((PsiMethod)method.getElement()).getParameterList().getParameters();
if (methodParameters.length == 0) return null;
final PsiParameter param = functionalTypeIdx < methodParameters.length ? methodParameters[functionalTypeIdx] : methodParameters[methodParameters.length - 1];
final PsiType functionalInterfaceType = ((MethodCandidateInfo)method).getSiteSubstitutor().substitute(param.getType());
return LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
}
private static TypeKind getKind(PsiType lambdaType) {
TypeKind typeKind = TypeKind.PRIMITIVE;
if (lambdaType instanceof PsiLambdaExpressionType) {
typeKind = areLambdaReturnExpressionsPrimitive((PsiLambdaExpressionType)lambdaType);
} else if (lambdaType instanceof PsiMethodReferenceType) {
final PsiElement referencedElement = ((PsiMethodReferenceType)lambdaType).getExpression().resolve();
if (referencedElement instanceof PsiMethod && !(((PsiMethod)referencedElement).getReturnType() instanceof PsiPrimitiveType)) {
typeKind = TypeKind.REFERENCE;
}
}
return typeKind;
}
private static TypeKind areLambdaReturnExpressionsPrimitive(PsiLambdaExpressionType lambdaType) {
final List<PsiExpression> returnExpressions = LambdaUtil.getReturnExpressions(lambdaType.getExpression());
TypeKind typeKind = TypeKind.NONE_DETERMINED;
for (PsiExpression expression : returnExpressions) {
final PsiType returnExprType = expression.getType();
if (returnExprType instanceof PsiPrimitiveType) {
if (typeKind == TypeKind.REFERENCE) {
typeKind = TypeKind.NONE_DETERMINED;
break;
}
typeKind = TypeKind.PRIMITIVE;
} else {
if (typeKind == TypeKind.PRIMITIVE) {
typeKind = TypeKind.NONE_DETERMINED;
break;
}
typeKind = TypeKind.REFERENCE;
}
}
return typeKind;
}
}

View File

@@ -22,6 +22,6 @@ class AmbiguityRawGenerics {
<Z> void foo(I3<Z> s) { }
void bar() {
foo<error descr="Ambiguous method call: both 'AmbiguityRawGenerics.foo(I)' and 'AmbiguityRawGenerics.foo(I1)' match">(()-> { throw new RuntimeException(); })</error>;
foo<error descr="Ambiguous method call: both 'AmbiguityRawGenerics.foo(I)' and 'AmbiguityRawGenerics.foo(I3<Object>)' match">(()-> { throw new RuntimeException(); })</error>;
}
}

View File

@@ -0,0 +1,45 @@
class A {
private interface AsyncFunction<I, O> {
Promise<O> apply(I input);
}
private interface Function<I, O> {
O apply(I input);
}
private interface Promise<V> {
<T1> Promise<T1> then(Function<? super V, T1> function);
<T2> Promise<T2> then(AsyncFunction<? super V, T2> function);
}
private static Promise<Integer> calculateLength(String word) {
return null;
}
public static void main(Promise<String> helloWorld) {
helloWorld.then(A::calculateLength);
}
}
class AAmbiguous {
private interface AsyncFunction<I, O> {
O apply(I input);
}
private interface Function<I, O> {
O apply(I input);
}
private interface Promise<V> {
<T1> Promise<T1> then(Function<? super V, T1> function);
<T2> Promise<T2> then(AsyncFunction<? super V, T2> function);
}
private static Promise<Integer> calculateLength(String word) {
return null;
}
public static void main(Promise<String> helloWorld) {
helloWorld.then<error descr="Ambiguous method call: both 'Promise.then(Function<? super String,Promise<Integer>>)' and 'Promise.then(AsyncFunction<? super String,Promise<Integer>>)' match">(AAmbiguous::calculateLength)</error>;
}
}

View File

@@ -95,7 +95,7 @@ class MyTest2 {
System.out.println(i);
}
private static void m(I2 i) {
private static void <warning descr="Private method 'm(MyTest2.I2)' is never used">m</warning>(I2 i) {
System.out.println(i);
}
@@ -104,6 +104,6 @@ class MyTest2 {
}
public static void main(String[] args) {
m(Foo::new);
m<error descr="Ambiguous method call: both 'MyTest2.m(I2)' and 'MyTest2.m(I3)' match">(Foo::new)</error>;
}
}

View File

@@ -91,6 +91,7 @@ public class LambdaHighlightingTest extends LightDaemonAnalyzerTestCase {
public void testDiamondInference() { doTest();}
public void testFunctionalInterfaceCheck() { doTest();}
public void testUnderscores() { doTest(true);}
public void testReturnTypeAmbiguity() { doTest();}
private void doTest() {
doTest(false);