/* * Copyright 2000-2014 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.infos; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.openapi.projectRoots.JavaVersionService; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.RecursionGuard; import com.intellij.openapi.util.RecursionManager; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; import com.intellij.psi.impl.source.resolve.ParameterTypeInferencePolicy; import com.intellij.psi.util.PsiUtil; import com.intellij.util.containers.ConcurrentWeakHashMap; import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; /** * @author ik, dsl */ public class MethodCandidateInfo extends CandidateInfo{ public static final RecursionGuard ourOverloadGuard = RecursionManager.createGuard("overload.guard"); public static final ThreadLocal> CURRENT_CANDIDATE = new ThreadLocal>(); @ApplicabilityLevelConstant private int myApplicabilityLevel; // benign race @ApplicabilityLevelConstant private int myPertinentApplicabilityLevel; private final PsiElement myArgumentList; private final PsiType[] myArgumentTypes; private final PsiType[] myTypeArguments; private PsiSubstitutor myCalcedSubstitutor; // benign race private final LanguageLevel myLanguageLevel; public MethodCandidateInfo(@NotNull PsiElement candidate, PsiSubstitutor substitutor, boolean accessProblem, boolean staticsProblem, PsiElement argumentList, PsiElement currFileContext, @Nullable PsiType[] argumentTypes, PsiType[] typeArguments) { this(candidate, substitutor, accessProblem, staticsProblem, argumentList, currFileContext, argumentTypes, typeArguments, PsiUtil.getLanguageLevel(argumentList)); } public MethodCandidateInfo(@NotNull PsiElement candidate, @NotNull PsiSubstitutor substitutor, boolean accessProblem, boolean staticsProblem, PsiElement argumentList, PsiElement currFileContext, @Nullable PsiType[] argumentTypes, PsiType[] typeArguments, @NotNull LanguageLevel languageLevel) { super(candidate, substitutor, accessProblem, staticsProblem, currFileContext); myArgumentList = argumentList; myArgumentTypes = argumentTypes; myTypeArguments = typeArguments; myLanguageLevel = languageLevel; } public boolean isVarargs() { return false; } public boolean isApplicable(){ return getApplicabilityLevel() != ApplicabilityLevel.NOT_APPLICABLE; } @ApplicabilityLevelConstant private int getApplicabilityLevelInner() { final PsiType[] argumentTypes = getArgumentTypes(); if (argumentTypes == null) return ApplicabilityLevel.NOT_APPLICABLE; int level = PsiUtil.getApplicabilityLevel(getElement(), getSubstitutor(), argumentTypes, myLanguageLevel); if (level > ApplicabilityLevel.NOT_APPLICABLE && !isTypeArgumentsApplicable()) level = ApplicabilityLevel.NOT_APPLICABLE; return level; } @ApplicabilityLevelConstant public int getApplicabilityLevel() { if(myApplicabilityLevel == 0){ myApplicabilityLevel = getApplicabilityLevelInner(); } return myApplicabilityLevel; } @ApplicabilityLevelConstant public int getPertinentApplicabilityLevel() { if (myPertinentApplicabilityLevel == 0) { myPertinentApplicabilityLevel = getPertinentApplicabilityLevelInner(); } return myPertinentApplicabilityLevel; } public int getPertinentApplicabilityLevelInner() { if (myArgumentList == null || !PsiUtil.isLanguageLevel8OrHigher(myArgumentList)) { return getApplicabilityLevel(); } @ApplicabilityLevelConstant int level; Integer boxedLevel = ourOverloadGuard.doPreventingRecursion(myArgumentList, false, new Computable() { @Override public Integer compute() { if (PsiUtil.isLanguageLevel8OrHigher(myArgumentList)) { PsiSubstitutor substitutor = getSubstitutor(false); Map map = CURRENT_CANDIDATE.get(); if (map == null) { map = new ConcurrentWeakHashMap(); CURRENT_CANDIDATE.set(map); } final PsiMethod method = getElement(); final CurrentCandidateProperties properties = new CurrentCandidateProperties(method, substitutor, isVarargs(), true); final CurrentCandidateProperties alreadyThere = map.put(getMarkerList(), properties); try { properties.setSubstitutor(substitutor); PsiType[] argumentTypes = getArgumentTypes(); if (argumentTypes == null) { return ApplicabilityLevel.NOT_APPLICABLE; } final int applicabilityLevel = PsiUtil.getApplicabilityLevel(method, substitutor, argumentTypes, myLanguageLevel); if (!isVarargs() && applicabilityLevel < ApplicabilityLevel.FIXED_ARITY) { return ApplicabilityLevel.NOT_APPLICABLE; } return applicabilityLevel; } finally { if (alreadyThere == null) map.remove(getMarkerList()); } } return getApplicabilityLevelInner(); } }); if (boxedLevel == null) { return getApplicabilityLevel(); } level = boxedLevel; if (level > ApplicabilityLevel.NOT_APPLICABLE && !isTypeArgumentsApplicable(false)) level = ApplicabilityLevel.NOT_APPLICABLE; return level; } @NotNull public PsiSubstitutor getSiteSubstitutor() { PsiSubstitutor incompleteSubstitutor = super.getSubstitutor(); if (myTypeArguments != null) { PsiMethod method = getElement(); PsiTypeParameter[] typeParams = method.getTypeParameters(); for (int i = 0; i < myTypeArguments.length && i < typeParams.length; i++) { incompleteSubstitutor = incompleteSubstitutor.put(typeParams[i], myTypeArguments[i]); } } return incompleteSubstitutor; } @NotNull @Override public PsiSubstitutor getSubstitutor() { return getSubstitutor(true); } @NotNull public PsiSubstitutor getSubstitutor(boolean includeReturnConstraint) { PsiSubstitutor substitutor = myCalcedSubstitutor; if (substitutor == null || !includeReturnConstraint && myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8) || isOverloadCheck()) { PsiSubstitutor incompleteSubstitutor = super.getSubstitutor(); PsiMethod method = getElement(); if (myTypeArguments == null) { final RecursionGuard.StackStamp stackStamp = PsiDiamondType.ourDiamondGuard.markStack(); final PsiSubstitutor inferredSubstitutor = inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE, includeReturnConstraint); if (!stackStamp.mayCacheNow() || isOverloadCheck() || !includeReturnConstraint && myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8) || getMarkerList() != null && PsiResolveHelper.ourGraphGuard.currentStack().contains(getMarkerList().getParent())) { return inferredSubstitutor; } myCalcedSubstitutor = substitutor = inferredSubstitutor; } else { PsiTypeParameter[] typeParams = method.getTypeParameters(); for (int i = 0; i < myTypeArguments.length && i < typeParams.length; i++) { incompleteSubstitutor = incompleteSubstitutor.put(typeParams[i], myTypeArguments[i]); } myCalcedSubstitutor = substitutor = incompleteSubstitutor; } } return substitutor; } public static boolean isOverloadCheck() { return !ourOverloadGuard.currentStack().isEmpty(); } public boolean isTypeArgumentsApplicable() { return isTypeArgumentsApplicable(false); } public boolean isTypeArgumentsApplicable(boolean includeReturnConstraint) { final PsiMethod psiMethod = getElement(); PsiTypeParameter[] typeParams = psiMethod.getTypeParameters(); if (myTypeArguments != null && typeParams.length != myTypeArguments.length && !PsiUtil.isLanguageLevel7OrHigher(psiMethod)){ return typeParams.length == 0 && JavaVersionService.getInstance().isAtLeast(psiMethod, JavaSdkVersion.JDK_1_7); } PsiSubstitutor substitutor = getSubstitutor(includeReturnConstraint); return GenericsUtil.isTypeArgumentsApplicable(typeParams, substitutor, getParent()); } protected PsiElement getParent() { return myArgumentList != null ? myArgumentList.getParent() : null; } @Override public boolean isValidResult(){ return super.isValidResult() && isApplicable(); } @NotNull @Override public PsiMethod getElement(){ return (PsiMethod)super.getElement(); } @NotNull public PsiSubstitutor inferTypeArguments(@NotNull ParameterTypeInferencePolicy policy, boolean includeReturnConstraint) { return inferTypeArguments(policy, myArgumentList instanceof PsiExpressionList ? ((PsiExpressionList)myArgumentList).getExpressions() : PsiExpression.EMPTY_ARRAY, includeReturnConstraint); } public PsiSubstitutor inferSubstitutorFromArgs(@NotNull ParameterTypeInferencePolicy policy, final PsiExpression[] arguments) { if (myTypeArguments == null) { return inferTypeArguments(policy, arguments, true); } else { return getSiteSubstitutor(); } } @NotNull public PsiSubstitutor inferTypeArguments(@NotNull ParameterTypeInferencePolicy policy, @NotNull PsiExpression[] arguments, boolean includeReturnConstraint) { Map map = CURRENT_CANDIDATE.get(); if (map == null) { map = new ConcurrentWeakHashMap(); CURRENT_CANDIDATE.set(map); } final PsiMethod method = getElement(); final CurrentCandidateProperties alreadyThere = map.get(getMarkerList()); if (alreadyThere == null) { map.put(getMarkerList(), new CurrentCandidateProperties(method, super.getSubstitutor(), isVarargs(), !includeReturnConstraint)); } try { PsiTypeParameter[] typeParameters = method.getTypeParameters(); if (!method.hasModifierProperty(PsiModifier.STATIC)) { final PsiClass containingClass = method.getContainingClass(); if (containingClass != null && PsiUtil.isRawSubstitutor(containingClass, mySubstitutor)) { Project project = containingClass.getProject(); JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project); return javaPsiFacade.getElementFactory().createRawSubstitutor(mySubstitutor, typeParameters); } } final PsiElement parent = getParent(); if (parent == null) return PsiSubstitutor.EMPTY; Project project = method.getProject(); JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project); return javaPsiFacade.getResolveHelper() .inferTypeArguments(typeParameters, method.getParameterList().getParameters(), arguments, mySubstitutor, parent, policy, myLanguageLevel); } finally { if (alreadyThere == null) map.remove(getMarkerList()); } } protected PsiElement getMarkerList() { return myArgumentList; } public boolean isInferencePossible() { return myArgumentList != null && myArgumentList.isValid(); } public static CurrentCandidateProperties getCurrentMethod(PsiElement context) { final Map currentMethodCandidates = CURRENT_CANDIDATE.get(); return currentMethodCandidates != null ? currentMethodCandidates.get(context) : null; } public static void updateSubstitutor(PsiElement context, PsiSubstitutor newSubstitutor) { final Map currentMethodCandidates = CURRENT_CANDIDATE.get(); if (currentMethodCandidates != null) { final CurrentCandidateProperties properties = currentMethodCandidates.get(context); if (properties != null) { properties.setSubstitutor(newSubstitutor); } } } public PsiType[] getArgumentTypes() { return myArgumentTypes; } @Override public boolean equals(Object o) { return super.equals(o) && isVarargs() == ((MethodCandidateInfo)o).isVarargs(); } @Override public int hashCode() { return 31 * super.hashCode() + (isVarargs() ? 1 : 0); } public static class CurrentCandidateProperties { private final PsiMethod myMethod; private PsiSubstitutor mySubstitutor; private boolean myVarargs; private boolean myApplicabilityCheck; public CurrentCandidateProperties(PsiMethod method, PsiSubstitutor substitutor, boolean varargs, boolean applicabilityCheck) { myMethod = method; mySubstitutor = substitutor; myVarargs = varargs; myApplicabilityCheck = applicabilityCheck; } public PsiMethod getMethod() { return myMethod; } public PsiSubstitutor getSubstitutor() { return mySubstitutor; } public void setSubstitutor(PsiSubstitutor substitutor) { mySubstitutor = substitutor; } public boolean isVarargs() { return myVarargs; } public void setVarargs(boolean varargs) { myVarargs = varargs; } public boolean isApplicabilityCheck() { return myApplicabilityCheck; } public void setApplicabilityCheck(boolean applicabilityCheck) { myApplicabilityCheck = applicabilityCheck; } } public static class ApplicabilityLevel { public static final int NOT_APPLICABLE = 1; public static final int VARARGS = 2; public static final int FIXED_ARITY = 3; } @MagicConstant(intValues = {ApplicabilityLevel.NOT_APPLICABLE, ApplicabilityLevel.VARARGS, ApplicabilityLevel.FIXED_ARITY}) public @interface ApplicabilityLevelConstant { } }