| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.psi.impl.source.resolve.graphInference; |
| |
| import com.intellij.ide.highlighter.JavaFileType; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiImplUtil; |
| import com.intellij.psi.impl.source.resolve.graphInference.constraints.*; |
| import com.intellij.psi.infos.MethodCandidateInfo; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.util.*; |
| import com.intellij.util.ArrayUtilRt; |
| import com.intellij.util.Function; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * User: anna |
| */ |
| public class InferenceSession { |
| private static final Logger LOG = Logger.getInstance("#" + InferenceSession.class.getName()); |
| public static final Key<PsiType> LOWER_BOUND = Key.create("LowBound"); |
| |
| private static final Key<Boolean> ERASED = Key.create("UNCHECKED_CONVERSION"); |
| private static final Function<Pair<PsiType, PsiType>, PsiType> UPPER_BOUND_FUNCTION = new Function<Pair<PsiType, PsiType>, PsiType>() { |
| @Override |
| public PsiType fun(Pair<PsiType, PsiType> pair) { |
| return GenericsUtil.getGreatestLowerBound(pair.first, pair.second); |
| } |
| }; |
| |
| private final Set<InferenceVariable> myInferenceVariables = new LinkedHashSet<InferenceVariable>(); |
| private final List<ConstraintFormula> myConstraints = new ArrayList<ConstraintFormula>(); |
| private final Set<ConstraintFormula> myConstraintsCopy = new HashSet<ConstraintFormula>(); |
| |
| private PsiSubstitutor mySiteSubstitutor; |
| private PsiManager myManager; |
| private int myConstraintIdx = 0; |
| |
| private boolean myErased = false; |
| |
| private final InferenceIncorporationPhase myIncorporationPhase = new InferenceIncorporationPhase(this); |
| |
| private final PsiElement myContext; |
| |
| private PsiSubstitutor myInferenceSubstitution = PsiSubstitutor.EMPTY; |
| private Map<PsiElement, InferenceSession> myNestedSessions = new HashMap<PsiElement, InferenceSession>(); |
| public void registerNestedSession(InferenceSession session) { |
| propagateVariables(session.getInferenceVariables()); |
| myNestedSessions.put(session.getContext(), session); |
| myNestedSessions.putAll(session.myNestedSessions); |
| } |
| |
| public InferenceSession(PsiTypeParameter[] typeParams, |
| PsiType[] leftTypes, |
| PsiType[] rightTypes, |
| PsiSubstitutor siteSubstitutor, |
| PsiManager manager, |
| PsiElement context) { |
| myManager = manager; |
| mySiteSubstitutor = siteSubstitutor; |
| myContext = context; |
| |
| initBounds(typeParams); |
| |
| LOG.assertTrue(leftTypes.length == rightTypes.length); |
| for (int i = 0; i < leftTypes.length; i++) { |
| final PsiType rightType = mySiteSubstitutor.substitute(rightTypes[i]); |
| if (rightType != null) { |
| addConstraint(new TypeCompatibilityConstraint(substituteWithInferenceVariables(leftTypes[i]), substituteWithInferenceVariables(rightType))); |
| } |
| } |
| } |
| |
| public InferenceSession(PsiTypeParameter[] typeParams, |
| PsiSubstitutor siteSubstitutor, |
| PsiManager manager, |
| PsiElement context) { |
| myManager = manager; |
| mySiteSubstitutor = siteSubstitutor; |
| myContext = context; |
| |
| initBounds(typeParams); |
| } |
| |
| public void initExpressionConstraints(PsiParameter[] parameters, PsiExpression[] args, PsiElement parent, PsiMethod method) { |
| final MethodCandidateInfo.CurrentCandidateProperties currentProperties = getCurrentProperties(parent); |
| initExpressionConstraints(parameters, args, parent, method, currentProperties != null && currentProperties.isVarargs()); |
| } |
| |
| public void initExpressionConstraints(PsiParameter[] parameters, |
| PsiExpression[] args, |
| PsiElement parent, |
| PsiMethod method, |
| boolean varargs) { |
| final MethodCandidateInfo.CurrentCandidateProperties currentProperties = getCurrentProperties(parent); |
| if (method == null) { |
| if (currentProperties != null) { |
| method = currentProperties.getMethod(); |
| } |
| } |
| if (parameters.length > 0) { |
| for (int i = 0; i < args.length; i++) { |
| if (args[i] != null && isPertinentToApplicability(args[i], method)) { |
| PsiType parameterType = getParameterType(parameters, i, mySiteSubstitutor, varargs); |
| addConstraint(new ExpressionCompatibilityConstraint(args[i], substituteWithInferenceVariables(parameterType))); |
| } |
| } |
| } |
| } |
| |
| private static MethodCandidateInfo.CurrentCandidateProperties getCurrentProperties(PsiElement parent) { |
| if (parent instanceof PsiCallExpression) { |
| return MethodCandidateInfo.getCurrentMethod(((PsiCallExpression)parent).getArgumentList()); |
| } |
| return null; |
| } |
| |
| /** |
| * Definition from 15.12.2.2 Phase 1: Identify Matching Arity Methods Applicable by Subtyping Strict Invocation |
| * An argument expression is considered pertinent to applicability for a potentially-applicable method m unless it has one of the following forms: |
| |
| 1) An implicitly-typed lambda expression (15.27.1). |
| 2) An inexact method reference (15.13.1). |
| 3) If m is a generic method and the method invocation does not provide explicit type arguments, an explicitly-typed lambda expression or |
| an exact method reference for which the corresponding target type (as derived from the signature of m) is a type parameter of m. |
| 4) An explicitly-typed lambda expression whose body is an expression that is not pertinent to applicability. |
| 5) An explicitly-typed lambda expression whose body is a block, where at least one result expression is not pertinent to applicability. |
| 6) A parenthesized expression (15.8.5) whose contained expression is not pertinent to applicability. |
| 7) A conditional expression (15.25) whose second or third operand is not pertinent to applicability. |
| */ |
| public static boolean isPertinentToApplicability(PsiExpression expr, PsiMethod method) { |
| if (expr instanceof PsiLambdaExpression && ((PsiLambdaExpression)expr).hasFormalParameterTypes() || |
| expr instanceof PsiMethodReferenceExpression && ((PsiMethodReferenceExpression)expr).isExact()) { |
| if (method != null && method.getTypeParameters().length > 0) { |
| final PsiElement parent = PsiUtil.skipParenthesizedExprUp(expr.getParent()); |
| if (parent instanceof PsiExpressionList) { |
| final PsiElement gParent = parent.getParent(); |
| if (gParent instanceof PsiCallExpression && ((PsiCallExpression)gParent).getTypeArgumentList().getTypeParameterElements().length == 0) { |
| final int idx = LambdaUtil.getLambdaIdx(((PsiExpressionList)parent), expr); |
| final PsiParameter[] parameters = method.getParameterList().getParameters(); |
| PsiType paramType; |
| if (idx > parameters.length - 1) { |
| final PsiType lastParamType = parameters[parameters.length - 1].getType(); |
| paramType = parameters[parameters.length - 1].isVarArgs() ? ((PsiEllipsisType)lastParamType).getComponentType() : lastParamType; |
| } |
| else { |
| paramType = parameters[idx].getType(); |
| } |
| final PsiClass psiClass = PsiUtil.resolveClassInType(paramType); //accept ellipsis here |
| if (psiClass instanceof PsiTypeParameter && ((PsiTypeParameter)psiClass).getOwner() == method) return false; |
| } |
| } |
| } |
| } |
| if (expr instanceof PsiLambdaExpression) { |
| if (!((PsiLambdaExpression)expr).hasFormalParameterTypes()) { |
| return false; |
| } |
| for (PsiExpression expression : LambdaUtil.getReturnExpressions((PsiLambdaExpression)expr)) { |
| if (!isPertinentToApplicability(expression, method)) return false; |
| } |
| return true; |
| } |
| if (expr instanceof PsiMethodReferenceExpression) { |
| return ((PsiMethodReferenceExpression)expr).isExact(); |
| } |
| if (expr instanceof PsiParenthesizedExpression) { |
| return isPertinentToApplicability(((PsiParenthesizedExpression)expr).getExpression(), method); |
| } |
| if (expr instanceof PsiConditionalExpression) { |
| final PsiExpression thenExpression = ((PsiConditionalExpression)expr).getThenExpression(); |
| if (!isPertinentToApplicability(thenExpression, method)) return false; |
| final PsiExpression elseExpression = ((PsiConditionalExpression)expr).getElseExpression(); |
| if (!isPertinentToApplicability(elseExpression, method)) return false; |
| } |
| return true; |
| } |
| |
| private static PsiType getParameterType(PsiParameter[] parameters, int i, @Nullable PsiSubstitutor substitutor, boolean varargs) { |
| if (substitutor == null) return null; |
| PsiType parameterType = substitutor.substitute(parameters[i < parameters.length ? i : parameters.length - 1].getType()); |
| if (parameterType instanceof PsiEllipsisType && varargs) { |
| parameterType = ((PsiEllipsisType)parameterType).getComponentType(); |
| } |
| return parameterType; |
| } |
| |
| @NotNull |
| public PsiSubstitutor infer() { |
| return infer(null, null, null); |
| } |
| |
| @NotNull |
| public PsiSubstitutor infer(@Nullable PsiParameter[] parameters, |
| @Nullable PsiExpression[] args, |
| @Nullable PsiElement parent) { |
| final MethodCandidateInfo.CurrentCandidateProperties properties = getCurrentProperties(parent); |
| if (!repeatInferencePhases(true)) { |
| //inferred result would be checked as candidate won't be applicable |
| return resolveSubset(myInferenceVariables, mySiteSubstitutor); |
| } |
| |
| if (properties != null && !properties.isApplicabilityCheck()) { |
| initReturnTypeConstraint(properties.getMethod(), (PsiCallExpression)parent); |
| if (!repeatInferencePhases(true)) { |
| return prepareSubstitution(); |
| } |
| |
| if (parameters != null && args != null && |
| !MethodCandidateInfo.ourOverloadGuard.currentStack().contains(PsiUtil.skipParenthesizedExprUp(parent.getParent()))) { |
| final Set<ConstraintFormula> additionalConstraints = new LinkedHashSet<ConstraintFormula>(); |
| if (parameters.length > 0) { |
| collectAdditionalConstraints(parameters, args, properties.getMethod(), PsiSubstitutor.EMPTY, additionalConstraints, properties.isVarargs()); |
| } |
| |
| if (!additionalConstraints.isEmpty() && !proceedWithAdditionalConstraints(additionalConstraints)) { |
| return prepareSubstitution(); |
| } |
| } |
| } |
| |
| final PsiSubstitutor substitutor = resolveBounds(myInferenceVariables, mySiteSubstitutor); |
| if (substitutor != null) { |
| if (myContext != null) { |
| myContext.putUserData(ERASED, myErased); |
| } |
| mySiteSubstitutor = substitutor; |
| for (InferenceVariable variable : myInferenceVariables) { |
| variable.setInstantiation(substitutor.substitute(variable.getParameter())); |
| } |
| } else { |
| return resolveSubset(myInferenceVariables, mySiteSubstitutor); |
| } |
| |
| return prepareSubstitution(); |
| } |
| |
| private void collectAdditionalConstraints(PsiParameter[] parameters, |
| PsiExpression[] args, |
| PsiMethod parentMethod, |
| PsiSubstitutor siteSubstitutor, |
| Set<ConstraintFormula> additionalConstraints, |
| boolean varargs) { |
| for (int i = 0; i < args.length; i++) { |
| final PsiExpression arg = PsiUtil.skipParenthesizedExprDown(args[i]); |
| if (arg != null) { |
| final InferenceSession nestedCallSession = findNestedCallSession(arg); |
| final PsiType parameterType = |
| nestedCallSession.substituteWithInferenceVariables(getParameterType(parameters, i, siteSubstitutor, varargs)); |
| if (!isPertinentToApplicability(arg, parentMethod)) { |
| additionalConstraints.add(new ExpressionCompatibilityConstraint(arg, parameterType)); |
| } |
| additionalConstraints.add(new CheckedExceptionCompatibilityConstraint(arg, parameterType)); |
| if (arg instanceof PsiCallExpression) { |
| //If the expression is a poly class instance creation expression (15.9) or a poly method invocation expression (15.12), |
| //the set contains all constraint formulas that would appear in the set C when determining the poly expression's invocation type. |
| final PsiMethod calledMethod = getCalledMethod((PsiCallExpression)arg); |
| if (PsiPolyExpressionUtil.isMethodCallPolyExpression(arg, calledMethod)) { |
| collectAdditionalConstraints(additionalConstraints, (PsiCallExpression)arg); |
| } |
| } else if (arg instanceof PsiLambdaExpression) { |
| collectLambdaReturnExpression(additionalConstraints, (PsiLambdaExpression)arg, parameterType); |
| } |
| } |
| } |
| } |
| |
| private static PsiMethod getCalledMethod(PsiCallExpression arg) { |
| final PsiExpressionList argumentList = arg.getArgumentList(); |
| if (argumentList == null || argumentList.getExpressions().length == 0) { |
| return null; |
| } |
| |
| MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(argumentList); |
| if (properties != null) { |
| return properties.getMethod(); |
| } |
| final JavaResolveResult resolveResult = getMethodResult(arg); |
| return resolveResult instanceof MethodCandidateInfo ? (PsiMethod)resolveResult.getElement() : null; |
| } |
| |
| private void collectLambdaReturnExpression(Set<ConstraintFormula> additionalConstraints, |
| PsiLambdaExpression lambdaExpression, |
| PsiType parameterType) { |
| final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(parameterType); |
| if (interfaceReturnType != null) { |
| final List<PsiExpression> returnExpressions = LambdaUtil.getReturnExpressions(lambdaExpression); |
| for (PsiExpression returnExpression : returnExpressions) { |
| processReturnExpression(additionalConstraints, returnExpression, interfaceReturnType); |
| } |
| } |
| } |
| |
| private void processReturnExpression(Set<ConstraintFormula> additionalConstraints, |
| PsiExpression returnExpression, |
| PsiType functionalType) { |
| if (returnExpression instanceof PsiCallExpression) { |
| final PsiMethod calledMethod = getCalledMethod((PsiCallExpression)returnExpression); |
| if (PsiPolyExpressionUtil.isMethodCallPolyExpression(returnExpression, calledMethod)) { |
| collectAdditionalConstraints(additionalConstraints, (PsiCallExpression)returnExpression); |
| } |
| } |
| else if (returnExpression instanceof PsiParenthesizedExpression) { |
| processReturnExpression(additionalConstraints, ((PsiParenthesizedExpression)returnExpression).getExpression(), functionalType); |
| } |
| else if (returnExpression instanceof PsiConditionalExpression) { |
| processReturnExpression(additionalConstraints, ((PsiConditionalExpression)returnExpression).getThenExpression(), functionalType); |
| processReturnExpression(additionalConstraints, ((PsiConditionalExpression)returnExpression).getElseExpression(), functionalType); |
| } |
| else if (returnExpression instanceof PsiLambdaExpression) { |
| collectLambdaReturnExpression(additionalConstraints, (PsiLambdaExpression)returnExpression, functionalType); |
| } |
| } |
| |
| private void collectAdditionalConstraints(final Set<ConstraintFormula> additionalConstraints, |
| final PsiCallExpression callExpression) { |
| PsiExpressionList argumentList = callExpression.getArgumentList(); |
| if (argumentList != null) { |
| final JavaResolveResult result = getMethodResult(callExpression); |
| MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(argumentList); |
| final PsiMethod method = result instanceof MethodCandidateInfo ? ((MethodCandidateInfo)result).getElement() : properties != null ? properties.getMethod() : null; |
| if (method != null) { |
| final PsiExpression[] newArgs = argumentList.getExpressions(); |
| final PsiParameter[] newParams = method.getParameterList().getParameters(); |
| if (newParams.length > 0) { |
| collectAdditionalConstraints(newParams, newArgs, method, result != null ? ((MethodCandidateInfo)result).getSiteSubstitutor() : properties.getSubstitutor(), |
| additionalConstraints, result != null ? ((MethodCandidateInfo)result).isVarargs() : properties.isVarargs()); |
| } |
| } |
| } |
| } |
| |
| private static JavaResolveResult getMethodResult(final PsiCallExpression callExpression) { |
| final PsiExpressionList argumentList = callExpression.getArgumentList(); |
| |
| final PsiLambdaExpression expression = PsiTreeUtil.getParentOfType(argumentList, PsiLambdaExpression.class); |
| final Computable<JavaResolveResult> computableResolve = new Computable<JavaResolveResult>() { |
| @Override |
| public JavaResolveResult compute() { |
| return callExpression.resolveMethodGenerics(); |
| } |
| }; |
| MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(argumentList); |
| return properties != null ? null : |
| expression == null |
| ? computableResolve.compute() |
| : PsiResolveHelper.ourGraphGuard.doPreventingRecursion(expression, false, computableResolve); |
| } |
| |
| public PsiSubstitutor retrieveNonPrimitiveEqualsBounds(Collection<InferenceVariable> variables) { |
| PsiSubstitutor substitutor = mySiteSubstitutor; |
| for (InferenceVariable variable : variables) { |
| final PsiType equalsBound = getEqualsBound(variable, substitutor); |
| if (!(equalsBound instanceof PsiPrimitiveType)) { |
| substitutor = substitutor.put(variable, equalsBound); |
| } |
| } |
| return substitutor; |
| } |
| |
| private PsiSubstitutor prepareSubstitution() { |
| for (InferenceVariable inferenceVariable : myInferenceVariables) { |
| final PsiTypeParameter typeParameter = inferenceVariable.getParameter(); |
| PsiType instantiation = inferenceVariable.getInstantiation(); |
| if (instantiation == PsiType.NULL) { |
| //failed inference |
| mySiteSubstitutor = mySiteSubstitutor |
| .put(typeParameter, JavaPsiFacade.getInstance(typeParameter.getProject()).getElementFactory().createType(typeParameter)); |
| } |
| } |
| return mySiteSubstitutor; |
| } |
| |
| public void initBounds(PsiTypeParameter... typeParameters) { |
| initBounds(myContext, typeParameters); |
| } |
| |
| public InferenceVariable[] initBounds(PsiElement context, PsiTypeParameter... typeParameters) { |
| List<InferenceVariable> result = new ArrayList<InferenceVariable>(typeParameters.length); |
| for (PsiTypeParameter parameter : typeParameters) { |
| InferenceVariable variable = new InferenceVariable(context, parameter); |
| result.add(variable); |
| myInferenceSubstitution = myInferenceSubstitution.put(parameter, |
| JavaPsiFacade.getElementFactory(variable.getProject()).createType(variable)); |
| } |
| for (InferenceVariable variable : result) { |
| PsiTypeParameter parameter = variable.getParameter(); |
| boolean added = false; |
| final PsiClassType[] extendsListTypes = parameter.getExtendsListTypes(); |
| for (PsiType classType : extendsListTypes) { |
| classType = substituteWithInferenceVariables(mySiteSubstitutor.substitute(classType)); |
| if (isProperType(classType)) { |
| added = true; |
| } |
| variable.addBound(classType, InferenceBound.UPPER); |
| } |
| if (!added) { |
| variable.addBound(PsiType.getJavaLangObject(parameter.getManager(), parameter.getResolveScope()), |
| InferenceBound.UPPER); |
| } |
| } |
| myInferenceVariables.addAll(result); |
| return result.toArray(new InferenceVariable[result.size()]); |
| } |
| |
| private void initReturnTypeConstraint(PsiMethod method, final PsiCallExpression context) { |
| if (PsiPolyExpressionUtil.isMethodCallPolyExpression(context, method)) { |
| PsiType returnType = method.getReturnType(); |
| if (!PsiType.VOID.equals(returnType) && returnType != null) { |
| PsiType targetType = getTargetType(context); |
| if (targetType != null && !PsiType.VOID.equals(targetType)) { |
| registerReturnTypeConstraints(PsiUtil.isRawSubstitutor(method, mySiteSubstitutor) ? returnType : mySiteSubstitutor.substitute(returnType), targetType); |
| } |
| } |
| } |
| |
| for (PsiClassType thrownType : method.getThrowsList().getReferencedTypes()) { |
| final InferenceVariable variable = getInferenceVariable(substituteWithInferenceVariables(thrownType)); |
| if (variable != null) { |
| variable.setThrownBound(); |
| } |
| } |
| } |
| |
| public void registerReturnTypeConstraints(PsiType returnType, PsiType targetType) { |
| returnType = substituteWithInferenceVariables(returnType); |
| final InferenceVariable inferenceVariable = shouldResolveAndInstantiate(returnType, targetType); |
| if (inferenceVariable != null) { |
| final PsiSubstitutor substitutor = resolveSubset(Collections.singletonList(inferenceVariable), mySiteSubstitutor); |
| final PsiType substitutedReturnType = substitutor.substitute(inferenceVariable.getParameter()); |
| if (substitutedReturnType != null) { |
| addConstraint(new TypeCompatibilityConstraint(targetType, PsiUtil.captureToplevelWildcards(substitutedReturnType, myContext))); |
| } |
| } |
| else { |
| if (FunctionalInterfaceParameterizationUtil.isWildcardParameterized(returnType)) { |
| final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(returnType); |
| final PsiClass psiClass = resolveResult.getElement(); |
| if (psiClass != null) { |
| LOG.assertTrue(returnType instanceof PsiClassType); |
| final PsiTypeParameter[] typeParameters = psiClass.getTypeParameters(); |
| InferenceVariable[] copy = initBounds(myContext, typeParameters); |
| final PsiType substitutedCapture = PsiUtil.captureToplevelWildcards(returnType, myContext); |
| myIncorporationPhase.addCapture(copy, (PsiClassType)substituteWithInferenceVariables(returnType)); |
| addConstraint(new TypeCompatibilityConstraint(targetType, substitutedCapture)); |
| } |
| } else { |
| addConstraint(new TypeCompatibilityConstraint(targetType, myErased ? TypeConversionUtil.erasure(returnType) : returnType)); |
| } |
| } |
| } |
| |
| private InferenceVariable shouldResolveAndInstantiate(PsiType returnType, PsiType targetType) { |
| final InferenceVariable inferenceVariable = getInferenceVariable(returnType); |
| if (inferenceVariable != null) { |
| if (targetType instanceof PsiPrimitiveType && hasPrimitiveWrapperBound(inferenceVariable)) { |
| return inferenceVariable; |
| } |
| if (targetType instanceof PsiClassType) { |
| if (myErased || |
| hasUncheckedBounds(inferenceVariable, (PsiClassType)targetType) || |
| hasWildcardParameterization(inferenceVariable, (PsiClassType)targetType)) { |
| return inferenceVariable; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static boolean hasPrimitiveWrapperBound(InferenceVariable inferenceVariable) { |
| final InferenceBound[] boundTypes = {InferenceBound.UPPER, InferenceBound.LOWER, InferenceBound.EQ}; |
| for (InferenceBound inferenceBound : boundTypes) { |
| final List<PsiType> bounds = inferenceVariable.getBounds(inferenceBound); |
| for (PsiType bound : bounds) { |
| if (PsiPrimitiveType.getUnboxedType(bound) != null) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static boolean hasUncheckedBounds(InferenceVariable inferenceVariable, PsiClassType targetType) { |
| if (!targetType.isRaw()) { |
| final InferenceBound[] boundTypes = {InferenceBound.EQ, InferenceBound.LOWER}; |
| for (InferenceBound inferenceBound : boundTypes) { |
| final List<PsiType> bounds = inferenceVariable.getBounds(inferenceBound); |
| for (PsiType bound : bounds) { |
| if (TypeCompatibilityConstraint.isUncheckedConversion(targetType, bound)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static boolean hasWildcardParameterization(InferenceVariable inferenceVariable, PsiClassType targetType) { |
| if (!FunctionalInterfaceParameterizationUtil.isWildcardParameterized(targetType)) { |
| final List<PsiType> bounds = inferenceVariable.getBounds(InferenceBound.LOWER); |
| final Processor<Pair<PsiType, PsiType>> differentParameterizationProcessor = new Processor<Pair<PsiType, PsiType>>() { |
| @Override |
| public boolean process(Pair<PsiType, PsiType> pair) { |
| return pair.first == null || pair.second == null || pair.first.equals(pair.second); |
| } |
| }; |
| if (InferenceIncorporationPhase.findParameterizationOfTheSameGenericClass(bounds, differentParameterizationProcessor)) return true; |
| final List<PsiType> eqBounds = inferenceVariable.getBounds(InferenceBound.EQ); |
| for (PsiType lowBound : bounds) { |
| if (FunctionalInterfaceParameterizationUtil.isWildcardParameterized(lowBound)) { |
| for (PsiType bound : eqBounds) { |
| if (lowBound.equals(bound)) { |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static PsiType getTargetType(final PsiExpression context) { |
| PsiType targetType = PsiTypesUtil.getExpectedTypeByParent(context); |
| if (targetType != null) { |
| return targetType; |
| } |
| final PsiElement parent = PsiUtil.skipParenthesizedExprUp(context.getParent()); |
| if (parent instanceof PsiExpressionList) { |
| PsiElement gParent = parent.getParent(); |
| if (gParent instanceof PsiAnonymousClass) { |
| gParent = gParent.getParent(); |
| } |
| if (gParent instanceof PsiCallExpression) { |
| final PsiExpressionList argumentList = ((PsiCallExpression)gParent).getArgumentList(); |
| if (argumentList != null) { |
| final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(argumentList); |
| if (properties != null && properties.isApplicabilityCheck()) { |
| return getTypeByMethod(context, argumentList, properties.getMethod(), properties.isVarargs(), properties.getSubstitutor()); |
| } |
| final JavaResolveResult result = ((PsiCallExpression)gParent).resolveMethodGenerics(); |
| final boolean varargs = properties != null && properties.isVarargs() || result instanceof MethodCandidateInfo && ((MethodCandidateInfo)result).isVarargs(); |
| return getTypeByMethod(context, argumentList, result.getElement(), |
| varargs, |
| PsiResolveHelper.ourGraphGuard.doPreventingRecursion(argumentList.getParent(), false, |
| new Computable<PsiSubstitutor>() { |
| @Override |
| public PsiSubstitutor compute() { |
| return result.getSubstitutor(); |
| } |
| } |
| ) |
| ); |
| } |
| } |
| } else if (parent instanceof PsiConditionalExpression) { |
| return getTargetType((PsiExpression)parent); |
| } |
| else if (parent instanceof PsiLambdaExpression) { |
| return getTargetTypeByContainingLambda((PsiLambdaExpression)parent); |
| } |
| else if (parent instanceof PsiReturnStatement) { |
| return getTargetTypeByContainingLambda(PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class)); |
| } |
| return null; |
| } |
| |
| private static PsiType getTargetTypeByContainingLambda(PsiLambdaExpression lambdaExpression) { |
| if (lambdaExpression != null) { |
| if (PsiUtil.skipParenthesizedExprUp(lambdaExpression.getParent()) instanceof PsiExpressionList) { |
| final PsiType typeTypeByParentCall = getTargetType(lambdaExpression); |
| return LambdaUtil.getFunctionalInterfaceReturnType( |
| FunctionalInterfaceParameterizationUtil.getGroundTargetType(typeTypeByParentCall, lambdaExpression)); |
| } |
| return LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression.getFunctionalInterfaceType()); |
| } |
| return null; |
| } |
| |
| private static PsiType getTypeByMethod(PsiExpression context, |
| PsiExpressionList argumentList, |
| PsiElement parentMethod, |
| boolean varargs, |
| PsiSubstitutor substitutor) { |
| if (parentMethod instanceof PsiMethod) { |
| final PsiParameter[] parameters = ((PsiMethod)parentMethod).getParameterList().getParameters(); |
| if (parameters.length == 0) return null; |
| final PsiExpression[] args = argumentList.getExpressions(); |
| if (!((PsiMethod)parentMethod).isVarArgs() && parameters.length != args.length) return null; |
| PsiElement arg = context; |
| while (arg.getParent() instanceof PsiParenthesizedExpression) { |
| arg = arg.getParent(); |
| } |
| final int i = ArrayUtilRt.find(args, arg); |
| if (i < 0) return null; |
| return getParameterType(parameters, i, substitutor, varargs); |
| } |
| return null; |
| } |
| |
| public InferenceVariable getInferenceVariable(PsiType psiType) { |
| final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(psiType); |
| if (psiClass instanceof InferenceVariable) { |
| return (InferenceVariable)psiClass; |
| } |
| return null; |
| } |
| |
| public boolean isProperType(@Nullable PsiType type) { |
| return collectDependencies(type, null); |
| } |
| |
| public boolean collectDependencies(@Nullable PsiType type, |
| @Nullable final Set<InferenceVariable> dependencies) { |
| if (type == null) return true; |
| final Boolean isProper = type.accept(new PsiTypeVisitor<Boolean>() { |
| @Nullable |
| @Override |
| public Boolean visitType(PsiType type) { |
| return true; |
| } |
| |
| @Nullable |
| @Override |
| public Boolean visitArrayType(PsiArrayType arrayType) { |
| return arrayType.getComponentType().accept(this); |
| } |
| |
| @Nullable |
| @Override |
| public Boolean visitWildcardType(PsiWildcardType wildcardType) { |
| final PsiType bound = wildcardType.getBound(); |
| if (bound == null) return true; |
| return bound.accept(this); |
| } |
| |
| @Nullable |
| @Override |
| public Boolean visitClassType(PsiClassType classType) { |
| final InferenceVariable inferenceVariable = getInferenceVariable(classType); |
| if (inferenceVariable != null) { |
| if (dependencies != null) { |
| dependencies.add(inferenceVariable); |
| return true; |
| } |
| return false; |
| } |
| for (PsiType psiType : classType.getParameters()) { |
| if (!psiType.accept(this)) return false; |
| } |
| return true; |
| } |
| }); |
| return dependencies != null ? !dependencies.isEmpty() : isProper; |
| } |
| |
| public boolean repeatInferencePhases(boolean incorporate) { |
| do { |
| if (!reduceConstraints()) { |
| //inference error occurred |
| return false; |
| } |
| |
| if (incorporate) { |
| if (!myIncorporationPhase.incorporate()) { |
| return false; |
| } |
| } |
| } while (incorporate && !myIncorporationPhase.isFullyIncorporated() || myConstraintIdx < myConstraints.size()); |
| |
| return true; |
| } |
| |
| private boolean reduceConstraints() { |
| List<ConstraintFormula> newConstraints = new ArrayList<ConstraintFormula>(); |
| for (int i = myConstraintIdx; i < myConstraints.size(); i++) { |
| ConstraintFormula constraint = myConstraints.get(i); |
| if (!constraint.reduce(this, newConstraints)) { |
| return false; |
| } |
| } |
| myConstraintIdx = myConstraints.size(); |
| for (ConstraintFormula constraint : newConstraints) { |
| addConstraint(constraint); |
| } |
| return true; |
| } |
| |
| private boolean isThrowable(List<PsiType> upperBounds) { |
| boolean commonThrowable = false; |
| for (PsiType upperBound : upperBounds) { |
| if (upperBound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || !isProperType(upperBound)) continue; |
| if (upperBound.equalsToText(CommonClassNames.JAVA_LANG_EXCEPTION) || |
| upperBound.equalsToText(CommonClassNames.JAVA_LANG_THROWABLE)) { |
| commonThrowable = true; |
| } else { |
| return false; |
| } |
| } |
| return commonThrowable; |
| } |
| |
| private PsiType substituteNonProperBound(PsiType bound, PsiSubstitutor substitutor) { |
| return isProperType(bound) ? bound : substitutor.substitute(bound); |
| } |
| |
| private PsiSubstitutor resolveBounds(final Collection<InferenceVariable> inferenceVariables, |
| PsiSubstitutor substitutor) { |
| final Collection<InferenceVariable> allVars = new ArrayList<InferenceVariable>(inferenceVariables); |
| while (!allVars.isEmpty()) { |
| final List<InferenceVariable> vars = InferenceVariablesOrder.resolveOrder(allVars, this); |
| if (!myIncorporationPhase.hasCaptureConstraints(vars)) { |
| PsiSubstitutor firstSubstitutor = resolveSubset(vars, substitutor); |
| if (firstSubstitutor != null) { |
| final Set<PsiTypeParameter> parameters = firstSubstitutor.getSubstitutionMap().keySet(); |
| if (GenericsUtil.findTypeParameterWithBoundError(parameters.toArray(new PsiTypeParameter[parameters.size()]), firstSubstitutor, myContext, true) != null) { |
| firstSubstitutor = null; |
| } |
| } |
| if (firstSubstitutor != null) { |
| substitutor = firstSubstitutor; |
| allVars.removeAll(vars); |
| continue; |
| } |
| } |
| |
| final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(getManager().getProject()); |
| final PsiTypeParameter[] freshParameters = createFreshVariables(vars, substitutor); |
| for (int i = 0; i < freshParameters.length; i++) { |
| PsiTypeParameter parameter = freshParameters[i]; |
| final InferenceVariable var = vars.get(i); |
| final PsiType lub = getLowerBound(var, PsiSubstitutor.EMPTY); |
| if (lub != PsiType.NULL) { |
| for (PsiClassType upperBoundType : parameter.getExtendsListTypes()) { |
| if (!TypeConversionUtil.isAssignable(upperBoundType, lub)) { |
| return null; |
| } |
| } |
| parameter.putUserData(LOWER_BOUND, lub); |
| } |
| var.addBound(elementFactory.createType(parameter), InferenceBound.EQ); |
| } |
| myIncorporationPhase.forgetCaptures(vars); |
| if (!repeatInferencePhases(true)) { |
| return null; |
| } |
| } |
| return substitutor; |
| } |
| |
| private PsiTypeParameter[] createFreshVariables(final List<InferenceVariable> vars, final PsiSubstitutor siteSubstitutor) { |
| final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(getManager().getProject()); |
| |
| PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; |
| final PsiTypeParameter[] yVars = new PsiTypeParameter[vars.size()]; |
| for (int i = 0; i < vars.size(); i++) { |
| InferenceVariable var = vars.get(i); |
| final PsiTypeParameter parameter = var.getParameter(); |
| yVars[i] = elementFactory.createTypeParameterFromText(getFreshVariableName(var), parameter); |
| substitutor = substitutor.put(var, elementFactory.createType(yVars[i])); |
| } |
| |
| |
| final PsiSubstitutor ySubstitutor = substitutor; |
| final String classText = "class I<" + StringUtil.join(vars, new Function<InferenceVariable, String>() { |
| @Override |
| public String fun(InferenceVariable variable) { |
| final PsiType glb = composeBound(variable, InferenceBound.UPPER, UPPER_BOUND_FUNCTION, ySubstitutor.putAll(siteSubstitutor), true); |
| return getFreshVariableName(variable) + " extends " + glb.getInternalCanonicalText(); |
| } |
| }, ", ") + ">{}"; |
| |
| final PsiFile file = |
| PsiFileFactory.getInstance(getManager().getProject()).createFileFromText("inference_dummy.java", JavaFileType.INSTANCE, classText); |
| LOG.assertTrue(file instanceof PsiJavaFile, classText); |
| final PsiClass[] classes = ((PsiJavaFile)file).getClasses(); |
| LOG.assertTrue(classes.length == 1, classText); |
| return classes[0].getTypeParameters(); |
| } |
| |
| private static String getFreshVariableName(InferenceVariable var) { |
| return var.getName(); |
| } |
| |
| private PsiSubstitutor resolveSubset(Collection<InferenceVariable> vars, PsiSubstitutor substitutor) { |
| for (InferenceVariable var : vars) { |
| LOG.assertTrue(var.getInstantiation() == PsiType.NULL); |
| final PsiTypeParameter typeParameter = var.getParameter(); |
| if (substitutor.getSubstitutionMap().containsKey(typeParameter) && var.getCallContext() != myContext) { |
| continue;//todo |
| } |
| |
| final PsiType eqBound = getEqualsBound(var, substitutor); |
| if (eqBound != PsiType.NULL && eqBound instanceof PsiPrimitiveType) continue; |
| PsiType type = eqBound != PsiType.NULL && (myErased || eqBound != null) ? eqBound : getLowerBound(var, substitutor); |
| if (type == PsiType.NULL) { |
| if (var.isThrownBound() && isThrowable(var.getBounds(InferenceBound.UPPER))) { |
| type = PsiType.getJavaLangRuntimeException(myManager, GlobalSearchScope.allScope(myManager.getProject())); |
| } |
| else { |
| if (substitutor.getSubstitutionMap().get(typeParameter) != null) continue; |
| type = myErased ? null : getUpperBound(var, substitutor); |
| } |
| } |
| substitutor = substitutor.put(typeParameter, type); |
| } |
| |
| return substitutor; |
| } |
| |
| private PsiType getLowerBound(InferenceVariable var, PsiSubstitutor substitutor) { |
| return composeBound(var, InferenceBound.LOWER, new Function<Pair<PsiType, PsiType>, PsiType>() { |
| @Override |
| public PsiType fun(Pair<PsiType, PsiType> pair) { |
| return GenericsUtil.getLeastUpperBound(pair.first, pair.second, myManager); |
| } |
| }, substitutor); |
| } |
| |
| private PsiType getUpperBound(InferenceVariable var, PsiSubstitutor substitutor) { |
| return composeBound(var, InferenceBound.UPPER, UPPER_BOUND_FUNCTION, substitutor); |
| } |
| |
| public PsiType getEqualsBound(InferenceVariable var, PsiSubstitutor substitutor) { |
| return composeBound(var, InferenceBound.EQ, new Function<Pair<PsiType, PsiType>, PsiType>() { |
| @Override |
| public PsiType fun(Pair<PsiType, PsiType> pair) { |
| return pair.first; //todo check if equals |
| } |
| }, substitutor); |
| } |
| |
| private PsiType composeBound(InferenceVariable variable, |
| InferenceBound boundType, |
| Function<Pair<PsiType, PsiType>, PsiType> fun, |
| PsiSubstitutor substitutor) { |
| return composeBound(variable, boundType, fun, substitutor, false); |
| } |
| |
| private PsiType composeBound(InferenceVariable variable, |
| InferenceBound boundType, |
| Function<Pair<PsiType, PsiType>, PsiType> fun, |
| PsiSubstitutor substitutor, |
| boolean includeNonProperBounds) { |
| final List<PsiType> lowerBounds = variable.getBounds(boundType); |
| PsiType lub = PsiType.NULL; |
| for (PsiType lowerBound : lowerBounds) { |
| lowerBound = substituteNonProperBound(lowerBound, substitutor); |
| if (includeNonProperBounds || isProperType(lowerBound)) { |
| if (lub == PsiType.NULL) { |
| lub = lowerBound; |
| } |
| else { |
| lub = fun.fun(Pair.create(lub, lowerBound)); |
| } |
| } |
| } |
| return lub; |
| } |
| |
| public PsiManager getManager() { |
| return myManager; |
| } |
| |
| public GlobalSearchScope getScope() { |
| return GlobalSearchScope.allScope(myManager.getProject()); |
| } |
| |
| public Collection<InferenceVariable> getInferenceVariables() { |
| return myInferenceVariables; |
| } |
| |
| public void addConstraint(ConstraintFormula constraint) { |
| if (myConstraintsCopy.add(constraint)) { |
| myConstraints.add(constraint); |
| } |
| } |
| |
| private boolean proceedWithAdditionalConstraints(Set<ConstraintFormula> additionalConstraints) { |
| final PsiSubstitutor siteSubstitutor = mySiteSubstitutor; |
| |
| while (!additionalConstraints.isEmpty()) { |
| //extract subset of constraints |
| final Set<ConstraintFormula> subset = buildSubset(additionalConstraints); |
| |
| //collect all input variables of selection |
| final Set<InferenceVariable> varsToResolve = new LinkedHashSet<InferenceVariable>(); |
| for (ConstraintFormula formula : subset) { |
| if (formula instanceof InputOutputConstraintFormula) { |
| collectVarsToResolve(varsToResolve, (InputOutputConstraintFormula)formula); |
| } |
| } |
| |
| for (ConstraintFormula formula : subset) { |
| if (!processOneConstraint(formula, siteSubstitutor, varsToResolve)) return false; |
| } |
| } |
| return true; |
| } |
| |
| private void collectVarsToResolve(Set<InferenceVariable> varsToResolve, InputOutputConstraintFormula formula) { |
| final Set<InferenceVariable> inputVariables = formula.getInputVariables(this); |
| if (inputVariables != null) { |
| for (InferenceVariable inputVariable : inputVariables) { |
| varsToResolve.addAll(inputVariable.getDependencies(this)); |
| } |
| varsToResolve.addAll(inputVariables); |
| } |
| } |
| |
| private boolean processOneConstraint(ConstraintFormula formula, PsiSubstitutor siteSubstitutor, Set<InferenceVariable> varsToResolve) { |
| if (formula instanceof ExpressionCompatibilityConstraint) { |
| final PsiExpression expression = ((ExpressionCompatibilityConstraint)formula).getExpression(); |
| final PsiCallExpression callExpression = PsiTreeUtil.getParentOfType(expression, PsiCallExpression.class, false); |
| if (callExpression != null) { |
| final InferenceSession session = myNestedSessions.get(callExpression); |
| if (session != null) { |
| formula.apply(session.myInferenceSubstitution, true); |
| collectVarsToResolve(varsToResolve, (InputOutputConstraintFormula)formula); |
| } |
| } |
| } |
| |
| //resolve input variables |
| PsiSubstitutor substitutor = resolveSubset(varsToResolve, siteSubstitutor); |
| if (substitutor == null) { |
| return false; |
| } |
| |
| if (myContext instanceof PsiCallExpression) { |
| PsiExpressionList argumentList = ((PsiCallExpression)myContext).getArgumentList(); |
| LOG.assertTrue(argumentList != null); |
| MethodCandidateInfo.updateSubstitutor(argumentList, substitutor); |
| } |
| |
| try { |
| formula.apply(substitutor, true); |
| |
| myConstraints.add(formula); |
| if (!repeatInferencePhases(true)) { |
| return false; |
| } |
| } |
| finally { |
| LambdaUtil.ourFunctionTypes.set(null); |
| } |
| return true; |
| } |
| |
| private Set<ConstraintFormula> buildSubset(final Set<ConstraintFormula> additionalConstraints) { |
| |
| final Set<ConstraintFormula> subset = new LinkedHashSet<ConstraintFormula>(); |
| final Set<InferenceVariable> outputVariables = new HashSet<InferenceVariable>(); |
| for (ConstraintFormula constraint : additionalConstraints) { |
| if (constraint instanceof InputOutputConstraintFormula) { |
| final Set<InferenceVariable> inputVariables = ((InputOutputConstraintFormula)constraint).getInputVariables(this); |
| final Set<InferenceVariable> outputVars = ((InputOutputConstraintFormula)constraint).getOutputVariables(inputVariables, this); |
| if (outputVars != null) { |
| outputVariables.addAll(outputVars); |
| } |
| } |
| } |
| |
| for (ConstraintFormula constraint : additionalConstraints) { |
| if (constraint instanceof InputOutputConstraintFormula) { |
| final Set<InferenceVariable> inputVariables = ((InputOutputConstraintFormula)constraint).getInputVariables(this); |
| if (inputVariables != null) { |
| boolean dependsOnOutput = false; |
| for (InferenceVariable inputVariable : inputVariables) { |
| if (dependsOnOutput) break; |
| if (inputVariable.hasInstantiation(this)) continue; |
| final Set<InferenceVariable> dependencies = inputVariable.getDependencies(this); |
| dependencies.add(inputVariable); |
| if (!hasCapture(inputVariable)) { |
| for (InferenceVariable outputVariable : outputVariables) { |
| if (ContainerUtil.intersects(outputVariable.getDependencies(this), dependencies)) { |
| dependsOnOutput = true; |
| break; |
| } |
| } |
| } |
| |
| dependencies.retainAll(outputVariables); |
| if (!dependencies.isEmpty()) { |
| dependsOnOutput = true; |
| break; |
| } |
| } |
| if (!dependsOnOutput) { |
| subset.add(constraint); |
| } |
| } |
| else { |
| subset.add(constraint); |
| } |
| } |
| else { |
| subset.add(constraint); |
| } |
| } |
| if (subset.isEmpty()) { |
| subset.add(additionalConstraints.iterator().next()); //todo choose one constraint |
| } |
| |
| additionalConstraints.removeAll(subset); |
| return subset; |
| } |
| |
| public PsiSubstitutor collectApplicabilityConstraints(final PsiMethodReferenceExpression reference, |
| final MethodCandidateInfo candidateInfo, |
| final PsiType functionalInterfaceType) { |
| final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); |
| final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); |
| LOG.assertTrue(interfaceMethod != null, myContext); |
| final PsiSubstitutor functionalInterfaceSubstitutor = LambdaUtil.getSubstitutor(interfaceMethod, resolveResult); |
| final MethodSignature signature = interfaceMethod.getSignature(functionalInterfaceSubstitutor); |
| |
| final boolean varargs = candidateInfo.isVarargs(); |
| final PsiMethod method = candidateInfo.getElement(); |
| |
| final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(reference); |
| |
| final PsiClass containingClass = qualifierResolveResult.getContainingClass(); |
| LOG.assertTrue(containingClass != null, myContext); |
| |
| final PsiParameter[] functionalMethodParameters = interfaceMethod.getParameterList().getParameters(); |
| final PsiParameter[] parameters = method.getParameterList().getParameters(); |
| |
| final boolean isStatic = method.hasModifierProperty(PsiModifier.STATIC); |
| |
| if (parameters.length == functionalMethodParameters.length && !varargs || isStatic && varargs) {//static methods |
| |
| if (method.isConstructor() && PsiUtil.isRawSubstitutor(containingClass, qualifierResolveResult.getSubstitutor())) { |
| initBounds(containingClass.getTypeParameters()); |
| } |
| |
| for (int i = 0; i < functionalMethodParameters.length; i++) { |
| final PsiType pType = signature.getParameterTypes()[i]; |
| addConstraint(new TypeCompatibilityConstraint(substituteWithInferenceVariables(getParameterType(parameters, i, PsiSubstitutor.EMPTY, varargs)), |
| PsiImplUtil.normalizeWildcardTypeByPosition(pType, reference))); |
| } |
| } |
| else if (parameters.length + 1 == functionalMethodParameters.length && !varargs || |
| !isStatic && varargs && functionalMethodParameters.length > 0 && PsiMethodReferenceUtil.hasReceiver(reference, method)) { //instance methods |
| initBounds(containingClass.getTypeParameters()); |
| |
| final PsiType pType = signature.getParameterTypes()[0]; |
| |
| PsiSubstitutor psiSubstitutor = qualifierResolveResult.getSubstitutor(); |
| // 15.28.1 If the ReferenceType is a raw type, and there exists a parameterization of this type, T, that is a supertype of P1, |
| // the type to search is the result of capture conversion (5.1.10) applied to T; |
| // otherwise, the type to search is the same as the type of the first search. Again, the type arguments, if any, are given by the method reference. |
| if (PsiUtil.isRawSubstitutor(containingClass, qualifierResolveResult.getSubstitutor())) { |
| final PsiClassType.ClassResolveResult pResult = PsiUtil.resolveGenericsClassInType(pType); |
| final PsiClass pClass = pResult.getElement(); |
| final PsiSubstitutor receiverSubstitutor = pClass != null ? TypeConversionUtil |
| .getClassSubstitutor(containingClass, pClass, pResult.getSubstitutor()) : null; |
| if (receiverSubstitutor != null) { |
| if (!method.hasTypeParameters()) { |
| if (signature.getParameterTypes().length == 1 || PsiUtil.isRawSubstitutor(containingClass, receiverSubstitutor)) { |
| return receiverSubstitutor; |
| } |
| } |
| psiSubstitutor = receiverSubstitutor; |
| } |
| } |
| |
| final PsiType qType = JavaPsiFacade.getElementFactory(method.getProject()).createType(containingClass, psiSubstitutor); |
| |
| addConstraint(new TypeCompatibilityConstraint(substituteWithInferenceVariables(qType), pType)); |
| |
| for (int i = 0; i < signature.getParameterTypes().length - 1; i++) { |
| final PsiType interfaceParamType = signature.getParameterTypes()[i + 1]; |
| addConstraint(new TypeCompatibilityConstraint(substituteWithInferenceVariables(getParameterType(parameters, i, PsiSubstitutor.EMPTY, varargs)), |
| PsiImplUtil.normalizeWildcardTypeByPosition(interfaceParamType, reference))); |
| } |
| } |
| |
| return null; |
| } |
| |
| public void setErased() { |
| myErased = true; |
| } |
| |
| public InferenceVariable getInferenceVariable(PsiTypeParameter parameter) { |
| return parameter instanceof InferenceVariable && myInferenceVariables.contains(parameter) ? (InferenceVariable)parameter : null; |
| } |
| |
| /** |
| * 18.5.4 More Specific Method Inference |
| */ |
| public static boolean isMoreSpecific(PsiMethod m1, |
| PsiMethod m2, |
| PsiExpression[] args, |
| PsiElement context, |
| boolean varargs) { |
| List<PsiTypeParameter> params = new ArrayList<PsiTypeParameter>(); |
| for (PsiTypeParameter param : PsiUtil.typeParametersIterable(m2)) { |
| params.add(param); |
| } |
| final InferenceSession session = new InferenceSession(params.toArray(new PsiTypeParameter[params.size()]), PsiSubstitutor.EMPTY, m2.getManager(), context); |
| |
| final PsiParameter[] parameters1 = m1.getParameterList().getParameters(); |
| final PsiParameter[] parameters2 = m2.getParameterList().getParameters(); |
| if (!varargs) { |
| LOG.assertTrue(parameters1.length == parameters2.length); |
| } |
| |
| final int paramsLength = !varargs ? parameters1.length : parameters1.length - 1; |
| for (int i = 0; i < paramsLength; i++) { |
| PsiType sType = getParameterType(parameters1, i, PsiSubstitutor.EMPTY, false); |
| PsiType tType = session.substituteWithInferenceVariables(getParameterType(parameters2, i, PsiSubstitutor.EMPTY, varargs)); |
| if (session.isProperType(sType) && session.isProperType(tType)) { |
| if (!TypeConversionUtil.isAssignable(tType, sType)) { |
| return false; |
| } |
| continue; |
| } |
| if (LambdaUtil.isFunctionalType(sType) && LambdaUtil.isFunctionalType(tType) && !relates(sType, tType)) { |
| if (!isFunctionalTypeMoreSpecific(sType, tType, session, args)) { |
| return false; |
| } |
| } else { |
| session.addConstraint(new StrictSubtypingConstraint(tType, sType)); |
| } |
| } |
| |
| if (varargs) { |
| PsiType sType = getParameterType(parameters1, paramsLength, PsiSubstitutor.EMPTY, true); |
| PsiType tType = session.substituteWithInferenceVariables(getParameterType(parameters2, paramsLength, PsiSubstitutor.EMPTY, true)); |
| session.addConstraint(new StrictSubtypingConstraint(tType, sType)); |
| } |
| |
| return session.repeatInferencePhases(true); |
| } |
| |
| /** |
| * 15.12.2.5 Choosing the Most Specific Method |
| * "a functional interface type S is more specific than a functional interface type T for an expression exp" part |
| */ |
| public static boolean isFunctionalTypeMoreSpecificOnExpression(PsiType sType, |
| PsiType tType, |
| PsiExpression arg) { |
| return isFunctionalTypeMoreSpecific(sType, tType, null, arg); |
| } |
| |
| private static boolean isFunctionalTypeMoreSpecific(PsiType sType, |
| PsiType tType, |
| @Nullable InferenceSession session, |
| PsiExpression... args) { |
| final PsiType capturedSType = sType;//todo capture of Si session != null && sType != null ? PsiUtil.captureToplevelWildcards(sType, session.myContext) : sType; |
| final PsiClassType.ClassResolveResult sResult = PsiUtil.resolveGenericsClassInType(capturedSType); |
| final PsiMethod sInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(sResult); |
| LOG.assertTrue(sInterfaceMethod != null); |
| final PsiSubstitutor sSubstitutor = LambdaUtil.getSubstitutor(sInterfaceMethod, sResult); |
| |
| final PsiClassType.ClassResolveResult tResult = PsiUtil.resolveGenericsClassInType(tType); |
| final PsiMethod tInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(tResult); |
| LOG.assertTrue(tInterfaceMethod != null); |
| final PsiSubstitutor tSubstitutor = LambdaUtil.getSubstitutor(tInterfaceMethod, tResult); |
| |
| for (PsiExpression arg : args) { |
| if (!argConstraints(arg, session, sInterfaceMethod, sSubstitutor, tInterfaceMethod, tSubstitutor)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| protected static boolean argConstraints(PsiExpression arg, |
| @Nullable InferenceSession session, |
| PsiMethod sInterfaceMethod, |
| PsiSubstitutor sSubstitutor, |
| PsiMethod tInterfaceMethod, |
| PsiSubstitutor tSubstitutor) { |
| if (arg instanceof PsiLambdaExpression && ((PsiLambdaExpression)arg).hasFormalParameterTypes()) { |
| final PsiType sReturnType = sSubstitutor.substitute(sInterfaceMethod.getReturnType()); |
| final PsiType tReturnType = tSubstitutor.substitute(tInterfaceMethod.getReturnType()); |
| |
| if (tReturnType == PsiType.VOID) { |
| return true; |
| } |
| |
| final List<PsiExpression> returnExpressions = LambdaUtil.getReturnExpressions((PsiLambdaExpression)arg); |
| |
| if (LambdaUtil.isFunctionalType(sReturnType) && LambdaUtil.isFunctionalType(tReturnType) && |
| !TypeConversionUtil.isAssignable(TypeConversionUtil.erasure(sReturnType), TypeConversionUtil.erasure(tReturnType)) && |
| !TypeConversionUtil.isAssignable(TypeConversionUtil.erasure(tReturnType), TypeConversionUtil.erasure(sReturnType))) { |
| |
| //Otherwise, if R1 and R2 are functional interface types, and neither interface is a subinterface of the other, |
| //then these rules are applied recursively to R1 and R2, for each result expression in expi. |
| if (!isFunctionalTypeMoreSpecific(sReturnType, tReturnType, session, returnExpressions.toArray(new PsiExpression[returnExpressions.size()]))) { |
| return false; |
| } |
| } else { |
| final boolean sPrimitive = sReturnType instanceof PsiPrimitiveType && sReturnType != PsiType.VOID; |
| final boolean tPrimitive = tReturnType instanceof PsiPrimitiveType && tReturnType != PsiType.VOID; |
| if (sPrimitive ^ tPrimitive) { |
| for (PsiExpression returnExpression : returnExpressions) { |
| if (!PsiPolyExpressionUtil.isPolyExpression(returnExpression)) { |
| final PsiType returnExpressionType = returnExpression.getType(); |
| if (sPrimitive) { |
| if (!(returnExpressionType instanceof PsiPrimitiveType)) { |
| return false; |
| } |
| } else { |
| if (!(returnExpressionType instanceof PsiClassType)) { |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| if (session != null) { |
| session.addConstraint(new StrictSubtypingConstraint(tReturnType, sReturnType)); |
| return true; |
| } else { |
| return sReturnType != null && tReturnType != null && TypeConversionUtil.isAssignable(tReturnType, sReturnType); |
| } |
| } |
| } |
| |
| if (arg instanceof PsiMethodReferenceExpression && ((PsiMethodReferenceExpression)arg).isExact()) { |
| final PsiParameter[] sParameters = sInterfaceMethod.getParameterList().getParameters(); |
| final PsiParameter[] tParameters = tInterfaceMethod.getParameterList().getParameters(); |
| if (session != null) { |
| LOG.assertTrue(sParameters.length == tParameters.length); |
| for (int i = 0; i < tParameters.length; i++) { |
| session.addConstraint(new TypeEqualityConstraint(tSubstitutor.substitute(tParameters[i].getType()), |
| sSubstitutor.substitute(sParameters[i].getType()))); |
| } |
| } |
| final PsiType sReturnType = sSubstitutor.substitute(sInterfaceMethod.getReturnType()); |
| final PsiType tReturnType = tSubstitutor.substitute(tInterfaceMethod.getReturnType()); |
| if (tReturnType == PsiType.VOID) { |
| return true; |
| } |
| |
| final boolean sPrimitive = sReturnType instanceof PsiPrimitiveType && sReturnType != PsiType.VOID; |
| final boolean tPrimitive = tReturnType instanceof PsiPrimitiveType && tReturnType != PsiType.VOID; |
| |
| if (sPrimitive ^ tPrimitive) { |
| final PsiMember member = ((PsiMethodReferenceExpression)arg).getPotentiallyApplicableMember(); |
| LOG.assertTrue(member != null); |
| if (member instanceof PsiMethod) { |
| final PsiType methodReturnType = ((PsiMethod)member).getReturnType(); |
| if (sPrimitive && methodReturnType instanceof PsiPrimitiveType && methodReturnType != PsiType.VOID || |
| tPrimitive && methodReturnType instanceof PsiClassType) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| if (session != null) { |
| session.addConstraint(new StrictSubtypingConstraint(tReturnType, sReturnType)); |
| return true; |
| } else { |
| return sReturnType != null && tReturnType != null && TypeConversionUtil.isAssignable(tReturnType, sReturnType); |
| } |
| } |
| |
| if (arg instanceof PsiParenthesizedExpression) { |
| return argConstraints(((PsiParenthesizedExpression)arg).getExpression(), session, sInterfaceMethod, sSubstitutor, tInterfaceMethod, tSubstitutor); |
| } |
| |
| if (arg instanceof PsiConditionalExpression) { |
| final PsiExpression thenExpression = ((PsiConditionalExpression)arg).getThenExpression(); |
| final PsiExpression elseExpression = ((PsiConditionalExpression)arg).getElseExpression(); |
| return argConstraints(thenExpression, session, sInterfaceMethod, sSubstitutor, tInterfaceMethod, tSubstitutor) && |
| argConstraints(elseExpression, session, sInterfaceMethod, sSubstitutor, tInterfaceMethod, tSubstitutor); |
| } |
| return false; |
| } |
| |
| /** |
| * if Si is a functional interface type and Ti is a parameterization of functional interface, I, and none of the following is true: |
| |
| * Si is a superinterface of I, or a parameterization of a superinterface of I. |
| * Si is subinterface of I, or a parameterization of a subinterface of I. |
| * Si is an intersection type and each element of the intersection is a superinterface of I, or a parameterization of a superinterface of I. |
| * Si is an intersection type and some element of the intersection is a subinterface of I, or a parameterization of a subinterface of I. |
| */ |
| private static boolean relates(PsiType sType, PsiType tType) { |
| final PsiType erasedType = TypeConversionUtil.erasure(tType); |
| LOG.assertTrue(erasedType != null); |
| if (sType instanceof PsiIntersectionType) { |
| boolean superRelation = true; |
| boolean subRelation = false; |
| for (PsiType sConjunct : ((PsiIntersectionType)sType).getConjuncts()) { |
| final PsiType sConjunctErasure = TypeConversionUtil.erasure(sConjunct); |
| if (sConjunctErasure != null) { |
| superRelation &= TypeConversionUtil.isAssignable(sConjunctErasure, erasedType); |
| subRelation |= TypeConversionUtil.isAssignable(erasedType, sConjunctErasure); |
| } |
| } |
| return superRelation || subRelation; |
| } |
| if (sType instanceof PsiClassType) { |
| final PsiType sTypeErasure = TypeConversionUtil.erasure(sType); |
| if (sTypeErasure != null) { |
| return TypeConversionUtil.isAssignable(sTypeErasure, erasedType) || TypeConversionUtil.isAssignable(erasedType, sTypeErasure); |
| } |
| } |
| return false; |
| } |
| |
| public void collectCaptureDependencies(InferenceVariable inferenceVariable, Set<InferenceVariable> dependencies) { |
| myIncorporationPhase.collectCaptureDependencies(inferenceVariable, dependencies); |
| } |
| |
| public boolean hasCapture(InferenceVariable inferenceVariable) { |
| return myIncorporationPhase.hasCaptureConstraints(Arrays.asList(inferenceVariable)); |
| } |
| |
| public static boolean wasUncheckedConversionPerformed(PsiElement call) { |
| final Boolean erased = call.getUserData(ERASED); |
| return erased != null && erased.booleanValue(); |
| } |
| |
| public PsiElement getContext() { |
| return myContext; |
| } |
| |
| public void propagateVariables(Collection<InferenceVariable> variables) { |
| myInferenceVariables.addAll(variables); |
| } |
| |
| public PsiType substituteWithInferenceVariables(PsiType type) { |
| return myInferenceSubstitution.substitute(type); |
| } |
| |
| public InferenceSession findNestedCallSession(PsiExpression arg) { |
| InferenceSession session = myNestedSessions.get(PsiTreeUtil.getParentOfType(arg, PsiCallExpression.class)); |
| if (session == null) { |
| session = this; |
| } |
| return session; |
| } |
| |
| public PsiType startWithFreshVars(PsiType type) { |
| PsiSubstitutor s = PsiSubstitutor.EMPTY; |
| for (InferenceVariable variable : myInferenceVariables) { |
| s = s.put(variable, JavaPsiFacade.getElementFactory(variable.getProject()).createType(variable.getParameter())); |
| } |
| return s.substitute(type); |
| } |
| } |