| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.psi.impl.source.tree.java; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.source.resolve.ParameterTypeInferencePolicy; |
| import com.intellij.psi.impl.source.resolve.ResolveCache; |
| import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil; |
| import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession; |
| import com.intellij.psi.infos.CandidateInfo; |
| import com.intellij.psi.infos.ClassCandidateInfo; |
| import com.intellij.psi.infos.MethodCandidateInfo; |
| import com.intellij.psi.scope.JavaScopeProcessorEvent; |
| import com.intellij.psi.scope.PsiConflictResolver; |
| import com.intellij.psi.scope.conflictResolvers.JavaMethodsConflictResolver; |
| import com.intellij.psi.scope.processor.MethodCandidatesProcessor; |
| import com.intellij.psi.util.MethodSignature; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.util.SmartList; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class MethodReferenceResolver implements ResolveCache.PolyVariantContextResolver<PsiMethodReferenceExpressionImpl> { |
| private static final Logger LOG = Logger.getInstance("#" + MethodReferenceResolver.class.getName()); |
| |
| @NotNull |
| @Override |
| public JavaResolveResult[] resolve(@NotNull final PsiMethodReferenceExpressionImpl reference, @NotNull final PsiFile containingFile, boolean incompleteCode) { |
| final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(reference); |
| |
| final PsiClass containingClass = qualifierResolveResult.getContainingClass(); |
| PsiSubstitutor substitutor = qualifierResolveResult.getSubstitutor(); |
| |
| if (containingClass != null) { |
| final PsiElement element = reference.getReferenceNameElement(); |
| final boolean isConstructor = reference.isConstructor(); |
| if (element instanceof PsiIdentifier || isConstructor) { |
| if (isConstructor && (containingClass.isEnum() || containingClass.hasModifierProperty(PsiModifier.ABSTRACT))) { |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| final PsiType functionalInterfaceType = getInterfaceType(reference); |
| final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); |
| final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); |
| final PsiSubstitutor functionalInterfaceSubstitutor = interfaceMethod != null ? LambdaUtil.getSubstitutor(interfaceMethod, resolveResult) : null; |
| final MethodSignature signature = interfaceMethod != null ? interfaceMethod.getSignature(functionalInterfaceSubstitutor) : null; |
| final PsiType interfaceMethodReturnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType); |
| if (isConstructor && containingClass.getConstructors().length == 0) { |
| if (interfaceMethod != null) { |
| final PsiClassType returnType = composeReturnType(containingClass, substitutor); |
| final InferenceSession session = new InferenceSession(containingClass.getTypeParameters(), substitutor, reference.getManager(), null); |
| if (!(session.isProperType(session.substituteWithInferenceVariables(returnType)) && session.isProperType(interfaceMethodReturnType))) { |
| session.registerReturnTypeConstraints(returnType, interfaceMethodReturnType); |
| substitutor = session.infer(); |
| } |
| } |
| ClassCandidateInfo candidateInfo = null; |
| final boolean isArray = containingClass == JavaPsiFacade.getElementFactory(reference.getProject()).getArrayClass(PsiUtil.getLanguageLevel(containingClass)); |
| if (signature == null || |
| !isArray && (containingClass.getContainingClass() == null || !isLocatedInStaticContext(containingClass, reference)) && signature.getParameterTypes().length == 0 || |
| isArray && arrayCreationSignature(signature)) { |
| candidateInfo = new ClassCandidateInfo(containingClass, substitutor); |
| } |
| return candidateInfo == null ? JavaResolveResult.EMPTY_ARRAY : new JavaResolveResult[]{candidateInfo}; |
| } |
| |
| final PsiConflictResolver conflictResolver = createResolver(reference, qualifierResolveResult, interfaceMethod, signature); |
| final MethodCandidatesProcessor processor = |
| new MethodCandidatesProcessor(reference, containingFile, new PsiConflictResolver[] {conflictResolver}, new SmartList<CandidateInfo>()) { |
| @Override |
| protected boolean acceptVarargs() { |
| return true; |
| } |
| |
| @Override |
| protected MethodCandidateInfo createCandidateInfo(@NotNull final PsiMethod method, |
| @NotNull final PsiSubstitutor substitutor, |
| final boolean staticProblem, |
| final boolean accessible, |
| final boolean varargs) { |
| final PsiExpressionList argumentList = getArgumentList(); |
| final PsiType[] typeParameters = reference.getTypeParameters(); |
| return new MethodCandidateInfo(method, substitutor, !accessible, staticProblem, argumentList, myCurrentFileContext, |
| argumentList != null ? argumentList.getExpressionTypes() : null, typeParameters.length > 0 ? typeParameters : null, |
| getLanguageLevel()) { |
| @Override |
| public boolean isVarargs() { |
| return varargs; |
| } |
| |
| @NotNull |
| @Override |
| public PsiSubstitutor inferTypeArguments(@NotNull ParameterTypeInferencePolicy policy, boolean includeReturnConstraint) { |
| return inferTypeArguments(); |
| } |
| |
| private PsiSubstitutor inferTypeArguments() { |
| if (interfaceMethod == null) return substitutor; |
| final InferenceSession session = new InferenceSession(method.getTypeParameters(), substitutor, reference.getManager(), reference); |
| final PsiSubstitutor psiSubstitutor = session.collectApplicabilityConstraints(reference, this, functionalInterfaceType); |
| if (psiSubstitutor != null) { |
| return psiSubstitutor; |
| } |
| |
| if (!session.repeatInferencePhases(false)) { |
| return substitutor; |
| } |
| |
| if (interfaceMethodReturnType != PsiType.VOID && interfaceMethodReturnType != null) { |
| if (method.isConstructor()) { |
| //todo |
| session.initBounds(reference, method.getContainingClass().getTypeParameters()); |
| } |
| final PsiType returnType = method.isConstructor() ? composeReturnType(containingClass, substitutor) : method.getReturnType(); |
| if (returnType != null) { |
| session.registerReturnTypeConstraints(returnType, interfaceMethodReturnType); |
| } |
| } |
| return session.infer(method.getParameterList().getParameters(), null, null); |
| } |
| }; |
| } |
| }; |
| processor.setIsConstructor(isConstructor); |
| processor.setName(isConstructor ? containingClass.getName() : element.getText()); |
| final PsiExpression expression = reference.getQualifierExpression(); |
| if (expression == null || !(expression.getType() instanceof PsiArrayType)) { |
| processor.setAccessClass(containingClass); |
| } |
| |
| if (qualifierResolveResult.isReferenceTypeQualified() && isLocatedInStaticContext(containingClass, reference)) { |
| processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null); |
| } |
| ResolveState state = ResolveState.initial().put(PsiSubstitutor.KEY, substitutor); |
| containingClass.processDeclarations(processor, state, reference, reference); |
| return processor.getResult(); |
| } |
| } |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| |
| private static boolean isLocatedInStaticContext(PsiClass containingClass, PsiMethodReferenceExpression reference) { |
| final PsiClass gContainingClass = containingClass.getContainingClass(); |
| if (gContainingClass == null || !containingClass.hasModifierProperty(PsiModifier.STATIC)) { |
| PsiClass aClass = null; |
| if (PsiTreeUtil.isAncestor(gContainingClass != null ? gContainingClass : containingClass, reference, false)) { |
| aClass = gContainingClass != null ? gContainingClass : containingClass; |
| } |
| if (PsiUtil.getEnclosingStaticElement(reference, aClass) != null) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected PsiType getInterfaceType(PsiMethodReferenceExpression reference) { |
| PsiType functionalInterfaceType = null; |
| final Map<PsiMethodReferenceExpression,PsiType> map = PsiMethodReferenceUtil.ourRefs.get(); |
| if (map != null) { |
| functionalInterfaceType = FunctionalInterfaceParameterizationUtil.getGroundTargetType(map.get(reference)); |
| } |
| |
| if (functionalInterfaceType == null) { |
| functionalInterfaceType = reference.getFunctionalInterfaceType(); |
| } |
| |
| return functionalInterfaceType; |
| } |
| |
| protected PsiConflictResolver createResolver(PsiMethodReferenceExpressionImpl referenceExpression, |
| PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult, |
| PsiMethod interfaceMethod, |
| MethodSignature signature) { |
| return new MethodReferenceConflictResolver(referenceExpression, qualifierResolveResult, signature, |
| interfaceMethod != null && interfaceMethod.isVarArgs()); |
| } |
| |
| private static PsiClassType composeReturnType(PsiClass containingClass, PsiSubstitutor substitutor) { |
| final boolean isRawSubst = PsiUtil.isRawSubstitutor(containingClass, substitutor); |
| return JavaPsiFacade.getElementFactory(containingClass.getProject()).createType(containingClass, isRawSubst ? PsiSubstitutor.EMPTY : substitutor); |
| } |
| |
| private static class MethodReferenceConflictResolver extends JavaMethodsConflictResolver { |
| private final MethodSignature mySignature; |
| private final PsiMethodReferenceExpressionImpl myReferenceExpression; |
| private final PsiMethodReferenceUtil.QualifierResolveResult myQualifierResolveResult; |
| private final boolean myFunctionalMethodVarArgs; |
| |
| private MethodReferenceConflictResolver(PsiMethodReferenceExpressionImpl referenceExpression, |
| PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult, |
| @Nullable MethodSignature signature, boolean varArgs) { |
| super(referenceExpression, signature != null ? signature.getParameterTypes() : PsiType.EMPTY_ARRAY, PsiUtil.getLanguageLevel(referenceExpression)); |
| myReferenceExpression = referenceExpression; |
| myQualifierResolveResult = qualifierResolveResult; |
| myFunctionalMethodVarArgs = varArgs; |
| mySignature = signature; |
| } |
| |
| @Override |
| protected int getPertinentApplicabilityLevel(@NotNull MethodCandidateInfo conflict) { |
| return conflict.isVarargs() ? MethodCandidateInfo.ApplicabilityLevel.VARARGS : MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY; |
| } |
| |
| @Nullable |
| @Override |
| public CandidateInfo resolveConflict(@NotNull List<CandidateInfo> conflicts) { |
| if (mySignature == null) return null; |
| |
| checkSameSignatures(conflicts); |
| checkAccessStaticLevels(conflicts, true); |
| |
| final PsiType[] argTypes = mySignature.getParameterTypes(); |
| boolean hasReceiver = PsiMethodReferenceUtil.hasReceiver(argTypes, myQualifierResolveResult, myReferenceExpression); |
| |
| final List<CandidateInfo> firstCandidates = new ArrayList<CandidateInfo>(); |
| final List<CandidateInfo> secondCandidates = new ArrayList<CandidateInfo>(); |
| |
| for (CandidateInfo conflict : conflicts) { |
| if (!(conflict instanceof MethodCandidateInfo)) continue; |
| final PsiMethod psiMethod = ((MethodCandidateInfo)conflict).getElement(); |
| |
| final PsiSubstitutor substitutor = conflict.getSubstitutor(); |
| final PsiType[] parameterTypes = psiMethod.getSignature(substitutor).getParameterTypes(); |
| |
| final boolean varargs = ((MethodCandidateInfo)conflict).isVarargs(); |
| if (varargs && (!psiMethod.isVarArgs() || myFunctionalMethodVarArgs)) continue; |
| |
| if ((varargs || argTypes.length == parameterTypes.length) && |
| PsiMethodReferenceUtil.isCorrectAssignment(parameterTypes, argTypes, varargs, 0)) { |
| firstCandidates.add(conflict); |
| } |
| |
| if (hasReceiver && |
| (varargs || argTypes.length == parameterTypes.length + 1) && |
| PsiMethodReferenceUtil.isCorrectAssignment(parameterTypes, argTypes, varargs, 1)) { |
| secondCandidates.add(conflict); |
| } |
| } |
| |
| if (myQualifierResolveResult.isReferenceTypeQualified() && myReferenceExpression.getReferenceNameElement() instanceof PsiIdentifier) { |
| //If the first search produces a static method, and no non-static method is applicable for the second search, then the result of the first search is the compile-time declaration. |
| CandidateInfo candidateInfo = filterStaticCorrectCandidates(firstCandidates, secondCandidates, true); |
| if (candidateInfo != null) { |
| return candidateInfo; |
| } |
| |
| //If the second search produces a non-static method, and no static method is applicable for the first search, then the result of the second search is the compile-time declaration. |
| candidateInfo = filterStaticCorrectCandidates(secondCandidates, firstCandidates, false); |
| if (candidateInfo != null) { |
| return candidateInfo; |
| } |
| } |
| |
| if (resolveConflicts(firstCandidates, secondCandidates, MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY)) { |
| return !firstCandidates.isEmpty() ? firstCandidates.get(0) : secondCandidates.get(0); |
| } |
| |
| if (resolveConflicts(firstCandidates, secondCandidates, MethodCandidateInfo.ApplicabilityLevel.VARARGS)) { |
| return !firstCandidates.isEmpty() ? firstCandidates.get(0) : secondCandidates.get(0); |
| } |
| |
| conflicts.clear(); |
| firstCandidates.addAll(secondCandidates); |
| conflicts.addAll(firstCandidates); |
| return null; |
| } |
| |
| private boolean resolveConflicts(List<CandidateInfo> firstCandidates, List<CandidateInfo> secondCandidates, int applicabilityLevel) { |
| |
| checkApplicability(firstCandidates); |
| checkSpecifics(firstCandidates, applicabilityLevel, myLanguageLevel); |
| |
| checkApplicability(secondCandidates); |
| checkSpecifics(secondCandidates, applicabilityLevel, myLanguageLevel); |
| |
| return firstCandidates.size() + secondCandidates.size() == 1; |
| } |
| |
| @Override |
| protected boolean nonComparable(@NotNull CandidateInfo method, @NotNull CandidateInfo conflict) { |
| if (method == conflict) return true; |
| PsiElement psiElement = method.getElement(); |
| PsiElement conflictElement = conflict.getElement(); |
| if (psiElement instanceof PsiMethod && conflictElement instanceof PsiMethod) { |
| if (((PsiMethod)psiElement).getParameterList().getParametersCount() != |
| ((PsiMethod)conflictElement).getParameterList().getParametersCount()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * 15.13.1 |
| */ |
| private static CandidateInfo filterStaticCorrectCandidates(List<CandidateInfo> firstCandidates, |
| List<CandidateInfo> secondCandidates, |
| boolean shouldBeStatic) { |
| if (firstCandidates.size() == 1) { |
| final CandidateInfo candidateInfo = firstCandidates.get(0); |
| final PsiElement element = candidateInfo.getElement(); |
| if (element instanceof PsiMethod) { |
| final boolean isStatic = ((PsiMethod)element).hasModifierProperty(PsiModifier.STATIC); |
| if (shouldBeStatic && isStatic || !shouldBeStatic && !isStatic) { |
| for (CandidateInfo secondCandidate : secondCandidates) { |
| final PsiElement psiElement = secondCandidate.getElement(); |
| if (psiElement instanceof PsiMethod) { |
| final boolean oppositeStatic = ((PsiMethod)psiElement).hasModifierProperty(PsiModifier.STATIC); |
| if (shouldBeStatic && !oppositeStatic || !shouldBeStatic && oppositeStatic) { |
| return null; |
| } |
| } |
| } |
| return candidateInfo; |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| private static boolean arrayCreationSignature(MethodSignature signature) { |
| final PsiType[] parameterTypes = signature.getParameterTypes(); |
| if (parameterTypes.length == 1 && parameterTypes[0] != null && TypeConversionUtil.isAssignable(PsiType.INT, parameterTypes[0])) { |
| return true; |
| } |
| return false; |
| } |
| } |