| /* |
| * 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.scope.conflictResolvers; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.projectRoots.JavaSdkVersion; |
| import com.intellij.openapi.projectRoots.JavaVersionService; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiSuperMethodImplUtil; |
| import com.intellij.psi.infos.CandidateInfo; |
| import com.intellij.psi.infos.MethodCandidateInfo; |
| import com.intellij.psi.scope.PsiConflictResolver; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.util.*; |
| import com.intellij.util.containers.HashSet; |
| import gnu.trove.THashMap; |
| import gnu.trove.THashSet; |
| import gnu.trove.TIntArrayList; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Created by IntelliJ IDEA. |
| * User: ik |
| * Date: 10.06.2003 |
| * Time: 19:41:51 |
| * To change this template use Options | File Templates. |
| */ |
| public class JavaMethodsConflictResolver implements PsiConflictResolver{ |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.scope.conflictResolvers.JavaMethodsConflictResolver"); |
| |
| private final PsiElement myArgumentsList; |
| private PsiType[] myActualParameterTypes; |
| protected LanguageLevel myLanguageLevel; |
| |
| public JavaMethodsConflictResolver(@NotNull PsiExpressionList list, @NotNull LanguageLevel languageLevel) { |
| this(list, null, languageLevel); |
| } |
| |
| public JavaMethodsConflictResolver(@NotNull PsiElement argumentsList, |
| PsiType[] actualParameterTypes, |
| @NotNull LanguageLevel languageLevel) { |
| myArgumentsList = argumentsList; |
| myActualParameterTypes = actualParameterTypes; |
| myLanguageLevel = languageLevel; |
| } |
| |
| @Override |
| public CandidateInfo resolveConflict(@NotNull List<CandidateInfo> conflicts){ |
| if (conflicts.isEmpty()) return null; |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| boolean atLeastOneMatch = checkParametersNumber(conflicts, getActualParameterTypes().length, true); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| checkSameSignatures(conflicts); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| checkAccessStaticLevels(conflicts, true); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| checkParametersNumber(conflicts, getActualParameterTypes().length, false); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| final int applicabilityLevel = checkApplicability(conflicts); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| // makes no sense to do further checks, because if no one candidate matches by parameters count |
| // then noone can be more specific |
| if (!atLeastOneMatch) return null; |
| |
| checkLambdaApplicable(conflicts, myLanguageLevel); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| checkSpecifics(conflicts, applicabilityLevel, myLanguageLevel); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| checkPrimitiveVarargs(conflicts, getActualParameterTypes().length); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| checkAccessStaticLevels(conflicts, false); |
| if (conflicts.size() == 1) return conflicts.get(0); |
| |
| Set<CandidateInfo> uniques = new THashSet<CandidateInfo>(conflicts); |
| if (uniques.size() == 1) return uniques.iterator().next(); |
| return null; |
| } |
| |
| private void checkLambdaApplicable(@NotNull List<CandidateInfo> conflicts, @NotNull LanguageLevel languageLevel) { |
| if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) return; |
| for (int i = 0; i < getActualParameterTypes().length; i++) { |
| PsiType parameterType = getActualParameterTypes()[i]; |
| if (parameterType instanceof PsiLambdaExpressionType) { |
| final PsiLambdaExpression lambdaExpression = ((PsiLambdaExpressionType)parameterType).getExpression(); |
| for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext(); ) { |
| ProgressManager.checkCanceled(); |
| final CandidateInfo conflict = iterator.next(); |
| final PsiMethod method = (PsiMethod)conflict.getElement(); |
| if (method != null) { |
| final PsiParameter[] methodParameters = method.getParameterList().getParameters(); |
| if (methodParameters.length == 0) continue; |
| final PsiParameter param = i < methodParameters.length ? methodParameters[i] : methodParameters[methodParameters.length - 1]; |
| final PsiType paramType = param.getType(); |
| if (!LambdaUtil.isAcceptable(lambdaExpression, conflict.getSubstitutor().substitute(paramType), lambdaExpression.hasFormalParameterTypes())) { |
| iterator.remove(); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public void checkSpecifics(@NotNull List<CandidateInfo> conflicts, |
| @MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel, |
| @NotNull LanguageLevel languageLevel) { |
| final boolean applicable = applicabilityLevel > MethodCandidateInfo.ApplicabilityLevel.NOT_APPLICABLE; |
| |
| int conflictsCount = conflicts.size(); |
| // Specifics |
| if (applicable) { |
| final CandidateInfo[] newConflictsArray = conflicts.toArray(new CandidateInfo[conflicts.size()]); |
| for (int i = 1; i < conflictsCount; i++) { |
| final CandidateInfo method = newConflictsArray[i]; |
| for (int j = 0; j < i; j++) { |
| ProgressManager.checkCanceled(); |
| final CandidateInfo conflict = newConflictsArray[j]; |
| if (nonComparable(method, conflict)) continue; |
| switch (isMoreSpecific(method, conflict, applicabilityLevel, languageLevel)) { |
| case FIRST: |
| conflicts.remove(conflict); |
| break; |
| case SECOND: |
| conflicts.remove(method); |
| break; |
| case NEITHER: |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| protected boolean nonComparable(CandidateInfo method, CandidateInfo conflict) { |
| assert method != conflict; |
| return false; |
| } |
| |
| protected static void checkAccessStaticLevels(List<CandidateInfo> conflicts, boolean checkAccessible) { |
| int conflictsCount = conflicts.size(); |
| |
| int maxCheckLevel = -1; |
| int[] checkLevels = new int[conflictsCount]; |
| int index = 0; |
| for (final CandidateInfo conflict : conflicts) { |
| ProgressManager.checkCanceled(); |
| final MethodCandidateInfo method = (MethodCandidateInfo)conflict; |
| final int level = checkAccessible ? getCheckAccessLevel(method) : getCheckStaticLevel(method); |
| checkLevels[index++] = level; |
| maxCheckLevel = Math.max(maxCheckLevel, level); |
| } |
| |
| for (int i = conflictsCount - 1; i >= 0; i--) { |
| // check for level |
| if (checkLevels[i] < maxCheckLevel) { |
| conflicts.remove(i); |
| } |
| } |
| } |
| |
| protected void checkSameSignatures(@NotNull List<CandidateInfo> conflicts) { |
| // candidates should go in order of class hierarchy traversal |
| // in order for this to work |
| Map<MethodSignature, CandidateInfo> signatures = new THashMap<MethodSignature, CandidateInfo>(conflicts.size()); |
| Set<PsiMethod> superMethods = new HashSet<PsiMethod>(); |
| for (CandidateInfo conflict : conflicts) { |
| final PsiMethod method = ((MethodCandidateInfo)conflict).getElement(); |
| for (HierarchicalMethodSignature methodSignature : method.getHierarchicalMethodSignature().getSuperSignatures()) { |
| final PsiMethod superMethod = methodSignature.getMethod(); |
| if (!CommonClassNames.JAVA_LANG_OBJECT.equals(superMethod.getContainingClass().getQualifiedName())) { |
| superMethods.add(superMethod); |
| } |
| } |
| } |
| nextConflict: |
| for (int i=0; i<conflicts.size();i++) { |
| ProgressManager.checkCanceled(); |
| CandidateInfo info = conflicts.get(i); |
| PsiMethod method = (PsiMethod)info.getElement(); |
| assert method != null; |
| |
| if (!method.hasModifierProperty(PsiModifier.STATIC) && superMethods.contains(method)) { |
| conflicts.remove(i); |
| i--; |
| continue; |
| } |
| |
| PsiClass class1 = method.getContainingClass(); |
| PsiSubstitutor infoSubstitutor = info.getSubstitutor(); |
| MethodSignature signature = method.getSignature(infoSubstitutor); |
| CandidateInfo existing = signatures.get(signature); |
| |
| if (existing == null) { |
| signatures.put(signature, info); |
| continue; |
| } |
| PsiMethod existingMethod = (PsiMethod)existing.getElement(); |
| assert existingMethod != null; |
| PsiClass existingClass = existingMethod.getContainingClass(); |
| if (class1 != null && existingClass != null && |
| class1.isInterface() && CommonClassNames.JAVA_LANG_OBJECT.equals(existingClass.getQualifiedName())) { //prefer interface methods to methods from Object |
| signatures.put(signature, info); |
| continue; |
| } |
| if (method == existingMethod) { |
| PsiElement scope1 = info.getCurrentFileResolveScope(); |
| PsiElement scope2 = existing.getCurrentFileResolveScope(); |
| if (scope1 instanceof PsiClass && |
| scope2 instanceof PsiClass && |
| PsiTreeUtil.isAncestor(scope1, scope2, true) && |
| !existing.isAccessible()) { //prefer methods from outer class to inaccessible base class methods |
| signatures.put(signature, info); |
| continue; |
| } |
| } |
| |
| // filter out methods with incorrect inferred bounds (for unrelated methods only) |
| boolean existingTypeParamAgree = areTypeParametersAgree(existing); |
| boolean infoTypeParamAgree = areTypeParametersAgree(info); |
| if (existingTypeParamAgree && !infoTypeParamAgree && !PsiSuperMethodImplUtil.isSuperMethodSmart(method, existingMethod)) { |
| conflicts.remove(i); |
| i--; |
| continue; |
| } |
| if (!existingTypeParamAgree && infoTypeParamAgree && !PsiSuperMethodImplUtil.isSuperMethodSmart(existingMethod, method)) { |
| signatures.put(signature, info); |
| int index = conflicts.indexOf(existing); |
| conflicts.remove(index); |
| i--; |
| continue; |
| } |
| |
| if (InheritanceUtil.isInheritorOrSelf(class1, existingClass, true) || |
| InheritanceUtil.isInheritorOrSelf(existingClass, class1, true)) { |
| PsiParameter[] parameters = method.getParameterList().getParameters(); |
| final PsiParameter[] existingParameters = existingMethod.getParameterList().getParameters(); |
| for (int i1 = 0, parametersLength = parameters.length; i1 < parametersLength; i1++) { |
| if (parameters[i1].getType() instanceof PsiArrayType && |
| !(existingParameters[i1].getType() instanceof PsiArrayType)) {//prefer more specific type |
| signatures.put(signature, info); |
| continue nextConflict; |
| } |
| } |
| PsiType returnType1 = method.getReturnType(); |
| PsiType returnType2 = existingMethod.getReturnType(); |
| if (returnType1 != null && returnType2 != null) { |
| returnType1 = infoSubstitutor.substitute(returnType1); |
| returnType2 = existing.getSubstitutor().substitute(returnType2); |
| if (!returnType1.equals(returnType2) && returnType1.isAssignableFrom(returnType2)) { |
| conflicts.remove(i); |
| i--; |
| continue; |
| } |
| } |
| |
| // prefer derived class |
| signatures.put(signature, info); |
| } else { |
| final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(myArgumentsList, PsiMethodCallExpression.class); |
| if (methodCallExpression != null) { |
| final PsiReferenceExpression expression = methodCallExpression.getMethodExpression(); |
| final PsiExpression qualifierExpression = expression.getQualifierExpression(); |
| PsiClass currentClass; |
| if (qualifierExpression != null) { |
| currentClass = PsiUtil.resolveClassInClassTypeOnly(qualifierExpression.getType()); |
| } else { |
| currentClass = PsiTreeUtil.getParentOfType(expression, PsiClass.class); |
| } |
| |
| if (currentClass != null && InheritanceUtil.isInheritorOrSelf(currentClass, class1, true) && InheritanceUtil.isInheritorOrSelf(currentClass, existingClass, true)) { |
| final PsiSubstitutor eSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(existingClass, currentClass, PsiSubstitutor.EMPTY); |
| final PsiSubstitutor cSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(class1, currentClass, PsiSubstitutor.EMPTY); |
| if (MethodSignatureUtil.areSignaturesEqual(existingMethod.getSignature(eSubstitutor), method.getSignature(cSubstitutor))) { |
| final PsiType returnType = eSubstitutor.substitute(existingMethod.getReturnType()); |
| final PsiType returnType1 = cSubstitutor.substitute(method.getReturnType()); |
| if (returnType != null && returnType1 != null && !returnType1.equals(returnType)) { |
| if (TypeConversionUtil.isAssignable(returnType, returnType1, false)) { |
| if (class1.isInterface() && !existingClass.isInterface()) continue; |
| conflicts.remove(existing); |
| } else { |
| if (!TypeConversionUtil.isAssignable(returnType1, returnType, false)) continue; |
| conflicts.remove(i); |
| } |
| i--; |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static boolean areTypeParametersAgree(CandidateInfo info) { |
| return ((MethodCandidateInfo)info).isApplicable(); |
| } |
| |
| private static boolean checkParametersNumber(final List<CandidateInfo> conflicts, |
| final int argumentsCount, |
| boolean ignoreIfStaticsProblem) { |
| boolean atLeastOneMatch = false; |
| TIntArrayList unmatchedIndices = null; |
| for (int i = 0; i < conflicts.size(); i++) { |
| ProgressManager.checkCanceled(); |
| CandidateInfo info = conflicts.get(i); |
| if (ignoreIfStaticsProblem && !info.isStaticsScopeCorrect()) return true; |
| if (!(info instanceof MethodCandidateInfo)) continue; |
| PsiMethod method = ((MethodCandidateInfo)info).getElement(); |
| if (method.isVarArgs()) return true; |
| if (method.getParameterList().getParametersCount() == argumentsCount) { |
| // remove all unmatched before |
| if (unmatchedIndices != null) { |
| for (int u=unmatchedIndices.size()-1; u>=0; u--) { |
| int index = unmatchedIndices.get(u); |
| conflicts.remove(index); |
| i--; |
| } |
| unmatchedIndices = null; |
| } |
| atLeastOneMatch = true; |
| } |
| else if (atLeastOneMatch) { |
| conflicts.remove(i); |
| i--; |
| } |
| else { |
| if (unmatchedIndices == null) unmatchedIndices = new TIntArrayList(conflicts.size()-i); |
| unmatchedIndices.add(i); |
| } |
| } |
| |
| return atLeastOneMatch; |
| } |
| |
| @MethodCandidateInfo.ApplicabilityLevelConstant |
| private static int checkApplicability(List<CandidateInfo> conflicts) { |
| @MethodCandidateInfo.ApplicabilityLevelConstant int maxApplicabilityLevel = 0; |
| boolean toFilter = false; |
| for (CandidateInfo conflict : conflicts) { |
| ProgressManager.checkCanceled(); |
| @MethodCandidateInfo.ApplicabilityLevelConstant final int level = preferVarargs((MethodCandidateInfo)conflict); |
| if (maxApplicabilityLevel > 0 && maxApplicabilityLevel != level) { |
| toFilter = true; |
| } |
| if (level > maxApplicabilityLevel) { |
| maxApplicabilityLevel = level; |
| } |
| } |
| |
| if (toFilter) { |
| for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext();) { |
| ProgressManager.checkCanceled(); |
| CandidateInfo info = iterator.next(); |
| final int level = preferVarargs((MethodCandidateInfo)info); |
| if (level < maxApplicabilityLevel) { |
| iterator.remove(); |
| } |
| } |
| } |
| |
| return maxApplicabilityLevel; |
| } |
| |
| private static int preferVarargs(MethodCandidateInfo info) { |
| final int level = info.getPertinentApplicabilityLevel(); |
| if (level == MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY) { |
| final PsiMethod psiMethod = info.getElement(); |
| if (psiMethod != null && psiMethod.isVarArgs() && JavaVersionService.getInstance().isAtLeast(psiMethod, JavaSdkVersion.JDK_1_7)) { |
| return level + 1; |
| } |
| } |
| return level; |
| } |
| |
| private static int getCheckAccessLevel(MethodCandidateInfo method){ |
| boolean visible = method.isAccessible(); |
| return visible ? 1 : 0; |
| } |
| |
| private static int getCheckStaticLevel(MethodCandidateInfo method){ |
| boolean available = method.isStaticsScopeCorrect(); |
| return (available ? 1 : 0) << 1 | |
| (method.getCurrentFileResolveScope() instanceof PsiImportStaticStatement ? 0 : 1); |
| } |
| |
| private PsiType[] getActualParameterTypes() { |
| if (myActualParameterTypes == null) { |
| LOG.assertTrue(myArgumentsList instanceof PsiExpressionList, myArgumentsList); |
| myActualParameterTypes = ((PsiExpressionList)myArgumentsList).getExpressionTypes(); |
| } |
| return myActualParameterTypes; |
| } |
| |
| private enum Specifics { |
| FIRST, |
| SECOND, |
| NEITHER |
| } |
| |
| private static boolean isBoxingHappened(PsiType argType, PsiType parameterType, @NotNull LanguageLevel languageLevel) { |
| if (argType == null) return parameterType instanceof PsiPrimitiveType; |
| if (parameterType instanceof PsiClassType) { |
| parameterType = ((PsiClassType)parameterType).setLanguageLevel(languageLevel); |
| } |
| |
| return TypeConversionUtil.boxingConversionApplicable(parameterType, argType); |
| } |
| |
| private Specifics isMoreSpecific(final CandidateInfo info1, |
| final CandidateInfo info2, |
| @MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel, |
| @NotNull LanguageLevel languageLevel) { |
| PsiMethod method1 = (PsiMethod)info1.getElement(); |
| PsiMethod method2 = (PsiMethod)info2.getElement(); |
| final PsiClass class1 = method1.getContainingClass(); |
| final PsiClass class2 = method2.getContainingClass(); |
| |
| final PsiParameter[] params1 = method1.getParameterList().getParameters(); |
| final PsiParameter[] params2 = method2.getParameterList().getParameters(); |
| |
| final PsiTypeParameter[] typeParameters1 = method1.getTypeParameters(); |
| final PsiTypeParameter[] typeParameters2 = method2.getTypeParameters(); |
| final PsiSubstitutor classSubstitutor1 = info1.getSubstitutor(); //substitutions for method type parameters will be ignored |
| final PsiSubstitutor classSubstitutor2 = info2.getSubstitutor(); |
| |
| final int max = Math.max(params1.length, params2.length); |
| PsiType[] types1 = new PsiType[max]; |
| PsiType[] types2 = new PsiType[max]; |
| final boolean varargsPosition = applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.VARARGS; |
| for (int i = 0; i < max; i++) { |
| ProgressManager.checkCanceled(); |
| PsiType type1 = params1.length > 0 ? params1[Math.min(i, params1.length - 1)].getType() : null; |
| PsiType type2 = params2.length > 0 ? params2[Math.min(i, params2.length - 1)].getType() : null; |
| if (varargsPosition) { |
| if (type1 instanceof PsiEllipsisType && type2 instanceof PsiEllipsisType && |
| params1.length == params2.length && |
| (!JavaVersionService.getInstance().isAtLeast(class1, JavaSdkVersion.JDK_1_7) || ((PsiArrayType)type1).getComponentType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || ((PsiArrayType)type2).getComponentType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT))) { |
| type1 = ((PsiEllipsisType)type1).toArrayType(); |
| type2 = ((PsiEllipsisType)type2).toArrayType(); |
| } |
| else { |
| type1 = type1 instanceof PsiEllipsisType ? ((PsiArrayType)type1).getComponentType() : type1; |
| type2 = type2 instanceof PsiEllipsisType ? ((PsiArrayType)type2).getComponentType() : type2; |
| } |
| } |
| |
| types1[i] = type1; |
| types2[i] = type2; |
| } |
| |
| boolean sameBoxing = true; |
| int[] boxingHappened = new int[2]; |
| for (int i = 0; i < types1.length; i++) { |
| ProgressManager.checkCanceled(); |
| PsiType type1 = classSubstitutor1.substitute(types1[i]); |
| PsiType type2 = classSubstitutor2.substitute(types2[i]); |
| PsiType argType = i < getActualParameterTypes().length ? getActualParameterTypes()[i] : null; |
| |
| boolean boxingInFirst = false; |
| if (isBoxingHappened(argType, type1, languageLevel)) { |
| boxingHappened[0] += 1; |
| boxingInFirst = true; |
| } |
| |
| boolean boxingInSecond = false; |
| if (isBoxingHappened(argType, type2, languageLevel)) { |
| boxingHappened[1] += 1; |
| boxingInSecond = true; |
| } |
| sameBoxing &= boxingInFirst == boxingInSecond; |
| } |
| if (boxingHappened[0] == 0 && boxingHappened[1] > 0) return Specifics.FIRST; |
| if (boxingHappened[0] > 0 && boxingHappened[1] == 0) return Specifics.SECOND; |
| |
| if (sameBoxing) { |
| final PsiSubstitutor siteSubstitutor1 = ((MethodCandidateInfo)info1).getSiteSubstitutor(); |
| final PsiSubstitutor siteSubstitutor2 = ((MethodCandidateInfo)info2).getSiteSubstitutor(); |
| |
| final PsiType[] types2AtSite = typesAtSite(types2, siteSubstitutor2); |
| final PsiType[] types1AtSite = typesAtSite(types1, siteSubstitutor1); |
| |
| final PsiSubstitutor methodSubstitutor1 = calculateMethodSubstitutor(typeParameters1, method1, siteSubstitutor1, types1, types2AtSite, languageLevel); |
| boolean applicable12 = isApplicableTo(types2AtSite, method1, languageLevel, varargsPosition, methodSubstitutor1); |
| |
| final PsiSubstitutor methodSubstitutor2 = calculateMethodSubstitutor(typeParameters2, method2, siteSubstitutor2, types2, types1AtSite, languageLevel); |
| boolean applicable21 = isApplicableTo(types1AtSite, method2, languageLevel, varargsPosition, methodSubstitutor2); |
| |
| final boolean typeArgsApplicable12 = GenericsUtil.isTypeArgumentsApplicable(typeParameters1, methodSubstitutor1, myArgumentsList, !applicable21); |
| final boolean typeArgsApplicable21 = GenericsUtil.isTypeArgumentsApplicable(typeParameters2, methodSubstitutor2, myArgumentsList, !applicable12); |
| |
| if (!typeArgsApplicable12) { |
| applicable12 = false; |
| } |
| |
| if (!typeArgsApplicable21) { |
| applicable21 = false; |
| } |
| |
| if (applicable12 || applicable21) { |
| |
| if (applicable12 && !applicable21) return Specifics.SECOND; |
| if (applicable21 && !applicable12) return Specifics.FIRST; |
| |
| final boolean abstract1 = method1.hasModifierProperty(PsiModifier.ABSTRACT); |
| final boolean abstract2 = method2.hasModifierProperty(PsiModifier.ABSTRACT); |
| if (abstract1 && !abstract2) { |
| return Specifics.SECOND; |
| } |
| if (abstract2 && !abstract1) { |
| return Specifics.FIRST; |
| } |
| } |
| if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { |
| boolean toCompareFunctional = false; |
| for (int i = 0; i < myActualParameterTypes.length; i++) { |
| if (types1.length > 0 && LambdaHighlightingUtil.checkInterfaceFunctional(types1[Math.min(i, types1.length - 1)]) == null && |
| types2.length > 0 && LambdaHighlightingUtil.checkInterfaceFunctional(types2[Math.min(i, types2.length - 1)]) == null) { |
| types1AtSite[Math.min(i, types1.length - 1)] = PsiType.NULL; |
| types2AtSite[Math.min(i, types2.length - 1)] = PsiType.NULL; |
| toCompareFunctional = true; |
| } |
| } |
| |
| if (toCompareFunctional) { |
| final boolean applicable12ignoreFunctionalType = isApplicableTo(types2AtSite, method1, languageLevel, varargsPosition, |
| calculateMethodSubstitutor(typeParameters1, method1, siteSubstitutor1, types1, types2AtSite, languageLevel)); |
| final boolean applicable21ignoreFunctionalType = isApplicableTo(types1AtSite, method2, languageLevel, varargsPosition, |
| calculateMethodSubstitutor(typeParameters2, method2, siteSubstitutor2, types2, types1AtSite, languageLevel)); |
| |
| if (applicable12ignoreFunctionalType || applicable21ignoreFunctionalType) { |
| Specifics specifics = null; |
| for (int i = 0; i < myActualParameterTypes.length; i++) { |
| if (types1.length > 0 && types1AtSite[Math.min(i, types1.length - 1)] == PsiType.NULL && |
| types2.length > 0 && types2AtSite[Math.min(i, types2.length - 1)] == PsiType.NULL) { |
| Specifics specific = compareFunctionalTypes(info1, info2, myActualParameterTypes[i], i, languageLevel); |
| if (specific == Specifics.NEITHER) { |
| specifics = Specifics.NEITHER; |
| break; |
| } |
| |
| if (specifics == null) { |
| specifics = specific; |
| } else if (specifics != specific) { |
| specifics = Specifics.NEITHER; |
| break; |
| } |
| } |
| } |
| |
| if (applicable12ignoreFunctionalType && !applicable21ignoreFunctionalType) { |
| return specifics == Specifics.SECOND ? Specifics.SECOND : Specifics.NEITHER; |
| } |
| |
| if (applicable21ignoreFunctionalType && !applicable12ignoreFunctionalType) { |
| return specifics == Specifics.FIRST ? Specifics.FIRST : Specifics.NEITHER; |
| } |
| |
| return specifics; |
| } |
| } |
| } |
| } |
| else if (varargsPosition) { |
| final PsiType lastParamType1 = classSubstitutor1.substitute(types1[types1.length - 1]); |
| final PsiType lastParamType2 = classSubstitutor2.substitute(types2[types1.length - 1]); |
| final boolean assignable1 = TypeConversionUtil.isAssignable(lastParamType2, lastParamType1); |
| final boolean assignable2 = TypeConversionUtil.isAssignable(lastParamType1, lastParamType2); |
| if (assignable1 && !assignable2) { |
| return Specifics.FIRST; |
| } |
| if (assignable2 && !assignable1) { |
| return Specifics.SECOND; |
| } |
| } |
| |
| if (class1 != class2) { |
| if (class2.isInheritor(class1, true) || class1.isInterface() && !class2.isInterface()) { |
| if (MethodSignatureUtil.isSubsignature(method1.getSignature(info1.getSubstitutor()), method2.getSignature(info2.getSubstitutor()))) { |
| return Specifics.SECOND; |
| } |
| else if (method1.hasModifierProperty(PsiModifier.STATIC) && method2.hasModifierProperty(PsiModifier.STATIC) && boxingHappened[0] == 0) { |
| return Specifics.SECOND; |
| } |
| } |
| else if (class1.isInheritor(class2, true) || class2.isInterface()) { |
| if (MethodSignatureUtil.areErasedParametersEqual(method1.getSignature(PsiSubstitutor.EMPTY), method2.getSignature(PsiSubstitutor.EMPTY)) && |
| MethodSignatureUtil.isSubsignature(method2.getSignature(info2.getSubstitutor()), method1.getSignature(info1.getSubstitutor()))) { |
| return Specifics.FIRST; |
| } |
| else if (method1.hasModifierProperty(PsiModifier.STATIC) && method2.hasModifierProperty(PsiModifier.STATIC) && boxingHappened[0] == 0) { |
| return Specifics.FIRST; |
| } |
| } |
| } |
| |
| final boolean raw1 = PsiUtil.isRawSubstitutor(method1, classSubstitutor1); |
| final boolean raw2 = PsiUtil.isRawSubstitutor(method2, classSubstitutor2); |
| if (raw1 ^ raw2) { |
| return raw1 ? Specifics.SECOND : Specifics.FIRST; |
| } |
| |
| return Specifics.NEITHER; |
| } |
| |
| private static boolean isApplicableTo(PsiType[] types2AtSite, |
| PsiMethod method1, |
| LanguageLevel languageLevel, |
| boolean varargsPosition, |
| final PsiSubstitutor methodSubstitutor1) { |
| final int applicabilityLevel = PsiUtil.getApplicabilityLevel(method1, methodSubstitutor1, types2AtSite, languageLevel, false, varargsPosition); |
| return applicabilityLevel > MethodCandidateInfo.ApplicabilityLevel.NOT_APPLICABLE; |
| } |
| |
| private static PsiType[] typesAtSite(PsiType[] types1, PsiSubstitutor siteSubstitutor1) { |
| final PsiType[] types = new PsiType[types1.length]; |
| for (int i = 0; i < types1.length; i++) { |
| types[i] = siteSubstitutor1.substitute(types1[i]); |
| } |
| return types; |
| } |
| |
| private static PsiSubstitutor calculateMethodSubstitutor(final PsiTypeParameter[] typeParameters, |
| final PsiMethod method, |
| final PsiSubstitutor siteSubstitutor, |
| final PsiType[] types1, |
| final PsiType[] types2, |
| @NotNull LanguageLevel languageLevel) { |
| PsiSubstitutor substitutor = PsiResolveHelper.SERVICE.getInstance(method.getProject()) |
| .inferTypeArguments(typeParameters, types1, types2, languageLevel); |
| for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable(method)) { |
| ProgressManager.checkCanceled(); |
| LOG.assertTrue(typeParameter != null); |
| if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) { |
| substitutor = substitutor.put(typeParameter, TypeConversionUtil.erasure(siteSubstitutor.substitute(typeParameter), substitutor)); |
| } |
| } |
| return substitutor; |
| } |
| |
| public void checkPrimitiveVarargs(final List<CandidateInfo> conflicts, |
| final int argumentsCount) { |
| if (JavaVersionService.getInstance().isAtLeast(myArgumentsList, JavaSdkVersion.JDK_1_7)) return; |
| CandidateInfo objectVararg = null; |
| for (CandidateInfo conflict : conflicts) { |
| ProgressManager.checkCanceled(); |
| final PsiMethod method = (PsiMethod)conflict.getElement(); |
| final int parametersCount = method.getParameterList().getParametersCount(); |
| if (method.isVarArgs() && parametersCount - 1 == argumentsCount) { |
| final PsiType type = method.getParameterList().getParameters()[parametersCount - 1].getType(); |
| final PsiType componentType = ((PsiArrayType)type).getComponentType(); |
| final PsiClassType classType = PsiType.getJavaLangObject(method.getManager(), GlobalSearchScope.allScope(method.getProject())); |
| if (Comparing.equal(componentType, classType)) { |
| objectVararg = conflict; |
| } |
| } |
| } |
| |
| if (objectVararg != null) { |
| for (CandidateInfo conflict : conflicts) { |
| ProgressManager.checkCanceled(); |
| PsiMethod method = (PsiMethod)conflict.getElement(); |
| if (method != objectVararg && method != null && method.isVarArgs()) { |
| final int paramsCount = method.getParameterList().getParametersCount(); |
| final PsiType type = method.getParameterList().getParameters()[paramsCount - 1].getType(); |
| final PsiType componentType = ((PsiArrayType)type).getComponentType(); |
| if (argumentsCount == paramsCount - 1 && componentType instanceof PsiPrimitiveType) { |
| conflicts.remove(objectVararg); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| enum TypeKind { |
| PRIMITIVE, REFERENCE, NONE_DETERMINED |
| } |
| |
| private static Specifics compareFunctionalTypes(CandidateInfo method, |
| CandidateInfo conflict, |
| PsiType paramType, |
| int functionalInterfaceIdx, |
| LanguageLevel languageLevel) { |
| final PsiMethod methodElement = (PsiMethod)method.getElement(); |
| final PsiMethod conflictElement = (PsiMethod)conflict.getElement(); |
| final PsiType interfaceReturnType = getReturnType(functionalInterfaceIdx, method); |
| final PsiType interfaceReturnType1 = getReturnType(functionalInterfaceIdx, conflict); |
| if (paramType instanceof PsiLambdaExpressionType) { |
| final PsiLambdaExpression lambdaExpression = ((PsiLambdaExpressionType)paramType).getExpression(); |
| if (!lambdaExpression.hasFormalParameterTypes()) { |
| return Specifics.NEITHER; |
| } |
| } |
| if (paramType instanceof PsiMethodReferenceType) { |
| final PsiMethodReferenceExpression |
| methodReferenceExpression = ((PsiMethodReferenceType)paramType).getExpression(); |
| if (!methodReferenceExpression.isExact()) { |
| return Specifics.NEITHER; |
| } |
| } |
| if (paramType instanceof PsiLambdaExpressionType || paramType instanceof PsiMethodReferenceType) { |
| if (interfaceReturnType != null && interfaceReturnType1 != null && !Comparing.equal(interfaceReturnType, interfaceReturnType1)) { |
| Specifics moreSpecific1 = comparePrimitives(paramType, interfaceReturnType, interfaceReturnType1); |
| if (moreSpecific1 == Specifics.NEITHER && (interfaceReturnType != PsiType.VOID && interfaceReturnType1 != PsiType.VOID)) { |
| return compareConflicts((MethodCandidateInfo)method, (MethodCandidateInfo)conflict, |
| methodElement, conflictElement, |
| interfaceReturnType, interfaceReturnType1, languageLevel); |
| } |
| return moreSpecific1; |
| } |
| } |
| return Specifics.NEITHER; |
| } |
| |
| private static Specifics compareConflicts(MethodCandidateInfo method, |
| MethodCandidateInfo conflict, |
| PsiMethod methodElement, |
| PsiMethod conflictElement, |
| PsiType interfaceReturnType, |
| PsiType interfaceReturnType1, |
| LanguageLevel languageLevel) { |
| final PsiSubstitutor siteSubstitutor1 = method.getSiteSubstitutor(); |
| final PsiSubstitutor siteSubstitutor2 = conflict.getSiteSubstitutor(); |
| |
| final PsiTypeParameter[] typeParameters1 = methodElement.getTypeParameters(); |
| final PsiTypeParameter[] typeParameters2 = conflictElement.getTypeParameters(); |
| |
| final PsiType[] types1AtSite = {interfaceReturnType1}; |
| final PsiType[] types2AtSite = {interfaceReturnType}; |
| |
| final PsiSubstitutor methodSubstitutor1 = calculateMethodSubstitutor(typeParameters1, methodElement, siteSubstitutor1, types2AtSite, types1AtSite, languageLevel); |
| final PsiSubstitutor methodSubstitutor2 = calculateMethodSubstitutor(typeParameters2, conflictElement, siteSubstitutor2, types1AtSite, types2AtSite,languageLevel); |
| |
| final boolean applicable12 = TypeConversionUtil.isAssignable(interfaceReturnType1, methodSubstitutor1.substitute(interfaceReturnType)); |
| final boolean applicable21 = TypeConversionUtil.isAssignable(interfaceReturnType, methodSubstitutor2.substitute(interfaceReturnType1)); |
| |
| |
| if (applicable12 || applicable21) { |
| if (!applicable21) { |
| return Specifics.FIRST; |
| } |
| |
| if (!applicable12) { |
| return Specifics.SECOND; |
| } |
| } |
| return Specifics.NEITHER; |
| } |
| |
| private static Specifics comparePrimitives(PsiType type, |
| PsiType interfaceReturnType, |
| PsiType interfaceReturnType1) { |
| final TypeKind typeKind = getKind(type); |
| Specifics moreSpecific1 = Specifics.NEITHER; |
| if (typeKind != TypeKind.NONE_DETERMINED) { |
| final boolean isPrimitive = typeKind == TypeKind.PRIMITIVE; |
| if (interfaceReturnType instanceof PsiPrimitiveType) { |
| if (interfaceReturnType1 instanceof PsiPrimitiveType && |
| TypeConversionUtil.isAssignable(interfaceReturnType, interfaceReturnType1)) { |
| moreSpecific1 = isPrimitive ? Specifics.SECOND : Specifics.FIRST; |
| } else { |
| moreSpecific1 = isPrimitive ? Specifics.FIRST : Specifics.SECOND; |
| } |
| } else if (interfaceReturnType1 instanceof PsiPrimitiveType) { |
| moreSpecific1 = isPrimitive ? Specifics.SECOND : Specifics.FIRST; |
| } |
| } |
| return moreSpecific1; |
| } |
| |
| @Nullable |
| private static PsiType getReturnType(int functionalTypeIdx, CandidateInfo method) { |
| final PsiParameter[] methodParameters = ((PsiMethod)method.getElement()).getParameterList().getParameters(); |
| if (methodParameters.length == 0) return null; |
| final PsiParameter param = functionalTypeIdx < methodParameters.length ? methodParameters[functionalTypeIdx] : methodParameters[methodParameters.length - 1]; |
| final PsiType functionalInterfaceType = ((MethodCandidateInfo)method).getSiteSubstitutor().substitute(param.getType()); |
| return LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType); |
| } |
| |
| private static TypeKind getKind(PsiType lambdaType) { |
| TypeKind typeKind = TypeKind.PRIMITIVE; |
| if (lambdaType instanceof PsiLambdaExpressionType) { |
| typeKind = areLambdaReturnExpressionsPrimitive((PsiLambdaExpressionType)lambdaType); |
| } else if (lambdaType instanceof PsiMethodReferenceType) { |
| final PsiElement referencedElement = ((PsiMethodReferenceType)lambdaType).getExpression().resolve(); |
| if (referencedElement instanceof PsiMethod && !(((PsiMethod)referencedElement).getReturnType() instanceof PsiPrimitiveType)) { |
| typeKind = TypeKind.REFERENCE; |
| } |
| } |
| return typeKind; |
| } |
| |
| private static TypeKind areLambdaReturnExpressionsPrimitive(PsiLambdaExpressionType lambdaType) { |
| final List<PsiExpression> returnExpressions = LambdaUtil.getReturnExpressions(lambdaType.getExpression()); |
| TypeKind typeKind = TypeKind.NONE_DETERMINED; |
| for (PsiExpression expression : returnExpressions) { |
| final PsiType returnExprType = expression.getType(); |
| if (returnExprType instanceof PsiPrimitiveType) { |
| if (typeKind == TypeKind.REFERENCE) { |
| typeKind = TypeKind.NONE_DETERMINED; |
| break; |
| } |
| typeKind = TypeKind.PRIMITIVE; |
| } else { |
| if (typeKind == TypeKind.PRIMITIVE) { |
| typeKind = TypeKind.NONE_DETERMINED; |
| break; |
| } |
| typeKind = TypeKind.REFERENCE; |
| } |
| } |
| return typeKind; |
| } |
| } |