most specific check: ensure that functional types are specifically checked when method is not generics (IDEA-136287)

This commit is contained in:
Anna Kozlova
2015-02-11 19:05:41 +01:00
parent 502dd423d9
commit ed0ff7c07f
5 changed files with 114 additions and 82 deletions
@@ -480,31 +480,55 @@ public final class PsiUtil extends PsiUtilCore {
return getApplicabilityLevel(method, substitutorForMethod, args, languageLevel, true, true);
}
public interface ApplicabilityChecker {
ApplicabilityChecker ASSIGNABILITY_CHECKER = new ApplicabilityChecker() {
@Override
public boolean isApplicable(PsiType left, PsiType right, boolean allowUncheckedConversion, int argId) {
return TypeConversionUtil.isAssignable(left, right, allowUncheckedConversion);
}
};
boolean isApplicable(PsiType left, PsiType right, boolean allowUncheckedConversion, int argId);
}
@MethodCandidateInfo.ApplicabilityLevelConstant
public static int getApplicabilityLevel(@NotNull final PsiMethod method,
@NotNull final PsiSubstitutor substitutorForMethod,
@NotNull final PsiType[] args,
@NotNull final LanguageLevel languageLevel,
final boolean allowUncheckedConversion,
final boolean checkVarargs) {
return getApplicabilityLevel(method, substitutorForMethod, args, languageLevel,
allowUncheckedConversion, checkVarargs, ApplicabilityChecker.ASSIGNABILITY_CHECKER);
}
@MethodCandidateInfo.ApplicabilityLevelConstant
public static int getApplicabilityLevel(@NotNull final PsiMethod method,
@NotNull final PsiSubstitutor substitutorForMethod,
@NotNull final PsiType[] args,
@NotNull final LanguageLevel languageLevel,
final boolean allowUncheckedConversion,
final boolean checkVarargs) {
final boolean checkVarargs,
@NotNull final ApplicabilityChecker function) {
final PsiParameter[] parms = method.getParameterList().getParameters();
if (args.length < parms.length - 1) return ApplicabilityLevel.NOT_APPLICABLE;
final PsiClass containingClass = method.getContainingClass();
final boolean isRaw = containingClass != null && isRawSubstitutor(method, substitutorForMethod) && isRawSubstitutor(containingClass, substitutorForMethod);
if (!areFirstArgumentsApplicable(args, parms, languageLevel, substitutorForMethod, isRaw)) return ApplicabilityLevel.NOT_APPLICABLE;
if (!areFirstArgumentsApplicable(args, parms, languageLevel, substitutorForMethod, isRaw, allowUncheckedConversion, function)) return ApplicabilityLevel.NOT_APPLICABLE;
if (args.length == parms.length) {
if (parms.length == 0) return ApplicabilityLevel.FIXED_ARITY;
PsiType parmType = getParameterType(parms[parms.length - 1], languageLevel, substitutorForMethod);
PsiType argType = args[args.length - 1];
if (argType == null) return ApplicabilityLevel.NOT_APPLICABLE;
if (TypeConversionUtil.isAssignable(parmType, argType, allowUncheckedConversion)) return ApplicabilityLevel.FIXED_ARITY;
if (function.isApplicable(parmType, argType, allowUncheckedConversion, parms.length - 1)) return ApplicabilityLevel.FIXED_ARITY;
if (isRaw) {
final PsiType erasedParamType = TypeConversionUtil.erasure(parmType);
final PsiType erasedArgType = TypeConversionUtil.erasure(argType);
if (erasedArgType != null && erasedParamType != null &&
TypeConversionUtil.isAssignable(erasedParamType, erasedArgType)) {
function.isApplicable(erasedParamType, erasedArgType, allowUncheckedConversion, parms.length - 1)) {
return ApplicabilityLevel.FIXED_ARITY;
}
}
@@ -523,7 +547,7 @@ public final class PsiUtil extends PsiUtilCore {
}
for (int i = parms.length - 1; i < args.length; i++) {
PsiType argType = args[i];
if (argType == null || !TypeConversionUtil.isAssignable(lastParmType, argType)) {
if (argType == null || !function.isApplicable(lastParmType, argType, allowUncheckedConversion, i)) {
return ApplicabilityLevel.NOT_APPLICABLE;
}
}
@@ -536,7 +560,9 @@ public final class PsiUtil extends PsiUtilCore {
private static boolean areFirstArgumentsApplicable(@NotNull PsiType[] args,
@NotNull final PsiParameter[] parms,
@NotNull LanguageLevel languageLevel,
@NotNull final PsiSubstitutor substitutorForMethod, boolean isRaw) {
@NotNull final PsiSubstitutor substitutorForMethod,
boolean isRaw,
boolean allowUncheckedConversion, ApplicabilityChecker function) {
for (int i = 0; i < parms.length - 1; i++) {
final PsiType type = args[i];
if (type == null) return false;
@@ -545,10 +571,11 @@ public final class PsiUtil extends PsiUtilCore {
if (isRaw) {
final PsiType substErasure = TypeConversionUtil.erasure(substitutedParmType);
final PsiType typeErasure = TypeConversionUtil.erasure(type);
if (substErasure != null && typeErasure != null && !TypeConversionUtil.isAssignable(substErasure, typeErasure)) {
if (substErasure != null && typeErasure != null && !function.isApplicable(substErasure, typeErasure, allowUncheckedConversion, i)) {
return false;
}
} else if (!TypeConversionUtil.isAssignable(substitutedParmType, type)) {
}
else if (!function.isApplicable(substitutedParmType, type, allowUncheckedConversion, i)) {
return false;
}
}
@@ -54,6 +54,8 @@ public class StrictSubtypingConstraint implements ConstraintFormula {
if (PsiType.NULL.equals(myT) || myT == null) return false;
if (PsiType.NULL.equals(myS) || myS == null || myT.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) return true;
if (PsiType.VOID.equals(myS) ^ PsiType.VOID.equals(myT)) return false;
InferenceVariable inferenceVariable = session.getInferenceVariable(myS);
if (inferenceVariable != null) {
inferenceVariable.addBound(myT, InferenceBound.UPPER);
@@ -33,7 +33,6 @@ 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;
@@ -444,7 +443,11 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
final PsiSubstitutor classSubstitutor1 = info1.getSubstitutor(false); //substitutions for method type parameters will be ignored
final PsiSubstitutor classSubstitutor2 = info2.getSubstitutor(false);
final int max = Math.max(params1.length, params2.length);
//process all arguments of varargs call
//todo check method reference actual params length
final int argsLength = languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && (method1.isVarArgs() || method2.isVarArgs())
? getActualParametersLength() : 0;
final int max = Math.max(Math.max(params1.length, params2.length), argsLength);
PsiType[] types1 = PsiType.createArray(max);
PsiType[] types2 = PsiType.createArray(max);
final boolean varargsPosition = applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.VARARGS;
@@ -542,63 +545,6 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
return Specifics.SECOND;
}
}
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && myArgumentsList instanceof PsiExpressionList && (typeParameters1.length == 0 || typeParameters2.length == 0)) {
boolean toCompareFunctional = false;
if (types1.length > 0 && types2.length > 0) {
for (int i = 0; i < getActualParametersLength(); i++) {
final PsiType type1 = types1[Math.min(i, types1.length - 1)];
final PsiType type2 = types2[Math.min(i, types2.length - 1)];
//from 15.12.2.5 Choosing the Most Specific Method
//In addition, a functional interface type S is more specific than a functional interface type T for an expression exp
// if T is not a subtype of S and one of the following conditions apply.
if (LambdaUtil.isFunctionalType(type1) && !TypeConversionUtil.erasure(type1).isAssignableFrom(type2) &&
LambdaUtil.isFunctionalType(type2) && !TypeConversionUtil.erasure(type2).isAssignableFrom(type1)) {
types1AtSite[Math.min(i, types1.length - 1)] = PsiType.NULL;
types2AtSite[Math.min(i, types2.length - 1)] = PsiType.NULL;
toCompareFunctional = true;
}
}
}
if (toCompareFunctional) {
final boolean applicable12ignoreFunctionalType = isApplicableTo(types2AtSite, method1, languageLevel, varargsPosition,
calculateMethodSubstitutor(typeParameters1, method1, siteSubstitutor1, types1, types2AtSite, languageLevel), null);
final boolean applicable21ignoreFunctionalType = isApplicableTo(types1AtSite, method2, languageLevel, varargsPosition,
calculateMethodSubstitutor(typeParameters2, method2, siteSubstitutor2, types2, types1AtSite, languageLevel), null);
if (applicable12ignoreFunctionalType || applicable21ignoreFunctionalType) {
Specifics specifics = null;
for (int i = 0; i < getActualParametersLength(); i++) {
if (types1AtSite[Math.min(i, types1.length - 1)] == PsiType.NULL &&
types2AtSite[Math.min(i, types2.length - 1)] == PsiType.NULL) {
Specifics specific = isFunctionalTypeMoreSpecific(info1, info2, ((PsiExpressionList)myArgumentsList).getExpressions()[i], i);
if (specific == Specifics.NEITHER) {
specifics = Specifics.NEITHER;
break;
}
if (specifics == null) {
specifics = specific;
} else if (specifics != specific) {
specifics = Specifics.NEITHER;
break;
}
}
}
if (!applicable12ignoreFunctionalType && applicable21ignoreFunctionalType) {
return specifics == Specifics.FIRST ? Specifics.FIRST : Specifics.NEITHER;
}
if (!applicable21ignoreFunctionalType && applicable12ignoreFunctionalType) {
return specifics == Specifics.SECOND ? Specifics.SECOND : Specifics.NEITHER;
}
return specifics;
}
}
}
}
else if (varargsPosition) {
final PsiType lastParamType1 = classSubstitutor1.substitute(types1[types1.length - 1]);
@@ -653,17 +599,44 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
@NotNull LanguageLevel languageLevel,
boolean varargsPosition,
@NotNull PsiSubstitutor methodSubstitutor1,
PsiMethod method2) {
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && method2 != null && method1.getTypeParameters().length > 0 && myArgumentsList instanceof PsiExpressionList) {
@NotNull PsiMethod method2) {
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && method1.getTypeParameters().length > 0 && myArgumentsList instanceof PsiExpressionList) {
final PsiElement parent = myArgumentsList.getParent();
if (parent instanceof PsiCallExpression && ((PsiCallExpression)parent).getTypeArguments().length == 0) {
return InferenceSession.isMoreSpecific(method2, method1, ((PsiExpressionList)myArgumentsList).getExpressions(), myArgumentsList, varargsPosition);
}
}
final int applicabilityLevel = PsiUtil.getApplicabilityLevel(method1, methodSubstitutor1, types2AtSite, languageLevel, false, varargsPosition);
final PsiUtil.ApplicabilityChecker applicabilityChecker = languageLevel.isAtLeast(LanguageLevel.JDK_1_8)
? new PsiUtil.ApplicabilityChecker() {
@Override
public boolean isApplicable(PsiType left, PsiType right,
boolean allowUncheckedConversion, int argId) {
return isTypeMoreSpecific(left, right, argId);
}
}
: PsiUtil.ApplicabilityChecker.ASSIGNABILITY_CHECKER;
final int applicabilityLevel = PsiUtil.getApplicabilityLevel(method1, methodSubstitutor1, types2AtSite, languageLevel, false, varargsPosition, applicabilityChecker);
return applicabilityLevel > MethodCandidateInfo.ApplicabilityLevel.NOT_APPLICABLE;
}
// 15.12.2.5
// A type S is more specific than a type T for any expression if S <: T (§4.10).
// A functional interface type S is more specific than a functional interface type T for
// an expression e if T is not a subtype of S and one of the following is true
private boolean isTypeMoreSpecific(PsiType left, PsiType right, int argId) {
if (TypeConversionUtil.isAssignable(left, right, false)) {
return true;
}
if (myArgumentsList instanceof PsiExpressionList) {
final PsiExpression[] expressions = ((PsiExpressionList)myArgumentsList).getExpressions();
if (argId < expressions.length) {
final Specifics specific = isFunctionalTypeMoreSpecific(expressions[argId], right, left);
return Specifics.FIRST.equals(specific);
}
}
return false;
}
@NotNull
private static PsiType[] typesAtSite(@NotNull PsiType[] types1, @NotNull PsiSubstitutor siteSubstitutor1) {
final PsiType[] types = PsiType.createArray(types1.length);
@@ -753,18 +726,14 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
}
@NotNull
private static Specifics isFunctionalTypeMoreSpecific(@NotNull CandidateInfo method,
@NotNull CandidateInfo conflict,
PsiExpression expr,
int functionalInterfaceIdx) {
private static Specifics isFunctionalTypeMoreSpecific(PsiExpression expr, PsiType sType, PsiType tType) {
if (expr instanceof PsiParenthesizedExpression) {
return isFunctionalTypeMoreSpecific(method, conflict, ((PsiParenthesizedExpression)expr).getExpression(), functionalInterfaceIdx);
return isFunctionalTypeMoreSpecific(((PsiParenthesizedExpression)expr).getExpression(), sType, tType);
}
if (expr instanceof PsiConditionalExpression) {
final Specifics thenSpecifics =
isFunctionalTypeMoreSpecific(method, conflict, ((PsiConditionalExpression)expr).getThenExpression(), functionalInterfaceIdx);
final Specifics elseSpecifics =
isFunctionalTypeMoreSpecific(method, conflict, ((PsiConditionalExpression)expr).getElseExpression(), functionalInterfaceIdx);
final Specifics thenSpecifics = isFunctionalTypeMoreSpecific(((PsiConditionalExpression)expr).getThenExpression(), sType, tType);
final Specifics elseSpecifics = isFunctionalTypeMoreSpecific(((PsiConditionalExpression)expr).getElseExpression(), sType, tType);
return thenSpecifics == elseSpecifics ? thenSpecifics : Specifics.NEITHER;
}
@@ -777,9 +746,9 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
return Specifics.NEITHER;
}
final PsiType sType = getFunctionalType(functionalInterfaceIdx, method);
final PsiType tType = getFunctionalType(functionalInterfaceIdx, conflict);
if (LambdaUtil.isFunctionalType(sType) && LambdaUtil.isFunctionalType(tType)) {
if (LambdaUtil.isFunctionalType(sType) && LambdaUtil.isFunctionalType(tType) &&
!TypeConversionUtil.erasure(tType).isAssignableFrom(sType) &&
!TypeConversionUtil.erasure(sType).isAssignableFrom(tType)) {
final boolean specific12 = InferenceSession.isFunctionalTypeMoreSpecificOnExpression(sType, tType, expr);
final boolean specific21 = InferenceSession.isFunctionalTypeMoreSpecificOnExpression(tType, sType, expr);
if (specific12 && !specific21) return Specifics.FIRST;
@@ -0,0 +1,30 @@
class ATest {
private void test(Function<ATest, String> nameF) {
Function<ATest, String> aTestStringFunction = nameF.andThen(ATest::withUnderscore);
Function<ATest, String> aTestStringFunction1 = nameF.andThen((s) -> {return ATest.withUnderscore(s);});
Function<ATest, String> aTestStringFunction2 = nameF.andThen((String s) -> ATest.withUnderscore(s));
System.out.println(aTestStringFunction);
System.out.println(aTestStringFunction1);
System.out.println(aTestStringFunction2);
}
static String withUnderscore(String s) {
return "";
}
}
@FunctionalInterface
interface Function<A, R> {
R apply(A a);
default <C> Function<A, C> andThen(Function<R, ? extends C> g) { return null;}
default Function1V<A> andThen(Function1V<R> g) {return null;}
}
@FunctionalInterface
interface Function1V<A> {
void apply(A a);
}
@@ -118,6 +118,10 @@ public class MostSpecificResolutionTest extends LightDaemonAnalyzerTestCase {
doTest();
}
public void testFunctionalTypeComparisonWhenMethodsAreNotGeneric() throws Exception {
doTest(false);
}
private void doTest() {
doTest(true);
}