varargs resolve bug
[fedora-idea.git] / source / com / intellij / psi / scope / conflictResolvers / JavaMethodsConflictResolver.java
blob79f68d993af5bd207981c1692ce8005d35fb4e54
1 package com.intellij.psi.scope.conflictResolvers;
3 import com.intellij.pom.java.LanguageLevel;
4 import com.intellij.psi.*;
5 import com.intellij.psi.infos.CandidateInfo;
6 import com.intellij.psi.infos.MethodCandidateInfo;
7 import com.intellij.psi.scope.PsiConflictResolver;
8 import com.intellij.psi.util.*;
9 import com.intellij.util.containers.ContainerUtil;
10 import com.intellij.util.Function;
11 import gnu.trove.THashSet;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Map;
18 /**
19 * Created by IntelliJ IDEA.
20 * User: ik
21 * Date: 10.06.2003
22 * Time: 19:41:51
23 * To change this template use Options | File Templates.
25 public class JavaMethodsConflictResolver implements PsiConflictResolver{
26 private final PsiElement myArgumentsList;
27 private final PsiType[] myActualParameterTypes;
29 public JavaMethodsConflictResolver(PsiExpressionList list){
30 myArgumentsList = list;
31 myActualParameterTypes = ContainerUtil.map2Array(list.getExpressions(), PsiType.class, new Function<PsiExpression, PsiType>() {
32 public PsiType fun(final PsiExpression expression) {
33 return expression.getType();
35 });
38 public JavaMethodsConflictResolver(final PsiElement argumentsList, final PsiType[] actualParameterTypes) {
39 myArgumentsList = argumentsList;
40 myActualParameterTypes = actualParameterTypes;
43 public CandidateInfo resolveConflict(List<CandidateInfo> conflicts){
44 if (conflicts.isEmpty()) return null;
45 if (conflicts.size() == 1) return conflicts.get(0);
46 checkSameSignatures(conflicts);
48 if (conflicts.size() == 1) return conflicts.get(0);
49 checkAccessLevels(conflicts);
51 if (conflicts.size() == 1) return conflicts.get(0);
53 checkParametersNumber(conflicts, myActualParameterTypes.length);
54 if (conflicts.size() == 1) return conflicts.get(0);
56 final int applicabilityLevel = checkApplicability(conflicts);
57 checkSpecifics(conflicts, applicabilityLevel);
59 if (conflicts.size() == 1) return conflicts.get(0);
61 THashSet<CandidateInfo> uniques = new THashSet<CandidateInfo>(conflicts);
62 if (uniques.size() == 1) return uniques.iterator().next();
63 return null;
66 private void checkSpecifics(List<CandidateInfo> conflicts, int applicabilityLevel) {
67 final boolean applicable = applicabilityLevel > MethodCandidateInfo.ApplicabilityLevel.NOT_APPLICABLE;
69 int conflictsCount = conflicts.size();
70 // Specifics
71 if (applicable) {
72 final CandidateInfo[] newConflictsArray = conflicts.toArray(new CandidateInfo[conflicts.size()]);
73 for (int i = 1; i < conflictsCount; i++) {
74 final CandidateInfo method = newConflictsArray[i];
75 for (int j = 0; j < i; j++) {
76 final CandidateInfo conflict = newConflictsArray[j];
77 assert conflict != method;
78 switch (isMoreSpecific(method, conflict, applicabilityLevel)) {
79 case TRUE:
80 conflicts.remove(conflict);
81 break;
82 case FALSE:
83 conflicts.remove(method);
84 break;
85 case CONFLICT:
86 break;
93 private static void checkAccessLevels(List<CandidateInfo> conflicts) {
94 int conflictsCount = conflicts.size();
96 int maxCheckLevel = -1;
97 int[] checkLevels = new int[conflictsCount];
98 int index = 0;
99 for (final CandidateInfo conflict : conflicts) {
100 final MethodCandidateInfo method = (MethodCandidateInfo)conflict;
101 final int level = getCheckLevel(method);
102 checkLevels[index++] = level;
103 maxCheckLevel = Math.max(maxCheckLevel, level);
106 for (int i = conflictsCount - 1; i >= 0; i--) {
107 // check for level
108 if (checkLevels[i] < maxCheckLevel) {
109 conflicts.remove(i);
114 private static void checkSameSignatures(final List<CandidateInfo> conflicts) {
115 // candidates should go in order of class hierarchy traversal
116 // in order for this to work
117 Map<MethodSignature, CandidateInfo> signatures = new HashMap<MethodSignature, CandidateInfo>();
118 for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext();) {
119 CandidateInfo info = iterator.next();
120 PsiMethod method = (PsiMethod)info.getElement();
121 assert method != null;
122 PsiClass class1 = method.getContainingClass();
123 MethodSignature signature = method.getSignature(info.getSubstitutor());
124 CandidateInfo existing = signatures.get(signature);
126 if (existing == null) {
127 signatures.put(signature, info);
128 continue;
130 PsiMethod existingMethod = (PsiMethod)existing.getElement();
131 assert existingMethod != null;
132 PsiClass existingClass = existingMethod.getContainingClass();
133 if (class1.isInterface() && "java.lang.Object".equals(existingClass.getQualifiedName())) { //prefer interface methods to methods from Object
134 signatures.put(signature, info);
135 continue;
137 if (method == existingMethod) {
138 PsiElement scope1 = info.getCurrentFileResolveScope();
139 PsiElement scope2 = existing.getCurrentFileResolveScope();
140 if (scope1 instanceof PsiClass && scope2 instanceof PsiClass && PsiTreeUtil.isAncestor(scope1, scope2, true) && !existing.isAccessible()) { //prefer methods from outer class to inaccessible base class methods
141 signatures.put(signature, info);
142 continue;
145 PsiType returnType1 = method.getReturnType();
146 PsiType returnType2 = existingMethod.getReturnType();
147 if (returnType1 != null && returnType2 != null) {
148 returnType1 = info.getSubstitutor().substitute(returnType1);
149 returnType2 = existing.getSubstitutor().substitute(returnType2);
150 if (returnType1.isAssignableFrom(returnType2) && (InheritanceUtil.isInheritorOrSelf(class1, existingClass, true) ||
151 InheritanceUtil.isInheritorOrSelf(existingClass, class1, true))) {
152 iterator.remove();
158 private static void checkParametersNumber(final List<CandidateInfo> conflicts, final int argumentsCount) {
159 boolean parametersNumberMatch = false;
160 for (CandidateInfo info : conflicts) {
161 if (info instanceof MethodCandidateInfo) {
162 final PsiMethod method = ((MethodCandidateInfo)info).getElement();
163 if (method.isVarArgs()) return;
164 if (method.getParameterList().getParametersCount() == argumentsCount) {
165 parametersNumberMatch = true;
170 if (parametersNumberMatch) {
171 for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext();) {
172 CandidateInfo info = iterator.next();
173 if (info instanceof MethodCandidateInfo) {
174 final PsiMethod method = ((MethodCandidateInfo)info).getElement();
175 if (method.getParameterList().getParametersCount() != argumentsCount) {
176 iterator.remove();
183 private static int checkApplicability(List<CandidateInfo> conflicts) {
184 int maxApplicabilityLevel = 0;
185 boolean toFilter = false;
186 for (CandidateInfo conflict : conflicts) {
187 final int level = ((MethodCandidateInfo)conflict).getApplicabilityLevel();
188 if (maxApplicabilityLevel > 0 && maxApplicabilityLevel != level) {
189 toFilter = true;
191 if (level > maxApplicabilityLevel) {
192 maxApplicabilityLevel = level;
196 if (toFilter) {
197 for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext();) {
198 CandidateInfo info = iterator.next();
199 final int level = ((MethodCandidateInfo)info).getApplicabilityLevel(); //cached
200 if (level < maxApplicabilityLevel) {
201 iterator.remove();
206 return maxApplicabilityLevel;
209 private static int getCheckLevel(MethodCandidateInfo method){
210 boolean visible = method.isAccessible();// && !method.myStaticProblem;
211 boolean available = method.isStaticsScopeCorrect();
212 return (visible ? 1 : 0) << 2 |
213 (available ? 1 : 0) << 1 |
214 (method.getCurrentFileResolveScope() instanceof PsiImportStaticStatement ? 0 : 1);
217 private enum Specifics {
218 FALSE,
219 TRUE,
220 CONFLICT
223 private static Specifics checkSubtyping(PsiType type1, PsiType type2) {
224 final boolean assignable2From1 = TypeConversionUtil.isAssignable(type2, type1, false);
225 final boolean assignable1From2 = TypeConversionUtil.isAssignable(type1, type2, false);
226 if (assignable1From2 || assignable2From1) {
227 if (assignable1From2 && assignable2From1) {
228 return null;
231 return assignable1From2 ? Specifics.FALSE : Specifics.TRUE;
234 return Specifics.CONFLICT;
237 private boolean isBoxingHappened(PsiType argType, PsiType parameterType) {
238 if (argType == null) return parameterType instanceof PsiPrimitiveType;
239 final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(myArgumentsList);
240 if (parameterType instanceof PsiClassType) {
241 parameterType = ((PsiClassType)parameterType).setLanguageLevel(languageLevel);
244 return TypeConversionUtil.boxingConversionApplicable(parameterType, argType);
247 private Specifics isMoreSpecific(final CandidateInfo info1, final CandidateInfo info2, final int applicabilityLevel) {
248 PsiMethod method1 = (PsiMethod)info1.getElement();
249 PsiMethod method2 = (PsiMethod)info2.getElement();
250 final PsiClass class1 = method1.getContainingClass();
251 final PsiClass class2 = method2.getContainingClass();
253 final PsiParameter[] params1 = method1.getParameterList().getParameters();
254 final PsiParameter[] params2 = method2.getParameterList().getParameters();
256 final PsiTypeParameter[] typeParameters1 = method1.getTypeParameters();
257 final PsiTypeParameter[] typeParameters2 = method2.getTypeParameters();
258 final PsiSubstitutor classSubstitutor1 = info1.getSubstitutor(); //substitutions for method type parameters will be ignored
259 final PsiSubstitutor classSubstitutor2 = info2.getSubstitutor();
260 PsiSubstitutor methodSubstitutor1 = PsiSubstitutor.EMPTY;
261 PsiSubstitutor methodSubstitutor2 = PsiSubstitutor.EMPTY;
263 final int max = Math.max(params1.length, params2.length);
264 PsiType[] types1 = new PsiType[max];
265 PsiType[] types2 = new PsiType[max];
266 for (int i = 0; i < max; i++) {
267 PsiType type1 = params1[Math.min(i, params1.length - 1)].getType();
268 PsiType type2 = params2[Math.min(i, params2.length - 1)].getType();
269 if (applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.VARARGS) {
270 if (type1 instanceof PsiEllipsisType && type2 instanceof PsiEllipsisType) {
271 type1 = ((PsiEllipsisType)type1).toArrayType();
272 type2 = ((PsiEllipsisType)type2).toArrayType();
274 else {
275 type1 = type1 instanceof PsiEllipsisType ? ((PsiArrayType)type1).getComponentType() : type1;
276 type2 = type2 instanceof PsiEllipsisType ? ((PsiArrayType)type2).getComponentType() : type2;
280 types1[i] = type1;
281 types2[i] = type2;
284 if (typeParameters1.length == 0 || typeParameters2.length == 0) {
285 if (typeParameters1.length > 0) {
286 final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(myArgumentsList.getProject()).getResolveHelper();
287 methodSubstitutor1 = calculateMethodSubstitutor(typeParameters1, types1, types2, resolveHelper);
289 else if (typeParameters2.length > 0) {
290 final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(myArgumentsList.getProject()).getResolveHelper();
291 methodSubstitutor2 = calculateMethodSubstitutor(typeParameters2, types2, types1, resolveHelper);
294 else {
295 PsiElementFactory factory = JavaPsiFacade.getInstance(myArgumentsList.getProject()).getElementFactory();
296 methodSubstitutor1 = factory.createRawSubstitutor(PsiSubstitutor.EMPTY, typeParameters1);
297 methodSubstitutor2 = factory.createRawSubstitutor(PsiSubstitutor.EMPTY, typeParameters2);
300 int[] boxingHappened = new int[2];
301 for (int i = 0; i < types1.length; i++) {
302 PsiType type1 = classSubstitutor1.substitute(methodSubstitutor1.substitute(types1[i]));
303 PsiType type2 = classSubstitutor2.substitute(methodSubstitutor2.substitute(types2[i]));
304 PsiType argType = i < myActualParameterTypes.length ? myActualParameterTypes[i] : null;
306 boxingHappened[0] += isBoxingHappened(argType, type1) ? 1 : 0;
307 boxingHappened[1] += isBoxingHappened(argType, type2) ? 1 : 0;
309 if (boxingHappened[0] == 0 && boxingHappened[1] > 0) return Specifics.TRUE;
310 if (boxingHappened[0] > 0 && boxingHappened[1] == 0) return Specifics.FALSE;
312 Specifics isMoreSpecific = null;
313 for (int i = 0; i < types1.length; i++) {
314 PsiType type1 = classSubstitutor1.substitute(methodSubstitutor1.substitute(types1[i]));
315 PsiType type2 = classSubstitutor2.substitute(methodSubstitutor2.substitute(types2[i]));
317 final Specifics specifics = checkSubtyping(type1, type2);
318 if (specifics == null) continue;
319 switch (specifics) {
320 case TRUE:
321 if (isMoreSpecific == Specifics.FALSE) return Specifics.CONFLICT;
322 isMoreSpecific = specifics;
323 break;
324 case FALSE:
325 if (isMoreSpecific == Specifics.TRUE) return Specifics.CONFLICT;
326 isMoreSpecific = specifics;
327 break;
328 case CONFLICT:
329 return Specifics.CONFLICT;
333 if (isMoreSpecific == null && class1 != class2) {
334 if (class2.isInheritor(class1, true) || class1.isInterface() && !class2.isInterface()) {
335 if (MethodSignatureUtil.isSubsignature(method1.getSignature(info1.getSubstitutor()), method2.getSignature(info2.getSubstitutor()))) {
336 isMoreSpecific = Specifics.FALSE;
339 else if (class1.isInheritor(class2, true) || class2.isInterface()) {
340 if (MethodSignatureUtil.isSubsignature(method2.getSignature(info2.getSubstitutor()), method1.getSignature(info1.getSubstitutor()))) {
341 isMoreSpecific = Specifics.TRUE;
345 if (isMoreSpecific == null) {
346 if (typeParameters1.length < typeParameters2.length) return Specifics.TRUE;
347 if (typeParameters1.length > typeParameters2.length) return Specifics.FALSE;
348 return Specifics.CONFLICT;
351 return isMoreSpecific;
354 private PsiSubstitutor calculateMethodSubstitutor(final PsiTypeParameter[] typeParameters,
355 final PsiType[] types1,
356 final PsiType[] types2,
357 final PsiResolveHelper resolveHelper) {
358 PsiSubstitutor substitutor = resolveHelper.inferTypeArguments(typeParameters, types1, types2, PsiUtil.getLanguageLevel(myArgumentsList));
359 for (PsiTypeParameter typeParameter : typeParameters) {
360 if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) {
361 substitutor = substitutor.put(typeParameter, TypeConversionUtil.typeParameterErasure(typeParameter));
364 return substitutor;