mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-21 14:37:45 +07:00
852 lines
38 KiB
Java
852 lines
38 KiB
Java
/*
|
|
* Copyright 2000-2015 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.openapi.util.Computable;
|
|
import com.intellij.pom.java.LanguageLevel;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.impl.PsiSuperMethodImplUtil;
|
|
import com.intellij.psi.impl.source.PsiImmediateClassType;
|
|
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
|
|
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
|
|
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.FactoryMap;
|
|
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 final 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 final CandidateInfo resolveConflict(@NotNull final List<CandidateInfo> conflicts){
|
|
final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(myArgumentsList);
|
|
if (properties != null && properties.isApplicabilityCheck()) {
|
|
final PsiMethod method = properties.getMethod();
|
|
LOG.error("Recursive conflict resolution for:" + method + "; " +
|
|
myArgumentsList.getText() + "; " +
|
|
"file=" + (method == null ? "<unknown>" : method.getContainingFile()));
|
|
}
|
|
return MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(myArgumentsList, false, new Computable<CandidateInfo>() {
|
|
@Override
|
|
public CandidateInfo compute() {
|
|
return guardedOverloadResolution(conflicts);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Nullable
|
|
protected CandidateInfo guardedOverloadResolution(@NotNull List<CandidateInfo> conflicts) {
|
|
if (conflicts.isEmpty()) return null;
|
|
if (conflicts.size() == 1) return conflicts.get(0);
|
|
|
|
boolean atLeastOneMatch = checkParametersNumber(conflicts, getActualParametersLength(), true);
|
|
if (conflicts.size() == 1) return conflicts.get(0);
|
|
|
|
final FactoryMap<MethodCandidateInfo, PsiSubstitutor> map = new FactoryMap<MethodCandidateInfo, PsiSubstitutor>() {
|
|
@Nullable
|
|
@Override
|
|
protected PsiSubstitutor create(MethodCandidateInfo key) {
|
|
return key.getSubstitutor(false);
|
|
}
|
|
};
|
|
|
|
checkSameSignatures(conflicts, map);
|
|
if (conflicts.size() == 1) return conflicts.get(0);
|
|
|
|
checkAccessStaticLevels(conflicts, true);
|
|
if (conflicts.size() == 1) return conflicts.get(0);
|
|
|
|
checkParametersNumber(conflicts, getActualParametersLength(), false);
|
|
if (conflicts.size() == 1) return conflicts.get(0);
|
|
|
|
checkStaticMethodsOfInterfaces(conflicts);
|
|
if (conflicts.size() == 1) return conflicts.get(0);
|
|
|
|
if (atLeastOneMatch) {
|
|
checkPotentiallyCompatibleMethods(conflicts);
|
|
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;
|
|
|
|
checkSpecifics(conflicts, applicabilityLevel, map, myLanguageLevel);
|
|
if (conflicts.size() == 1) return conflicts.get(0);
|
|
|
|
checkPrimitiveVarargs(conflicts, getActualParametersLength());
|
|
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 static void checkPotentiallyCompatibleMethods(@NotNull List<CandidateInfo> conflicts) {
|
|
for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext(); ) {
|
|
CandidateInfo conflict = iterator.next();
|
|
if (conflict instanceof MethodCandidateInfo &&
|
|
!((MethodCandidateInfo)conflict).isPotentiallyCompatible()) {
|
|
iterator.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void checkSpecifics(@NotNull List<CandidateInfo> conflicts,
|
|
@MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel,
|
|
@NotNull LanguageLevel languageLevel) {
|
|
checkSpecifics(conflicts, applicabilityLevel, null, languageLevel);
|
|
}
|
|
|
|
public void checkSpecifics(@NotNull List<CandidateInfo> conflicts,
|
|
@MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel,
|
|
FactoryMap<MethodCandidateInfo, PsiSubstitutor> map,
|
|
@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, applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY)) continue;
|
|
switch (isMoreSpecific((MethodCandidateInfo)method, (MethodCandidateInfo)conflict, applicabilityLevel, map, languageLevel)) {
|
|
case FIRST:
|
|
conflicts.remove(conflict);
|
|
break;
|
|
case SECOND:
|
|
conflicts.remove(method);
|
|
break;
|
|
case NEITHER:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean nonComparable(@NotNull CandidateInfo method, @NotNull CandidateInfo conflict, boolean fixedArity) {
|
|
assert method != conflict;
|
|
return false;
|
|
}
|
|
|
|
protected static void checkAccessStaticLevels(@NotNull 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) {
|
|
checkSameSignatures(conflicts, null);
|
|
}
|
|
|
|
protected void checkSameSignatures(@NotNull List<CandidateInfo> conflicts, FactoryMap<MethodCandidateInfo, PsiSubstitutor> map) {
|
|
// 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();
|
|
final PsiClass containingClass = method.getContainingClass();
|
|
final boolean isInterface = containingClass != null && containingClass.isInterface();
|
|
for (HierarchicalMethodSignature methodSignature : method.getHierarchicalMethodSignature().getSuperSignatures()) {
|
|
final PsiMethod superMethod = methodSignature.getMethod();
|
|
if (!isInterface) {
|
|
superMethods.add(superMethod);
|
|
}
|
|
else {
|
|
final PsiClass aClass = superMethod.getContainingClass();
|
|
if (aClass != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.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();
|
|
|
|
if (!method.hasModifierProperty(PsiModifier.STATIC) && superMethods.contains(method)) {
|
|
conflicts.remove(i);
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
PsiClass class1 = method.getContainingClass();
|
|
PsiSubstitutor infoSubstitutor = getSubstitutor((MethodCandidateInfo)info, map);
|
|
MethodSignature signature = method.getSignature(infoSubstitutor);
|
|
CandidateInfo existing = signatures.get(signature);
|
|
|
|
if (existing == null) {
|
|
signatures.put(signature, info);
|
|
continue;
|
|
}
|
|
PsiMethod existingMethod = (PsiMethod)existing.getElement();
|
|
PsiClass existingClass = existingMethod.getContainingClass();
|
|
if (class1 != null && existingClass != null) { //prefer interface methods to methods from Object
|
|
if (class1.isInterface() && CommonClassNames.JAVA_LANG_OBJECT.equals(existingClass.getQualifiedName())) {
|
|
signatures.put(signature, info);
|
|
continue;
|
|
}
|
|
else if (existingClass.isInterface() && CommonClassNames.JAVA_LANG_OBJECT.equals(class1.getQualifiedName())) {
|
|
conflicts.remove(info);
|
|
i--;
|
|
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 = getSubstitutor((MethodCandidateInfo)existing, map).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 && existingClass != null && class1 != null) {
|
|
final PsiSubstitutor eSubstitutor = TypeConversionUtil.getMaybeSuperClassSubstitutor(existingClass, currentClass, PsiSubstitutor.EMPTY, null);
|
|
final PsiSubstitutor cSubstitutor = TypeConversionUtil.getMaybeSuperClassSubstitutor(class1, currentClass, PsiSubstitutor.EMPTY, null);
|
|
if (eSubstitutor != null && cSubstitutor != null &&
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
private static PsiSubstitutor getSubstitutor(MethodCandidateInfo existing, FactoryMap<MethodCandidateInfo, PsiSubstitutor> map) {
|
|
return map != null ? map.get(existing) : existing.getSubstitutor(false);
|
|
}
|
|
|
|
private static boolean areTypeParametersAgree(@NotNull CandidateInfo info) {
|
|
return ((MethodCandidateInfo)info).getPertinentApplicabilityLevel() != MethodCandidateInfo.ApplicabilityLevel.NOT_APPLICABLE;
|
|
}
|
|
|
|
/**
|
|
* choose to accept static interface methods during search to get "Static interface methods must be invoked on containing interface class only" error
|
|
* instead of non clear javac message that symbol not found
|
|
*
|
|
* but these methods should be ignored during overload resolution if another methods are present
|
|
*/
|
|
private void checkStaticMethodsOfInterfaces(@NotNull List<CandidateInfo> conflicts) {
|
|
if (!(myArgumentsList instanceof PsiExpressionList)) return;
|
|
PsiClass qualifierClass = null;
|
|
for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext(); ) {
|
|
CandidateInfo conflict = iterator.next();
|
|
if (!(conflict instanceof MethodCandidateInfo)) continue;
|
|
final PsiMethod method = ((MethodCandidateInfo)conflict).getElement();
|
|
if (method.hasModifierProperty(PsiModifier.STATIC)) {
|
|
if (conflict.getCurrentFileResolveScope() instanceof PsiImportStaticStatement) continue;
|
|
final PsiClass containingClass = method.getContainingClass();
|
|
if (containingClass != null && containingClass.isInterface()) {
|
|
if (qualifierClass == null) {
|
|
qualifierClass = getQualifiedClass(method);
|
|
if (qualifierClass == null) return;
|
|
}
|
|
if (!containingClass.getManager().areElementsEquivalent(containingClass, qualifierClass)) {
|
|
iterator.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private PsiClass getQualifiedClass(PsiMethod method) {
|
|
final PsiElement parent = myArgumentsList.getParent();
|
|
if (parent instanceof PsiMethodCallExpression) {
|
|
final PsiExpression expression = ((PsiMethodCallExpression)parent).getMethodExpression().getQualifierExpression();
|
|
if (expression instanceof PsiReferenceExpression) {
|
|
final PsiElement resolve = ((PsiReferenceExpression)expression).resolve();
|
|
if (resolve instanceof PsiClass) {
|
|
return (PsiClass)resolve;
|
|
}
|
|
}
|
|
else if (expression == null && !ImportsUtil.hasStaticImportOn(parent, method, true)) {
|
|
return PsiTreeUtil.getParentOfType(parent, PsiClass.class);
|
|
}
|
|
|
|
if (expression != null) {
|
|
return PsiUtil.resolveClassInType(expression.getType());
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public boolean checkParametersNumber(@NotNull 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();
|
|
final int parametersCount = method.getParameterList().getParametersCount();
|
|
if (((myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8) ? ((MethodCandidateInfo)info).isVarargs() : method.isVarArgs()) && parametersCount - 1 <= argumentsCount) ||
|
|
parametersCount == 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
|
|
public int checkApplicability(@NotNull List<CandidateInfo> conflicts) {
|
|
@MethodCandidateInfo.ApplicabilityLevelConstant int maxApplicabilityLevel = 0;
|
|
boolean toFilter = false;
|
|
for (CandidateInfo conflict : conflicts) {
|
|
ProgressManager.checkCanceled();
|
|
@MethodCandidateInfo.ApplicabilityLevelConstant final int level = getPertinentApplicabilityLevel((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 = getPertinentApplicabilityLevel((MethodCandidateInfo)info);
|
|
if (level < maxApplicabilityLevel) {
|
|
iterator.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
return maxApplicabilityLevel;
|
|
}
|
|
|
|
protected int getPertinentApplicabilityLevel(@NotNull MethodCandidateInfo conflict) {
|
|
return conflict.getPertinentApplicabilityLevel();
|
|
}
|
|
|
|
private static int getCheckAccessLevel(@NotNull MethodCandidateInfo method){
|
|
boolean visible = method.isAccessible();
|
|
return visible ? 1 : 0;
|
|
}
|
|
|
|
private static int getCheckStaticLevel(@NotNull MethodCandidateInfo method){
|
|
boolean available = method.isStaticsScopeCorrect();
|
|
return (available ? 1 : 0) << 1 |
|
|
(method.getCurrentFileResolveScope() instanceof PsiImportStaticStatement ? 0 : 1);
|
|
}
|
|
|
|
private int getActualParametersLength() {
|
|
if (myActualParameterTypes == null) {
|
|
LOG.assertTrue(myArgumentsList instanceof PsiExpressionList, myArgumentsList);
|
|
return ((PsiExpressionList)myArgumentsList).getExpressions().length;
|
|
}
|
|
return myActualParameterTypes.length;
|
|
}
|
|
|
|
private enum Specifics {
|
|
FIRST,
|
|
SECOND,
|
|
NEITHER
|
|
}
|
|
|
|
private Specifics isMoreSpecific(@NotNull MethodCandidateInfo info1,
|
|
@NotNull MethodCandidateInfo info2,
|
|
@MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel,
|
|
FactoryMap<MethodCandidateInfo, PsiSubstitutor> map,
|
|
@NotNull LanguageLevel languageLevel) {
|
|
PsiMethod method1 = info1.getElement();
|
|
PsiMethod method2 = info2.getElement();
|
|
final PsiClass class1 = method1.getContainingClass();
|
|
final PsiClass class2 = method2.getContainingClass();
|
|
|
|
final PsiParameter[] params1 = method1.getParameterList().getParameters();
|
|
final PsiParameter[] params2 = method2.getParameterList().getParameters();
|
|
|
|
final PsiTypeParameter[] typeParameters1 = method1.getTypeParameters();
|
|
final PsiTypeParameter[] typeParameters2 = method2.getTypeParameters();
|
|
final PsiSubstitutor classSubstitutor1 = getSubstitutor(info1, map); //substitutions for method type parameters will be ignored
|
|
final PsiSubstitutor classSubstitutor2 = getSubstitutor(info2, map);
|
|
|
|
//process all arguments of varargs call
|
|
//todo check method reference actual params length
|
|
final int argsLength = languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && (method1.isVarArgs() || method2.isVarArgs())
|
|
? getActualParametersLength() : 0;
|
|
final int max = Math.max(Math.max(params1.length, params2.length), argsLength);
|
|
PsiType[] types1 = PsiType.createArray(max);
|
|
PsiType[] types2 = PsiType.createArray(max);
|
|
final boolean varargsPosition = applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.VARARGS;
|
|
for (int i = 0; i < max; i++) {
|
|
ProgressManager.checkCanceled();
|
|
PsiType type1 = params1.length > 0 ? params1[Math.min(i, params1.length - 1)].getType() : null;
|
|
PsiType type2 = params2.length > 0 ? params2[Math.min(i, params2.length - 1)].getType() : null;
|
|
if (varargsPosition) {
|
|
if (type1 instanceof PsiEllipsisType && type2 instanceof PsiEllipsisType &&
|
|
params1.length == params2.length &&
|
|
class1 != null && (!JavaVersionService.getInstance().isAtLeast(class1, JavaSdkVersion.JDK_1_7) || ((PsiArrayType)type1).getComponentType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || ((PsiArrayType)type2).getComponentType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT))) {
|
|
type1 = ((PsiEllipsisType)type1).toArrayType();
|
|
type2 = ((PsiEllipsisType)type2).toArrayType();
|
|
}
|
|
else {
|
|
type1 = type1 instanceof PsiEllipsisType ? ((PsiArrayType)type1).getComponentType() : type1;
|
|
type2 = type2 instanceof PsiEllipsisType ? ((PsiArrayType)type2).getComponentType() : type2;
|
|
}
|
|
}
|
|
|
|
types1[i] = type1;
|
|
types2[i] = type2;
|
|
}
|
|
|
|
boolean sameBoxing = true;
|
|
boolean[] boxingHappened = new boolean [2];
|
|
|
|
final PsiExpression[] args = myArgumentsList instanceof PsiExpressionList ? ((PsiExpressionList)myArgumentsList).getExpressions() : null;
|
|
|
|
for (int i = 0; i < types1.length; i++) {
|
|
ProgressManager.checkCanceled();
|
|
final PsiExpression arg = args != null && i < args.length ? args[i] : null;
|
|
final PsiType argType = myActualParameterTypes != null && i < getActualParametersLength() ? myActualParameterTypes[i] : null;
|
|
if (arg == null && argType == null) continue;
|
|
|
|
boolean boxingInFirst = false;
|
|
if (isBoxingUsed(classSubstitutor1.substitute(types1[i]), argType, arg)) {
|
|
boxingHappened[0] |= true;
|
|
boxingInFirst = true;
|
|
}
|
|
|
|
boolean boxingInSecond = false;
|
|
if (isBoxingUsed(classSubstitutor2.substitute(types2[i]), argType, arg)) {
|
|
boxingHappened[1] |= true;
|
|
boxingInSecond = true;
|
|
}
|
|
sameBoxing &= boxingInFirst == boxingInSecond;
|
|
}
|
|
if (!boxingHappened[0] && boxingHappened[1]) return Specifics.FIRST;
|
|
if (boxingHappened[0] && !boxingHappened[1]) return Specifics.SECOND;
|
|
|
|
if (sameBoxing) {
|
|
final PsiSubstitutor siteSubstitutor1 = info1.getSiteSubstitutor();
|
|
final PsiSubstitutor siteSubstitutor2 = info2.getSiteSubstitutor();
|
|
|
|
final PsiType[] types2AtSite = typesAtSite(types2, siteSubstitutor2);
|
|
final PsiType[] types1AtSite = typesAtSite(types1, siteSubstitutor1);
|
|
|
|
final PsiSubstitutor methodSubstitutor1 = calculateMethodSubstitutor(typeParameters1, method1, siteSubstitutor1, types1, types2AtSite,
|
|
languageLevel);
|
|
boolean applicable12 = isApplicableTo(types2AtSite, method1, languageLevel, varargsPosition, methodSubstitutor1, method2, siteSubstitutor2);
|
|
|
|
final PsiSubstitutor methodSubstitutor2 = calculateMethodSubstitutor(typeParameters2, method2, siteSubstitutor2, types2, types1AtSite, languageLevel);
|
|
boolean applicable21 = isApplicableTo(types1AtSite, method2, languageLevel, varargsPosition, methodSubstitutor2, method1, siteSubstitutor1);
|
|
|
|
if (!myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
|
|
final boolean typeArgsApplicable12 = GenericsUtil.isTypeArgumentsApplicable(typeParameters1, methodSubstitutor1, myArgumentsList, !applicable21);
|
|
final boolean typeArgsApplicable21 = GenericsUtil.isTypeArgumentsApplicable(typeParameters2, methodSubstitutor2, myArgumentsList, !applicable12);
|
|
|
|
if (!typeArgsApplicable12) {
|
|
applicable12 = false;
|
|
}
|
|
|
|
if (!typeArgsApplicable21) {
|
|
applicable21 = false;
|
|
}
|
|
}
|
|
|
|
if (applicable12 || applicable21) {
|
|
|
|
if (applicable12 && !applicable21) return Specifics.SECOND;
|
|
if (applicable21 && !applicable12) return Specifics.FIRST;
|
|
|
|
//from 15.12.2.5 Choosing the Most Specific Method: concrete = nonabstract or default
|
|
final boolean abstract1 = method1.hasModifierProperty(PsiModifier.ABSTRACT) || method1.hasModifierProperty(PsiModifier.DEFAULT);
|
|
final boolean abstract2 = method2.hasModifierProperty(PsiModifier.ABSTRACT) || method2.hasModifierProperty(PsiModifier.DEFAULT);
|
|
if (abstract1 && !abstract2) {
|
|
return Specifics.SECOND;
|
|
}
|
|
if (abstract2 && !abstract1) {
|
|
return Specifics.FIRST;
|
|
}
|
|
|
|
if (abstract1 && abstract2 && MethodSignatureUtil.areOverrideEquivalent(method1, method2)) {
|
|
final PsiType returnType1 = method1.getReturnType();
|
|
final PsiType returnType2 = method2.getReturnType();
|
|
if (returnType1 != null && returnType2 != null && returnType1.isAssignableFrom(returnType2)) {
|
|
return Specifics.SECOND;
|
|
}
|
|
return Specifics.FIRST;
|
|
}
|
|
}
|
|
}
|
|
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 (isSubSignature(method1, method2, classSubstitutor1, classSubstitutor2, boxingHappened)) {
|
|
return Specifics.SECOND;
|
|
}
|
|
}
|
|
else if (class1.isInheritor(class2, true) || class2.isInterface()) {
|
|
if (isSubSignature(method2, method1, classSubstitutor2, classSubstitutor1, boxingHappened)) {
|
|
return Specifics.FIRST;
|
|
}
|
|
}
|
|
}
|
|
|
|
final boolean varargs1 = info1.isVarargs();
|
|
final boolean varargs2 = info2.isVarargs();
|
|
if (varargs1 ^ varargs2) {
|
|
return varargs1 ? Specifics.SECOND : Specifics.FIRST;
|
|
}
|
|
|
|
return Specifics.NEITHER;
|
|
}
|
|
|
|
private static boolean isBoxingUsed(PsiType parameterType, @Nullable PsiType argType, PsiExpression arg) {
|
|
ProgressManager.checkCanceled();
|
|
final boolean isExpressionTypePrimitive = argType != null ? argType instanceof PsiPrimitiveType
|
|
: PsiPolyExpressionUtil.isExpressionOfPrimitiveType(arg);
|
|
return parameterType instanceof PsiPrimitiveType ^ isExpressionTypePrimitive;
|
|
}
|
|
|
|
private static boolean isSubSignature(PsiMethod method1,
|
|
PsiMethod method2,
|
|
PsiSubstitutor classSubstitutor1,
|
|
PsiSubstitutor classSubstitutor2,
|
|
boolean[] boxingHappened) {
|
|
return MethodSignatureUtil.areErasedParametersEqual(method1.getSignature(PsiSubstitutor.EMPTY), method2.getSignature(PsiSubstitutor.EMPTY)) &&
|
|
MethodSignatureUtil.isSubsignature(method1.getSignature(classSubstitutor1), method2.getSignature(classSubstitutor2)) ||
|
|
method1.hasModifierProperty(PsiModifier.STATIC) && method2.hasModifierProperty(PsiModifier.STATIC) && !boxingHappened[0];
|
|
}
|
|
|
|
private boolean isApplicableTo(@NotNull PsiType[] types2AtSite,
|
|
@NotNull PsiMethod method1,
|
|
@NotNull final LanguageLevel languageLevel,
|
|
boolean varargsPosition,
|
|
@NotNull PsiSubstitutor methodSubstitutor1,
|
|
@NotNull PsiMethod method2,
|
|
final PsiSubstitutor siteSubstitutor1) {
|
|
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && method1.getTypeParameters().length > 0 && myArgumentsList instanceof PsiExpressionList) {
|
|
final PsiElement parent = myArgumentsList.getParent();
|
|
if (parent instanceof PsiCallExpression) {
|
|
return InferenceSession.isMoreSpecific(method2, method1, siteSubstitutor1, ((PsiExpressionList)myArgumentsList).getExpressions(), myArgumentsList, varargsPosition);
|
|
}
|
|
}
|
|
final PsiUtil.ApplicabilityChecker applicabilityChecker = new PsiUtil.ApplicabilityChecker() {
|
|
@Override
|
|
public boolean isApplicable(PsiType left, PsiType right, boolean allowUncheckedConversion, int argId) {
|
|
if (right instanceof PsiClassType) {
|
|
final PsiClass rightClass = ((PsiClassType)right).resolve();
|
|
if (rightClass instanceof PsiTypeParameter) {
|
|
right = new PsiImmediateClassType(rightClass, siteSubstitutor1);
|
|
}
|
|
}
|
|
return languageLevel.isAtLeast(LanguageLevel.JDK_1_8) ? isTypeMoreSpecific(left, right, argId) : TypeConversionUtil.isAssignable(left, right, allowUncheckedConversion);
|
|
}
|
|
};
|
|
final int applicabilityLevel = PsiUtil.getApplicabilityLevel(method1, methodSubstitutor1, types2AtSite, languageLevel, false, varargsPosition, applicabilityChecker);
|
|
return applicabilityLevel > MethodCandidateInfo.ApplicabilityLevel.NOT_APPLICABLE;
|
|
}
|
|
|
|
// 15.12.2.5
|
|
// A type S is more specific than a type T for any expression if S <: T (§4.10).
|
|
// A functional interface type S is more specific than a functional interface type T for
|
|
// an expression e if T is not a subtype of S and one of the following is true
|
|
private boolean isTypeMoreSpecific(PsiType left, PsiType right, int argId) {
|
|
if (TypeConversionUtil.isAssignable(left, right, false)) {
|
|
return true;
|
|
}
|
|
if (myArgumentsList instanceof PsiExpressionList) {
|
|
final PsiExpression[] expressions = ((PsiExpressionList)myArgumentsList).getExpressions();
|
|
if (argId < expressions.length) {
|
|
return isFunctionalTypeMoreSpecific(expressions[argId], right, left);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@NotNull
|
|
private static PsiType[] typesAtSite(@NotNull PsiType[] types1, @NotNull PsiSubstitutor siteSubstitutor1) {
|
|
final PsiType[] types = PsiType.createArray(types1.length);
|
|
for (int i = 0; i < types1.length; i++) {
|
|
types[i] = siteSubstitutor1.substitute(types1[i]);
|
|
}
|
|
return types;
|
|
}
|
|
|
|
@NotNull
|
|
private static PsiSubstitutor calculateMethodSubstitutor(@NotNull PsiTypeParameter[] typeParameters,
|
|
@NotNull PsiMethod method,
|
|
@NotNull PsiSubstitutor siteSubstitutor,
|
|
@NotNull PsiType[] types1,
|
|
@NotNull 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)) {
|
|
PsiType type = siteSubstitutor.substitute(typeParameter);
|
|
if (type instanceof PsiClassType && typeParameter.getOwner() == method) {
|
|
final PsiClass aClass = ((PsiClassType)type).resolve();
|
|
if (aClass instanceof PsiTypeParameter && ((PsiTypeParameter)aClass).getOwner() == method) {
|
|
type = TypeConversionUtil.erasure(type, siteSubstitutor);
|
|
}
|
|
}
|
|
substitutor = substitutor.put(typeParameter, type);
|
|
} else {
|
|
final PsiType type = substitutor.substitute(typeParameter);
|
|
if (type instanceof PsiClassType) {
|
|
final PsiClass aClass = ((PsiClassType)type).resolve();
|
|
if (aClass instanceof PsiTypeParameter) {
|
|
substitutor = substitutor.put(typeParameter, JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass, siteSubstitutor));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return substitutor;
|
|
}
|
|
|
|
public void checkPrimitiveVarargs(@NotNull 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.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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean isFunctionalTypeMoreSpecific(PsiExpression expr, PsiType sType, PsiType tType) {
|
|
if (expr instanceof PsiParenthesizedExpression) {
|
|
return isFunctionalTypeMoreSpecific(((PsiParenthesizedExpression)expr).getExpression(), sType, tType);
|
|
}
|
|
|
|
if (expr instanceof PsiConditionalExpression) {
|
|
return isFunctionalTypeMoreSpecific(((PsiConditionalExpression)expr).getThenExpression(), sType, tType) &&
|
|
isFunctionalTypeMoreSpecific(((PsiConditionalExpression)expr).getElseExpression(), sType, tType);
|
|
}
|
|
|
|
if (expr instanceof PsiFunctionalExpression) {
|
|
|
|
if (expr instanceof PsiLambdaExpression && !((PsiLambdaExpression)expr).hasFormalParameterTypes()) {
|
|
return false;
|
|
}
|
|
if (expr instanceof PsiMethodReferenceExpression && !((PsiMethodReferenceExpression)expr).isExact()) {
|
|
return false;
|
|
}
|
|
|
|
if (LambdaUtil.isFunctionalType(sType) && LambdaUtil.isFunctionalType(tType) &&
|
|
!TypeConversionUtil.erasure(tType).isAssignableFrom(sType) &&
|
|
!TypeConversionUtil.erasure(sType).isAssignableFrom(tType)) {
|
|
return InferenceSession.isFunctionalTypeMoreSpecificOnExpression(sType, tType, expr);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
} |