@NotNull private static Specifics isFunctionalTypeMoreSpecific( @NotNull CandidateInfo method, @NotNull CandidateInfo conflict, PsiExpression expr, int functionalInterfaceIdx) { if (expr instanceof PsiParenthesizedExpression) { return isFunctionalTypeMoreSpecific( method, conflict, ((PsiParenthesizedExpression) expr).getExpression(), functionalInterfaceIdx); } if (expr instanceof PsiConditionalExpression) { final Specifics thenSpecifics = isFunctionalTypeMoreSpecific( method, conflict, ((PsiConditionalExpression) expr).getThenExpression(), functionalInterfaceIdx); final Specifics elseSpecifics = isFunctionalTypeMoreSpecific( method, conflict, ((PsiConditionalExpression) expr).getElseExpression(), functionalInterfaceIdx); return thenSpecifics == elseSpecifics ? thenSpecifics : Specifics.NEITHER; } if (expr instanceof PsiFunctionalExpression) { if (expr instanceof PsiLambdaExpression && !((PsiLambdaExpression) expr).hasFormalParameterTypes()) { return Specifics.NEITHER; } if (expr instanceof PsiMethodReferenceExpression && !((PsiMethodReferenceExpression) expr).isExact()) { return Specifics.NEITHER; } final PsiType sType = getFunctionalType(functionalInterfaceIdx, method); final PsiType tType = getFunctionalType(functionalInterfaceIdx, conflict); if (LambdaUtil.isFunctionalType(sType) && LambdaUtil.isFunctionalType(tType)) { final boolean specific12 = InferenceSession.isFunctionalTypeMoreSpecificOnExpression(sType, tType, expr); final boolean specific21 = InferenceSession.isFunctionalTypeMoreSpecificOnExpression(tType, sType, expr); if (specific12 && !specific21) return Specifics.FIRST; if (!specific12 && specific21) return Specifics.SECOND; } } return Specifics.NEITHER; }
public static boolean isReceiverType( PsiType functionalInterfaceType, PsiClass containingClass, @Nullable PsiMethod referencedMethod) { final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); final MethodSignature function = LambdaUtil.getFunction(resolveResult.getElement()); if (function != null) { final int interfaceMethodParamsLength = function.getParameterTypes().length; if (interfaceMethodParamsLength > 0) { final PsiType firstParamType = resolveResult.getSubstitutor().substitute(function.getParameterTypes()[0]); boolean isReceiver = isReceiverType( firstParamType, containingClass, PsiUtil.resolveGenericsClassInType(firstParamType).getSubstitutor()); if (isReceiver) { if (referencedMethod == null) { if (interfaceMethodParamsLength == 1) return true; return false; } if (referencedMethod.getParameterList().getParametersCount() != interfaceMethodParamsLength - 1) { return false; } return true; } } } return false; }
private static boolean hasReceiver( @NotNull PsiMethodReferenceExpression methodRef, @NotNull PsiMethod method, PsiType functionalInterfaceType) { final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); final MethodSignature signature = interfaceMethod != null ? interfaceMethod.getSignature( LambdaUtil.getSubstitutor(interfaceMethod, resolveResult)) : null; if (signature == null) { return false; } final PsiType[] parameterTypes = signature.getParameterTypes(); final QualifierResolveResult qualifierResolveResult = getQualifierResolveResult(methodRef); return (method.getParameterList().getParametersCount() + 1 == parameterTypes.length || method.isVarArgs() && parameterTypes.length > 0 && !method.hasModifierProperty(PsiModifier.STATIC)) && hasReceiver(parameterTypes, qualifierResolveResult, methodRef); }
public static String checkReturnType( PsiMethodReferenceExpression expression, JavaResolveResult result, PsiType functionalInterfaceType) { final PsiElement resolve = result.getElement(); if (resolve instanceof PsiMethod) { final PsiClass containingClass = ((PsiMethod) resolve).getContainingClass(); LOG.assertTrue(containingClass != null); PsiSubstitutor subst = result.getSubstitutor(); PsiClass qContainingClass = getQualifierResolveResult(expression).getContainingClass(); if (qContainingClass != null && isReceiverType(functionalInterfaceType, containingClass, (PsiMethod) resolve)) { subst = TypeConversionUtil.getClassSubstitutor(containingClass, qContainingClass, subst); LOG.assertTrue(subst != null); } final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType); PsiType returnType = PsiTypesUtil.patchMethodGetClassReturnType( expression, expression, (PsiMethod) resolve, null, PsiUtil.getLanguageLevel(expression)); if (returnType == null) { returnType = ((PsiMethod) resolve).getReturnType(); } PsiType methodReturnType = subst.substitute(returnType); if (interfaceReturnType != null && interfaceReturnType != PsiType.VOID) { if (methodReturnType == null) { methodReturnType = JavaPsiFacade.getElementFactory(expression.getProject()) .createType(containingClass, subst); } if (!TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType, false)) { return "Bad return type in method reference: cannot convert " + methodReturnType.getCanonicalText() + " to " + interfaceReturnType.getCanonicalText(); } } } return null; }
static PsiSubstitutor infer( @NotNull PsiTypeParameter[] typeParameters, @NotNull PsiParameter[] parameters, @NotNull PsiExpression[] arguments, @NotNull PsiSubstitutor partialSubstitutor, @NotNull final PsiElement parent, @NotNull final ParameterTypeInferencePolicy policy) { if (parent instanceof PsiCall) { final PsiExpressionList argumentList = ((PsiCall) parent).getArgumentList(); final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(argumentList); // overload resolution can't depend on outer call => should not traverse to top if (properties != null && !properties.isApplicabilityCheck() && // in order to to avoid caching of candidates's errors on parent (!) , so check for // overload resolution is left here // But overload resolution can depend on type of lambda parameter. As it can't depend on // lambda body, // traversing down would stop at lambda level and won't take into account overloaded // method !MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argumentList)) { final PsiCall topLevelCall = PsiResolveHelper.ourGraphGuard.doPreventingRecursion( parent, false, new Computable<PsiCall>() { @Override public PsiCall compute() { if (parent instanceof PsiExpression && !PsiPolyExpressionUtil.isPolyExpression((PsiExpression) parent)) { return null; } return LambdaUtil.treeWalkUp(parent); } }); if (topLevelCall != null) { InferenceSession session; if (MethodCandidateInfo.isOverloadCheck() || !PsiDiamondType.ourDiamondGuard.currentStack().isEmpty() || LambdaUtil.isLambdaParameterCheck()) { session = startTopLevelInference(topLevelCall, policy); } else { session = CachedValuesManager.getCachedValue( topLevelCall, new CachedValueProvider<InferenceSession>() { @Nullable @Override public Result<InferenceSession> compute() { return new Result<InferenceSession>( startTopLevelInference(topLevelCall, policy), PsiModificationTracker.MODIFICATION_COUNT); } }); if (session != null) { // reject cached top level session if it was based on wrong candidate: check nested // session if candidate (it's type parameters) are the same // such situations are avoided when overload resolution is performed // (MethodCandidateInfo.isOverloadCheck above) // but situations when client code iterates through // PsiResolveHelper.getReferencedMethodCandidates or similar are impossible to guess final Map<PsiElement, InferenceSession> sessions = session.getInferenceSessionContainer().myNestedSessions; final InferenceSession childSession = sessions.get(parent); if (childSession != null) { for (PsiTypeParameter parameter : typeParameters) { if (!childSession .getInferenceSubstitution() .getSubstitutionMap() .containsKey(parameter)) { session = startTopLevelInference(topLevelCall, policy); break; } } } } } if (session != null) { final PsiSubstitutor childSubstitutor = inferNested( typeParameters, parameters, arguments, partialSubstitutor, (PsiCall) parent, policy, properties, session); if (childSubstitutor != null) return childSubstitutor; } else if (topLevelCall instanceof PsiMethodCallExpression) { return new InferenceSession( typeParameters, partialSubstitutor, parent.getManager(), parent, policy) .prepareSubstitution(); } } } } final InferenceSession inferenceSession = new InferenceSession( typeParameters, partialSubstitutor, parent.getManager(), parent, policy); inferenceSession.initExpressionConstraints(parameters, arguments, parent); return inferenceSession.infer(parameters, arguments, parent); }
private static PsiSubstitutor inferNested( final PsiTypeParameter[] typeParameters, @NotNull final PsiParameter[] parameters, @NotNull final PsiExpression[] arguments, final PsiSubstitutor partialSubstitutor, @NotNull final PsiCall parent, @NotNull final ParameterTypeInferencePolicy policy, final MethodCandidateInfo.CurrentCandidateProperties properties, final InferenceSession parentSession) { final CompoundInitialState compoundInitialState = createState(parentSession); InitialInferenceState initialInferenceState = compoundInitialState.getInitialState(parent); if (initialInferenceState != null) { final InferenceSession childSession = new InferenceSession(initialInferenceState); final List<String> errorMessages = parentSession.getIncompatibleErrorMessages(); if (errorMessages != null) { return childSession.prepareSubstitution(); } return childSession.collectAdditionalAndInfer( parameters, arguments, properties, compoundInitialState.getInitialSubstitutor()); } // we do not investigate lambda return expressions when lambda's return type is already inferred // (proper) // this way all calls from lambda's return expressions won't appear in nested sessions else { PsiElement gParent = PsiUtil.skipParenthesizedExprUp(parent.getParent()); // find the nearest parent which appears in the map and start inference with a provided target // type for a nested lambda while (true) { if (gParent instanceof PsiReturnStatement) { // process code block lambda final PsiElement returnContainer = gParent.getParent(); if (returnContainer instanceof PsiCodeBlock) { gParent = returnContainer.getParent(); } } if (gParent instanceof PsiLambdaExpression) { final PsiCall call = PsiTreeUtil.getParentOfType(gParent, PsiCall.class); if (call != null) { initialInferenceState = compoundInitialState.getInitialState(call); if (initialInferenceState != null) { final int idx = LambdaUtil.getLambdaIdx(call.getArgumentList(), gParent); final PsiMethod method = call.resolveMethod(); if (method != null && idx > -1) { final PsiType parameterType = PsiTypesUtil.getParameterType( method.getParameterList().getParameters(), idx, true); final PsiType parameterTypeInTermsOfSession = initialInferenceState.getInferenceSubstitutor().substitute(parameterType); final PsiType lambdaTargetType = compoundInitialState .getInitialSubstitutor() .substitute(parameterTypeInTermsOfSession); return LambdaUtil.performWithLambdaTargetType( (PsiLambdaExpression) gParent, lambdaTargetType, new Producer<PsiSubstitutor>() { @Nullable @Override public PsiSubstitutor produce() { if (call.equals(PsiTreeUtil.getParentOfType(parent, PsiCall.class, true))) { // parent was mentioned in the top inference session // just proceed with the target type final InferenceSession inferenceSession = new InferenceSession( typeParameters, partialSubstitutor, parent.getManager(), parent, policy); inferenceSession.initExpressionConstraints(parameters, arguments, parent); return inferenceSession.infer(parameters, arguments, parent); } // one of the grand parents were found in the top inference session // start from it as it is the top level call final InferenceSession sessionInsideLambda = startTopLevelInference(call, policy); return inferNested( typeParameters, parameters, arguments, partialSubstitutor, parent, policy, properties, sessionInsideLambda); } }); } } else { gParent = PsiUtil.skipParenthesizedExprUp(call.getParent()); continue; } } } break; } } return null; }
private Specifics isMoreSpecific( @NotNull MethodCandidateInfo info1, @NotNull MethodCandidateInfo info2, @MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel, @NotNull LanguageLevel languageLevel) { PsiMethod method1 = info1.getElement(); PsiMethod method2 = info2.getElement(); final PsiClass class1 = method1.getContainingClass(); final PsiClass class2 = method2.getContainingClass(); final PsiParameter[] params1 = method1.getParameterList().getParameters(); final PsiParameter[] params2 = method2.getParameterList().getParameters(); final PsiTypeParameter[] typeParameters1 = method1.getTypeParameters(); final PsiTypeParameter[] typeParameters2 = method2.getTypeParameters(); 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); PsiType[] types1 = PsiType.createArray(max); PsiType[] types2 = PsiType.createArray(max); final boolean varargsPosition = applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.VARARGS; for (int i = 0; i < max; i++) { ProgressManager.checkCanceled(); PsiType type1 = params1.length > 0 ? params1[Math.min(i, params1.length - 1)].getType() : null; PsiType type2 = params2.length > 0 ? params2[Math.min(i, params2.length - 1)].getType() : null; if (varargsPosition) { if (type1 instanceof PsiEllipsisType && type2 instanceof PsiEllipsisType && params1.length == params2.length && class1 != null && (!JavaVersionService.getInstance().isAtLeast(class1, JavaSdkVersion.JDK_1_7) || ((PsiArrayType) type1) .getComponentType() .equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || ((PsiArrayType) type2) .getComponentType() .equalsToText(CommonClassNames.JAVA_LANG_OBJECT))) { type1 = ((PsiEllipsisType) type1).toArrayType(); type2 = ((PsiEllipsisType) type2).toArrayType(); } else { type1 = type1 instanceof PsiEllipsisType ? ((PsiArrayType) type1).getComponentType() : type1; type2 = type2 instanceof PsiEllipsisType ? ((PsiArrayType) type2).getComponentType() : type2; } } types1[i] = type1; types2[i] = type2; } boolean sameBoxing = true; int[] boxingHappened = new int[2]; for (int i = 0; i < types1.length; i++) { ProgressManager.checkCanceled(); PsiType type1 = classSubstitutor1.substitute(types1[i]); PsiType type2 = classSubstitutor2.substitute(types2[i]); PsiType argType = i < getActualParameterTypes().length ? getActualParameterTypes()[i] : null; boolean boxingInFirst = false; if (isBoxingHappened(argType, type1, languageLevel)) { boxingHappened[0] += 1; boxingInFirst = true; } boolean boxingInSecond = false; if (isBoxingHappened(argType, type2, languageLevel)) { boxingHappened[1] += 1; boxingInSecond = true; } sameBoxing &= boxingInFirst == boxingInSecond; } if (boxingHappened[0] == 0 && boxingHappened[1] > 0) return Specifics.FIRST; if (boxingHappened[0] > 0 && boxingHappened[1] == 0) return Specifics.SECOND; if (sameBoxing) { final PsiSubstitutor siteSubstitutor1 = info1.getSiteSubstitutor(); final PsiSubstitutor siteSubstitutor2 = info2.getSiteSubstitutor(); final PsiType[] types2AtSite = typesAtSite(types2, siteSubstitutor2); final PsiType[] types1AtSite = typesAtSite(types1, siteSubstitutor1); final PsiSubstitutor methodSubstitutor1 = calculateMethodSubstitutor( typeParameters1, method1, siteSubstitutor1, types1, types2AtSite, languageLevel); boolean applicable12 = isApplicableTo( types2AtSite, method1, languageLevel, varargsPosition, methodSubstitutor1, method2); final PsiSubstitutor methodSubstitutor2 = calculateMethodSubstitutor( typeParameters2, method2, siteSubstitutor2, types2, types1AtSite, languageLevel); boolean applicable21 = isApplicableTo( types1AtSite, method2, languageLevel, varargsPosition, methodSubstitutor2, method1); if (!myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { final boolean typeArgsApplicable12 = GenericsUtil.isTypeArgumentsApplicable( typeParameters1, methodSubstitutor1, myArgumentsList, !applicable21); final boolean typeArgsApplicable21 = GenericsUtil.isTypeArgumentsApplicable( typeParameters2, methodSubstitutor2, myArgumentsList, !applicable12); if (!typeArgsApplicable12) { applicable12 = false; } if (!typeArgsApplicable21) { applicable21 = false; } } if (applicable12 || applicable21) { if (applicable12 && !applicable21) return Specifics.SECOND; if (applicable21 && !applicable12) return Specifics.FIRST; final boolean abstract1 = method1.hasModifierProperty(PsiModifier.ABSTRACT); final boolean abstract2 = method2.hasModifierProperty(PsiModifier.ABSTRACT); if (abstract1 && !abstract2) { return Specifics.SECOND; } if (abstract2 && !abstract1) { return Specifics.FIRST; } } 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]); final PsiType lastParamType2 = classSubstitutor2.substitute(types2[types1.length - 1]); final boolean assignable1 = TypeConversionUtil.isAssignable(lastParamType2, lastParamType1); final boolean assignable2 = TypeConversionUtil.isAssignable(lastParamType1, lastParamType2); if (assignable1 && !assignable2) { return Specifics.FIRST; } if (assignable2 && !assignable1) { return Specifics.SECOND; } } if (class1 != class2) { if (class2.isInheritor(class1, true) || class1.isInterface() && !class2.isInterface()) { if (MethodSignatureUtil.isSubsignature( method1.getSignature(info1.getSubstitutor(false)), method2.getSignature(info2.getSubstitutor(false)))) { return Specifics.SECOND; } else if (method1.hasModifierProperty(PsiModifier.STATIC) && method2.hasModifierProperty(PsiModifier.STATIC) && boxingHappened[0] == 0) { return Specifics.SECOND; } } else if (MethodSignatureUtil.areErasedParametersEqual( method1.getSignature(PsiSubstitutor.EMPTY), method2.getSignature(PsiSubstitutor.EMPTY)) && MethodSignatureUtil.isSubsignature( method2.getSignature(info2.getSubstitutor(false)), method1.getSignature(info1.getSubstitutor(false)))) { return Specifics.FIRST; } else if (class1.isInheritor(class2, true) || class2.isInterface()) { if (method1.hasModifierProperty(PsiModifier.STATIC) && method2.hasModifierProperty(PsiModifier.STATIC) && boxingHappened[0] == 0) { return Specifics.FIRST; } } } final boolean raw1 = PsiUtil.isRawSubstitutor(method1, classSubstitutor1); final boolean raw2 = PsiUtil.isRawSubstitutor(method2, classSubstitutor2); if (raw1 ^ raw2) { return raw1 ? Specifics.SECOND : Specifics.FIRST; } final boolean varargs1 = info1.isVarargs(); final boolean varargs2 = info2.isVarargs(); if (varargs1 ^ varargs2) { return varargs1 ? Specifics.SECOND : Specifics.FIRST; } return Specifics.NEITHER; }