blob: 22cc0512cd90077eacb34d4126d298cb91a6d1d3 [file] [log] [blame]
/*
* 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;
}
}