Files
openide/java/java-impl/src/com/intellij/codeInsight/JavaTargetElementEvaluator.java
Tagir Valeev 33edc0ffa4 [java-psi] Rework extension methods support, according to review IJ-CR-4151
GitOrigin-RevId: 29d547dc38b2feba7e55b8fcfb420094341219b8
2020-12-03 06:06:11 +00:00

407 lines
17 KiB
Java

// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiExtensionMethod;
import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
import com.intellij.psi.impl.light.LightRecordMember;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.*;
import com.intellij.util.BitUtil;
import com.intellij.util.Processor;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class JavaTargetElementEvaluator extends TargetElementEvaluatorEx2 implements TargetElementUtilExtender{
private static final int NEW_AS_CONSTRUCTOR = 0x04;
private static final int THIS_ACCEPTED = 0x10;
private static final int SUPER_ACCEPTED = 0x20;
@Override
public int getAllAdditionalFlags() {
return NEW_AS_CONSTRUCTOR | THIS_ACCEPTED | SUPER_ACCEPTED;
}
/**
* Accepts THIS or SUPER or USE_DFA but not NEW_AS_CONSTRUCTOR.
*/
@Override
public int getAdditionalDefinitionSearchFlags() {
return THIS_ACCEPTED | SUPER_ACCEPTED;
}
/**
* Accepts NEW_AS_CONSTRUCTOR but not THIS or SUPER.
*/
@Override
public int getAdditionalReferenceSearchFlags() {
return NEW_AS_CONSTRUCTOR;
}
@Nullable
@Override
public PsiElement adjustTargetElement(Editor editor, int offset, int flags, @NotNull PsiElement targetElement) {
if (targetElement instanceof PsiKeyword) {
if (targetElement.getParent() instanceof PsiThisExpression) {
if (!BitUtil.isSet(flags, THIS_ACCEPTED)) return null;
PsiType type = ((PsiThisExpression)targetElement.getParent()).getType();
if (!(type instanceof PsiClassType)) return null;
return ((PsiClassType)type).resolve();
}
if (targetElement.getParent() instanceof PsiSuperExpression) {
if (!BitUtil.isSet(flags, SUPER_ACCEPTED)) return null;
PsiType type = ((PsiSuperExpression)targetElement.getParent()).getType();
if (!(type instanceof PsiClassType)) return null;
return ((PsiClassType)type).resolve();
}
}
return super.adjustTargetElement(editor, offset, flags, targetElement);
}
@Override
public boolean isAcceptableNamedParent(@NotNull PsiElement parent) {
return !(parent instanceof PsiDocTag) && !(parent instanceof PsiAnonymousClass);
}
@Override
@NotNull
public ThreeState isAcceptableReferencedElement(@NotNull final PsiElement element, final PsiElement referenceOrReferencedElement) {
if (isEnumConstantReference(element, referenceOrReferencedElement)) return ThreeState.NO;
return super.isAcceptableReferencedElement(element, referenceOrReferencedElement);
}
private static boolean isEnumConstantReference(final PsiElement element, final PsiElement referenceOrReferencedElement) {
return element != null &&
element.getParent() instanceof PsiEnumConstant &&
referenceOrReferencedElement instanceof PsiMethod && ((PsiMethod)referenceOrReferencedElement).isConstructor();
}
@Nullable
@Override
public PsiElement adjustReferenceOrReferencedElement(@NotNull PsiFile file,
@NotNull Editor editor,
int offset,
int flags,
@Nullable PsiElement refElement) {
PsiReference ref = null;
if (refElement == null) {
ref = TargetElementUtil.findReference(editor, offset);
if (ref instanceof PsiJavaReference) {
refElement = ((PsiJavaReference)ref).advancedResolve(true).getElement();
}
else if (ref == null) {
final PsiElement element = file.findElementAt(offset);
if (element != null) {
final PsiElement parent = element.getParent();
if (parent instanceof PsiFunctionalExpression &&
(PsiUtil.isJavaToken(element, JavaTokenType.ARROW) || PsiUtil.isJavaToken(element, JavaTokenType.DOUBLE_COLON))) {
refElement = LambdaUtil.resolveFunctionalInterfaceClass((PsiFunctionalExpression)parent);
}
else if (element instanceof PsiKeyword &&
parent instanceof PsiTypeElement &&
((PsiTypeElement)parent).isInferredType()) {
refElement = PsiUtil.resolveClassInType(((PsiTypeElement)parent).getType());
}
}
}
}
if (refElement != null) {
if (BitUtil.isSet(flags, NEW_AS_CONSTRUCTOR)) {
if (ref == null) {
ref = TargetElementUtil.findReference(editor, offset);
}
if (ref != null) {
PsiElement parent = ref.getElement().getParent();
if (parent instanceof PsiAnonymousClass) {
parent = parent.getParent();
}
if (parent instanceof PsiNewExpression) {
PsiMethod constructor = ((PsiNewExpression)parent).resolveConstructor();
if (constructor != null) {
refElement = constructor;
} else if (refElement instanceof PsiClass && ((PsiClass)refElement).getConstructors().length > 0) {
return null;
}
}
}
}
if (refElement instanceof PsiMirrorElement) {
return ((PsiMirrorElement)refElement).getPrototype();
}
if (refElement instanceof PsiClass) {
final PsiFile containingFile = refElement.getContainingFile();
if (containingFile != null && containingFile.getVirtualFile() == null) { // in mirror file of compiled class
String qualifiedName = ((PsiClass)refElement).getQualifiedName();
if (qualifiedName == null) return null;
return JavaPsiFacade.getInstance(refElement.getProject()).findClass(qualifiedName, refElement.getResolveScope());
}
}
if (refElement instanceof PsiExtensionMethod) {
refElement = ((PsiExtensionMethod)refElement).getTargetMethod();
}
if (refElement instanceof LightRecordMember) {
return ((LightRecordMember)refElement).getRecordComponent();
}
if (refElement instanceof LightRecordCanonicalConstructor) {
return ((LightRecordCanonicalConstructor)refElement).getContainingClass();
}
}
return super.adjustReferenceOrReferencedElement(file, editor, offset, flags, refElement);
}
@Nullable
@Override
public PsiElement getNamedElement(@NotNull PsiElement element) {
if (element instanceof PsiIdentifier) {
PsiElement parent = element.getParent();
if (parent instanceof PsiClass && element.equals(((PsiClass)parent).getNameIdentifier()) ||
parent instanceof PsiVariable && element.equals(((PsiVariable)parent).getNameIdentifier()) ||
parent instanceof PsiMethod && element.equals(((PsiMethod)parent).getNameIdentifier()) ||
parent instanceof PsiLabeledStatement && element.equals(((PsiLabeledStatement)parent).getLabelIdentifier())) {
return parent;
}
if (parent instanceof PsiJavaModuleReferenceElement) {
PsiElement grand = parent.getParent();
if (grand instanceof PsiJavaModule) {
return grand;
}
}
}
return null;
}
@Nullable
public static PsiReferenceExpression findReferenceExpression(Editor editor) {
final PsiReference ref = TargetElementUtil.findReference(editor);
return ref instanceof PsiReferenceExpression ? (PsiReferenceExpression)ref : null;
}
@Nullable
@Override
public PsiElement adjustReference(@NotNull final PsiReference ref) {
final PsiElement parent = ref.getElement().getParent();
if (parent instanceof PsiMethodCallExpression) return parent;
return super.adjustReference(ref);
}
@Nullable
@Override
public PsiElement adjustElement(Editor editor, int flags, @Nullable PsiElement element, @Nullable PsiElement contextElement) {
if (element instanceof PsiAnonymousClass) {
return ((PsiAnonymousClass)element).getBaseClassType().resolve();
}
return element;
}
@Override
@Nullable
public Collection<PsiElement> getTargetCandidates(@NotNull PsiReference reference) {
PsiElement parent = reference.getElement().getParent();
if (parent instanceof PsiMethodCallExpression ||
parent instanceof PsiNewExpression && !((PsiNewExpression)parent).isArrayCreation()) {
PsiCallExpression callExpr = (PsiCallExpression)parent;
boolean allowStatics = false;
PsiExpression qualifier = callExpr instanceof PsiMethodCallExpression ? ((PsiMethodCallExpression)callExpr).getMethodExpression().getQualifierExpression()
: ((PsiNewExpression)callExpr).getQualifier();
if (qualifier == null) {
allowStatics = true;
}
else if (qualifier instanceof PsiJavaCodeReferenceElement) {
PsiElement referee = ((PsiJavaCodeReferenceElement)qualifier).advancedResolve(true).getElement();
if (referee instanceof PsiClass) allowStatics = true;
}
PsiResolveHelper helper = JavaPsiFacade.getInstance(parent.getProject()).getResolveHelper();
PsiElement[] candidates = PsiUtil.mapElements(helper.getReferencedMethodCandidates(callExpr, false));
final Collection<PsiElement> methods = new LinkedHashSet<>();
for (PsiElement candidate1 : candidates) {
PsiMethod candidate = (PsiMethod)candidate1;
if (candidate.hasModifierProperty(PsiModifier.STATIC) && !allowStatics) continue;
List<PsiMethod> supers = Arrays.asList(candidate.findSuperMethods());
if (supers.isEmpty()) {
methods.add(candidate);
}
else {
methods.addAll(supers);
}
}
return methods;
}
return super.getTargetCandidates(reference);
}
@Override
@Nullable
public PsiElement getGotoDeclarationTarget(@NotNull final PsiElement element, @Nullable final PsiElement navElement) {
if (navElement == element && element instanceof PsiCompiledElement && element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
if (method.isConstructor() && method.getParameterList().isEmpty()) {
PsiClass aClass = method.getContainingClass();
PsiElement navClass = aClass == null ? null : aClass.getNavigationElement();
if (aClass != navClass) return navClass;
}
}
return super.getGotoDeclarationTarget(element, navElement);
}
@Override
public boolean includeSelfInGotoImplementation(@NotNull final PsiElement element) {
if (element instanceof PsiModifierListOwner && ((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.ABSTRACT)) {
return false;
}
return super.includeSelfInGotoImplementation(element);
}
@Override
public boolean acceptImplementationForReference(@Nullable PsiReference reference, @NotNull PsiElement element) {
if (reference instanceof PsiReferenceExpression && element instanceof PsiMember) {
return getClassesWithMember(reference, (PsiMember)element) != null;
}
return super.acceptImplementationForReference(reference, element);
}
private static PsiClass @Nullable [] getClassesWithMember(final PsiReference reference, final PsiMember member) {
return ApplicationManager.getApplication().runReadAction(new Computable<>() {
@Override
public PsiClass[] compute() {
PsiClass containingClass = member.getContainingClass();
final PsiExpression expression = ((PsiReferenceExpression)reference).getQualifierExpression();
PsiClass psiClass;
if (reference instanceof PsiMethodReferenceExpression) {
psiClass = PsiMethodReferenceUtil.getQualifierResolveResult((PsiMethodReferenceExpression)reference).getContainingClass();
}
else if (expression != null) {
psiClass = PsiUtil.resolveClassInType(expression.getType());
}
else {
if (member instanceof PsiClass) {
psiClass = (PsiClass)member;
final PsiElement resolve = ((PsiReferenceExpression)reference).advancedResolve(true).getElement();
if (resolve instanceof PsiClass) {
containingClass = (PsiClass)resolve;
}
}
else {
psiClass = PsiTreeUtil.getParentOfType((PsiReferenceExpression)reference, PsiClass.class);
}
}
if (containingClass == null && psiClass == null) return PsiClass.EMPTY_ARRAY;
if (containingClass != null) {
PsiClass[] inheritors = getInheritors(containingClass, psiClass, new HashSet<>());
return inheritors.length == 0 ? null : inheritors;
}
return null;
}
private PsiClass[] getInheritors(PsiClass containingClass, PsiClass psiClass, Set<? super PsiClass> visited) {
if (psiClass instanceof PsiTypeParameter) {
List<PsiClass> result = new ArrayList<>();
for (PsiClassType classType : psiClass.getExtendsListTypes()) {
PsiClass aClass = classType.resolve();
if (aClass != null && visited.add(aClass)) {
ContainerUtil.addAll(result, getInheritors(containingClass, aClass, visited));
}
}
return result.toArray(PsiClass.EMPTY_ARRAY);
}
PsiElementFindProcessor<PsiClass> processor1 = new PsiElementFindProcessor<>(containingClass);
while (psiClass != null) {
if (!processor1.process(psiClass) ||
!ClassInheritorsSearch.search(containingClass).forEach(new PsiElementFindProcessor<>(psiClass)) ||
!ClassInheritorsSearch.search(psiClass).forEach(processor1)) {
return new PsiClass[]{psiClass};
}
psiClass = psiClass.getContainingClass();
}
return PsiClass.EMPTY_ARRAY;
}
});
}
@Override
@Nullable
public SearchScope getSearchScope(Editor editor, @NotNull final PsiElement element) {
final PsiReferenceExpression referenceExpression = editor != null ? findReferenceExpression(editor) : null;
if (referenceExpression != null && element instanceof PsiMethod) {
if (!PsiUtil.canBeOverridden((PsiMethod)element)) {
return element.getUseScope();
}
final PsiClass[] memberClass = getClassesWithMember(referenceExpression, (PsiMember)element);
if (memberClass != null && memberClass.length == 1) {
PsiClass aClass = memberClass[0];
return CachedValuesManager.getCachedValue(aClass, () -> {
return new CachedValueProvider.Result<>(getHierarchyScope(aClass, aClass.getUseScope(), true),
PsiModificationTracker.MODIFICATION_COUNT);
});
}
}
return super.getSearchScope(editor, element);
}
/**
* Narrow given scope to include only those places where methods called on given class qualifier could be defined.
*
* @param aClass qualifier type class
* @param scope a scope to narrow
* @param areFunctionalInheritorsExpected true, iff scope should be ignored for functional interfaces to avoid eager functional expressions search
* @return narrowed scope or null if <code>aClass</code> is a functional interface and functional expressions can be processed by the caller
*/
@Contract("_,_,false->!null")
public @Nullable static SearchScope getHierarchyScope(@NotNull PsiClass aClass,
@NotNull SearchScope scope,
boolean areFunctionalInheritorsExpected) {
final List<PsiClass> classesToSearch = new ArrayList<>();
classesToSearch.add(aClass);
classesToSearch.addAll(ClassInheritorsSearch.search(aClass, scope, true).findAll());
final Set<PsiClass> supers = new HashSet<>();
for (PsiClass psiClass : classesToSearch) {
if (areFunctionalInheritorsExpected && LambdaUtil.isFunctionalClass(psiClass)) {
return null;
}
supers.addAll(InheritanceUtil.getSuperClasses(psiClass));
}
final List<PsiElement> elements = new ArrayList<>();
elements.addAll(classesToSearch);
elements.addAll(supers);
return new LocalSearchScope(PsiUtilCore.toPsiElementArray(elements));
}
private static class PsiElementFindProcessor<T extends PsiClass> implements Processor<T> {
private final T myElement;
PsiElementFindProcessor(T t) {
myElement = t;
}
@Override
public boolean process(T t) {
if (InheritanceUtil.isInheritorOrSelf(t, myElement, true)) return false;
return !myElement.getManager().areElementsEquivalent(myElement, t);
}
}
}