/* * 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.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; 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.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.util.ArrayUtilRt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.Serializable; import java.util.*; /** * User: anna */ public class InferenceSession { private static final Logger LOG = Logger.getInstance("#" + InferenceSession.class.getName()); private Map<PsiTypeParameter, InferenceVariable> myInferenceVariables = new LinkedHashMap<PsiTypeParameter, InferenceVariable>(); private final List<ConstraintFormula> myConstraints = new ArrayList<ConstraintFormula>(); private PsiSubstitutor mySiteSubstitutor; private PsiManager myManager; private int myConstraintIdx = 0; private final InferenceIncorporationPhase myIncorporationPhase = new InferenceIncorporationPhase(this); public InferenceSession(PsiSubstitutor siteSubstitutor) { mySiteSubstitutor = siteSubstitutor; } public InferenceSession(PsiTypeParameter[] typeParams, PsiType[] leftTypes, PsiType[] rightTypes, PsiSubstitutor siteSubstitutor, PsiManager manager) { myManager = manager; mySiteSubstitutor = siteSubstitutor; 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) { myConstraints.add(new TypeCompatibilityConstraint(leftTypes[i], rightType)); } } } public InferenceSession(PsiTypeParameter[] typeParams, PsiParameter[] parameters, PsiExpression[] args, PsiSubstitutor siteSubstitutor, PsiElement parent, PsiManager manager) { myManager = manager; mySiteSubstitutor = siteSubstitutor; initBounds(typeParams); final Pair<PsiMethod, PsiCallExpression> pair = getPair(parent); if (parameters.length > 0) { for (int i = 0; i < args.length; i++) { PsiType parameterType = getParameterType(parameters, args, i, mySiteSubstitutor); if (args[i] != null && (pair == null || isPertinentToApplicability(args[i], pair.first, mySiteSubstitutor, parameterType, this))) { myConstraints.add(new ExpressionCompatibilityConstraint(args[i], parameterType)); } } } if (pair != null) { initReturnTypeConstraint(pair.first, (PsiCallExpression)parent); } } private static Pair<PsiMethod, PsiCallExpression> getPair(PsiElement parent) { if (parent instanceof PsiCallExpression) { final Pair<PsiMethod, PsiSubstitutor> pair = MethodCandidateInfo.getCurrentMethod(((PsiCallExpression)parent).getArgumentList()); if (pair != null) { return Pair.create(pair.first, (PsiCallExpression)parent); } } return null; } private static boolean areLambdaParameterTypesKnown(PsiSubstitutor siteSubstitutor, PsiType targetType, @NotNull InferenceSession session) { final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(targetType); final PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); if (method != null) { final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(method, resolveResult); for (PsiParameter parameter : method.getParameterList().getParameters()) { if (!session.isProperType(siteSubstitutor.substitute(substitutor.substitute(parameter.getType())))) return false; } return true; } return false; } public static boolean isPertinentToApplicability(PsiExpression expr, PsiMethod method) { return isPertinentToApplicability(expr, method, PsiSubstitutor.EMPTY, null, null); } public static boolean isPertinentToApplicability(PsiExpression expr, PsiMethod method, PsiSubstitutor siteSubstitutor, @Nullable PsiType targetType, @Nullable InferenceSession session) { if (expr instanceof PsiLambdaExpression) { if (!((PsiLambdaExpression)expr).hasFormalParameterTypes() && (session == null || !areLambdaParameterTypesKnown(siteSubstitutor, targetType, session))) { return false; } for (PsiExpression expression : LambdaUtil.getReturnExpressions((PsiLambdaExpression)expr)) { if (!isPertinentToApplicability(expression, method, siteSubstitutor, targetType, session)) return false; } if (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.resolveClassInClassTypeOnly(paramType); if (psiClass instanceof PsiTypeParameter && ((PsiTypeParameter)psiClass).getOwner() == method) return false; } } for (PsiExpression expression : LambdaUtil.getReturnExpressions((PsiLambdaExpression)expr)) { if (PsiPolyExpressionUtil.isPolyExpression(expression)) { return false; } } } return true; } if (expr instanceof PsiMethodReferenceExpression) { return ((PsiMethodReferenceExpression)expr).isExact(); } if (expr instanceof PsiParenthesizedExpression) { return isPertinentToApplicability(((PsiParenthesizedExpression)expr).getExpression(), method, siteSubstitutor, targetType, session); } if (expr instanceof PsiConditionalExpression) { final PsiExpression thenExpression = ((PsiConditionalExpression)expr).getThenExpression(); if (!isPertinentToApplicability(thenExpression, method, siteSubstitutor, targetType, session)) return false; final PsiExpression elseExpression = ((PsiConditionalExpression)expr).getElseExpression(); if (!isPertinentToApplicability(elseExpression, method, siteSubstitutor, targetType, session)) return false; } return true; } private static PsiType getParameterType(PsiParameter[] parameters, PsiExpression[] args, int i, PsiSubstitutor substitutor) { PsiType parameterType = substitutor.substitute(parameters[i < parameters.length ? i : parameters.length - 1].getType()); if (parameterType instanceof PsiEllipsisType) { if (args.length != parameters.length || PsiPolyExpressionUtil.isPolyExpression(args[i]) || args[i] != null && !(args[i].getType() instanceof PsiArrayType)) { 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) { repeatInferencePhases(); mySiteSubstitutor = resolveBounds(myInferenceVariables.values(), mySiteSubstitutor, false); if (parameters != null && args != null) { final Set<ConstraintFormula> additionalConstraints = new HashSet<ConstraintFormula>(); if (parameters.length > 0) { final Pair<PsiMethod, PsiCallExpression> pair = getPair(parent); for (int i = 0; i < args.length; i++) { PsiType parameterType = getParameterType(parameters, args, i, mySiteSubstitutor); if (args[i] != null) { if (pair == null || !isPertinentToApplicability(args[i], pair.first, mySiteSubstitutor, parameterType, this) || !isProperType(LambdaUtil.getFunctionalInterfaceReturnType(parameterType))) { additionalConstraints.add(new ExpressionCompatibilityConstraint(args[i], parameterType)); } additionalConstraints.add(new CheckedExceptionCompatibilityConstraint(args[i], parameterType)); } } } if (!additionalConstraints.isEmpty()) { for (InferenceVariable inferenceVariable : myInferenceVariables.values()) { inferenceVariable.ignoreInstantiation(); } proceedWithAdditionalConstraints(additionalConstraints); } } mySiteSubstitutor = resolveBounds(myInferenceVariables.values(), mySiteSubstitutor, true); for (InferenceVariable inferenceVariable : myInferenceVariables.values()) { if (inferenceVariable.isCaptured()) continue; 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; } private void initBounds(PsiTypeParameter[] typeParameters) { for (PsiTypeParameter parameter : typeParameters) { myInferenceVariables.put(parameter, new InferenceVariable(parameter)); } for (InferenceVariable variable : myInferenceVariables.values()) { final PsiTypeParameter parameter = variable.getParameter(); boolean added = false; final PsiClassType[] extendsListTypes = parameter.getExtendsListTypes(); for (PsiType classType : extendsListTypes) { classType = 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); } } } public void addCapturedVariable(PsiTypeParameter param) { if (myInferenceVariables.containsKey(param)) return; //same method call final InferenceVariable variable = new InferenceVariable(param); variable.setCaptured(true); myInferenceVariables.put(param, variable); } private void initReturnTypeConstraint(PsiMethod method, PsiCallExpression context) { if (PsiPolyExpressionUtil.isMethodCallPolyExpression(context, method) || context instanceof PsiNewExpression && PsiDiamondType.ourDiamondGuard.currentStack().contains(context)) { final PsiType returnType = method.getReturnType(); if (!PsiType.VOID.equals(returnType) && returnType != null) { PsiType targetType = PsiTypesUtil.getExpectedTypeByParent(context); if (targetType == null) { final PsiElement parent = PsiUtil.skipParenthesizedExprUp(context.getParent()); if (parent instanceof PsiExpressionList) { final PsiElement gParent = parent.getParent(); if (gParent instanceof PsiCallExpression) { final PsiExpressionList argumentList = ((PsiCallExpression)gParent).getArgumentList(); if (argumentList != null) { final Pair<PsiMethod, PsiSubstitutor> pair = MethodCandidateInfo.getCurrentMethod(argumentList); final JavaResolveResult resolveResult = pair == null ? ((PsiCallExpression)gParent).resolveMethodGenerics() : null; final PsiElement parentMethod = pair != null ? pair.first : resolveResult.getElement(); if (parentMethod instanceof PsiMethod) { final PsiParameter[] parameters = ((PsiMethod)parentMethod).getParameterList().getParameters(); PsiElement arg = context; while (arg.getParent() instanceof PsiParenthesizedExpression) { arg = parent.getParent(); } final PsiExpression[] args = argumentList.getExpressions(); targetType = getParameterType(parameters, args, ArrayUtilRt.find(args, arg), pair != null ? pair.second : resolveResult.getSubstitutor()); } } } } else if (parent instanceof PsiConditionalExpression) { targetType = PsiTypesUtil.getExpectedTypeByParent((PsiExpression)parent); } else if (parent instanceof PsiLambdaExpression) { targetType = LambdaUtil.getFunctionalInterfaceReturnType(((PsiLambdaExpression)parent).getFunctionalInterfaceType()); } } if (targetType != null) { myConstraints.add(new TypeCompatibilityConstraint(GenericsUtil.eliminateWildcards(targetType, false), PsiImplUtil.normalizeWildcardTypeByPosition(returnType, context))); } } } } public InferenceVariable getInferenceVariable(PsiType psiType) { return getInferenceVariable(psiType, true); } public InferenceVariable getInferenceVariable(PsiType psiType, boolean acceptCaptured) { final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(psiType); if (psiClass instanceof PsiTypeParameter) { final InferenceVariable inferenceVariable = myInferenceVariables.get(psiClass); if (inferenceVariable != null && (acceptCaptured || !inferenceVariable.isCaptured())) { return inferenceVariable; } } return null; } public boolean isProperType(@Nullable PsiType type) { return isProperType(type, true); } public boolean isProperType(@Nullable PsiType type, boolean acceptCaptured) { return collectDependencies(type, null, acceptCaptured); } public boolean collectDependencies(@Nullable PsiType type, @Nullable final Set<InferenceVariable> dependencies, final boolean acceptCaptured) { 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, acceptCaptured); 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; } private boolean repeatInferencePhases() { do { if (!reduceConstraints()) { //inference error occurred return false; } myIncorporationPhase.incorporate(); } while (!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 PsiSubstitutor resolveBounds(final Collection<InferenceVariable> inferenceVariables, PsiSubstitutor substitutor, boolean acceptObject) { final List<List<InferenceVariable>> independentVars = InferenceVariablesOrder.resolveOrder(inferenceVariables, this); for (List<InferenceVariable> variables : independentVars) { for (InferenceVariable inferenceVariable : variables) { if (inferenceVariable.isCaptured() || inferenceVariable.getInstantiation() != PsiType.NULL) continue; final PsiTypeParameter typeParameter = inferenceVariable.getParameter(); try { final List<PsiType> eqBounds = inferenceVariable.getBounds(InferenceBound.EQ); final List<PsiType> lowerBounds = inferenceVariable.getBounds(InferenceBound.LOWER); final List<PsiType> upperBounds = inferenceVariable.getBounds(InferenceBound.UPPER); if (/*eqBounds.contains(null) || lowerBounds.contains(null) || */upperBounds.contains(null)) { inferenceVariable.setInstantiation(null); continue; } PsiType bound = null; for (PsiType eqBound : eqBounds) { if (eqBound == null) continue; bound = acceptBoundsWithRecursiveDependencies(typeParameter, eqBound, substitutor); if (bound != null) break; } if (bound != null) { if (bound instanceof PsiCapturedWildcardType && eqBounds.size() > 1) { continue; } inferenceVariable.setInstantiation(bound); } else { PsiType lub = null; for (PsiType lowerBound : lowerBounds) { lowerBound = acceptBoundsWithRecursiveDependencies(typeParameter, lowerBound, substitutor); if (isProperType(lowerBound, false)) { if (lub == null) { lub = lowerBound; } else { lub = GenericsUtil.getLeastUpperBound(lub, lowerBound, myManager); } } } if (lub != null) { inferenceVariable.setInstantiation(lub instanceof PsiCapturedWildcardType ? ((PsiCapturedWildcardType)lub).getWildcard() : lub); } else if (acceptObject || upperBounds.size() > 1 || !upperBounds.get(0).equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { PsiType glb = null; for (PsiType upperBound : upperBounds) { upperBound = acceptBoundsWithRecursiveDependencies(typeParameter, upperBound, substitutor); if (isProperType(upperBound, false)) { if (glb == null) { glb = upperBound; } else { glb = GenericsUtil.getGreatestLowerBound(glb, upperBound); } } } if (glb != null) { inferenceVariable.setInstantiation(glb); } } } } finally { final PsiType instantiation = inferenceVariable.getInstantiation(); if (instantiation != PsiType.NULL) { substitutor = substitutor.put(typeParameter, instantiation); } } } } return substitutor; } private PsiType acceptBoundsWithRecursiveDependencies(PsiTypeParameter typeParameter, PsiType bound, PsiSubstitutor substitutor) { if (!isProperType(bound)) { final PsiSubstitutor subst = PsiUtil.resolveClassInType(bound) != typeParameter ? substitutor.put(typeParameter, null) : substitutor; return subst.substitute(bound); } return bound; } public PsiManager getManager() { return myManager; } public GlobalSearchScope getScope() { return GlobalSearchScope.allScope(myManager.getProject()); } public Collection<InferenceVariable> getInferenceVariables() { return myInferenceVariables.values(); } public void addConstraint(ConstraintFormula constraint) { if (!myConstraints.contains(constraint)) { myConstraints.add(constraint); } } public Collection<PsiTypeParameter> getTypeParams() { return myInferenceVariables.keySet(); } public void addVariable(PsiTypeParameter typeParameter, final PsiType parameter) { InferenceVariable variable = new InferenceVariable(typeParameter); if (parameter instanceof PsiWildcardType) { PsiType bound = ((PsiWildcardType)parameter).getBound(); if (bound != null) { variable.addBound(bound, ((PsiWildcardType)parameter).isExtends() ? InferenceBound.UPPER : InferenceBound.LOWER); } else { variable.addBound(PsiType.getJavaLangObject(typeParameter.getManager(), parameter.getResolveScope()), InferenceBound.UPPER); } } else { variable.addBound(parameter, InferenceBound.EQ); } myInferenceVariables.put(typeParameter, variable); } private boolean proceedWithAdditionalConstraints(Set<ConstraintFormula> additionalConstraints) { while (!additionalConstraints.isEmpty()) { final Set<InferenceVariable> outputVariables = new HashSet<InferenceVariable>(); for (ConstraintFormula constraint : additionalConstraints) { if (constraint instanceof InputOutputConstraintFormula) { final Set<InferenceVariable> outputVars = ((InputOutputConstraintFormula)constraint).getOutputVariables(((InputOutputConstraintFormula)constraint).getInputVariables(this), this); if (outputVars != null) { outputVariables.addAll(outputVars); } } } Set<ConstraintFormula> subset = new HashSet<ConstraintFormula>(); final Set<InferenceVariable> varsToResolve = new HashSet<InferenceVariable>(); 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) { final Set<InferenceVariable> dependencies = inputVariable.getDependencies(this); dependencies.add(inputVariable); dependencies.retainAll(outputVariables); if (!dependencies.isEmpty()) { dependsOnOutput = true; break; } } if (!dependsOnOutput) { subset.add(constraint); varsToResolve.addAll(inputVariables); } } } else { subset.add(constraint); } } if (subset.isEmpty()) { subset = Collections.singleton(additionalConstraints.iterator().next()); //todo choose one constraint } additionalConstraints.removeAll(subset); myConstraints.addAll(subset); if (!repeatInferencePhases()) { return false; } mySiteSubstitutor = resolveBounds(varsToResolve, mySiteSubstitutor, true); for (ConstraintFormula additionalConstraint : additionalConstraints) { additionalConstraint.apply(mySiteSubstitutor); } } return true; } }