/* * Copyright 2012-2014 Sergey Ignatov * * 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 org.intellij.erlang.psi.impl; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import org.intellij.erlang.bif.ErlangBifTable; import org.intellij.erlang.bif.ErlangOperatorTable; import org.intellij.erlang.index.ErlangModuleIndex; import org.intellij.erlang.psi.*; import org.intellij.erlang.sdk.ErlangSdkRelease; import org.intellij.erlang.sdk.ErlangSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class ErlangFunctionReferenceImpl extends ErlangPsiPolyVariantCachingReferenceBase<ErlangQAtom> implements ErlangFunctionReference { @Nullable private final ErlangQAtom myModuleAtom; private final String myReferenceName; private final int myArity; public ErlangFunctionReferenceImpl(@NotNull ErlangQAtom element, @Nullable ErlangQAtom moduleAtom, int arity) { super(element, ErlangPsiImplUtil.getTextRangeForReference(element)); myReferenceName = ErlangPsiImplUtil.getNameIdentifier(element).getText(); myModuleAtom = moduleAtom; myArity = arity; } @Override public PsiElement resolveInner() { if (suppressResolve()) return null; // for #132 if (myModuleAtom != null) { String moduleName = ErlangPsiImplUtil.getName(myModuleAtom); ErlangFunction explicitFunction = getExternalFunction(moduleName); boolean resolveToCallSite = explicitFunction == null && ( ErlangBifTable.isBif(moduleName, myReferenceName, myArity) || ErlangOperatorTable.canBeInvokedAsFunction(moduleName, myReferenceName, myArity) || myReferenceName.equals(ErlangBifTable.MODULE_INFO) && (myArity == 1 || myArity == 0) ); return resolveToCallSite ? getElement() : explicitFunction; } ErlangFile file = ObjectUtils.tryCast(getElement().getContainingFile(), ErlangFile.class); if (file == null) return null; ErlangFunction declaredFunction = file.getFunction(myReferenceName, myArity); if (declaredFunction != null) return declaredFunction; ErlangFunction fromImport = resolveImport(file.getImportedFunction(myReferenceName, myArity)); if (fromImport != null) return fromImport; List<ErlangFunction> declaredInIncludes = ErlangPsiImplUtil.getErlangFunctionsFromIncludes(file, false, myReferenceName, myArity); if (!declaredInIncludes.isEmpty()) return ContainerUtil.getFirstItem(declaredInIncludes); List<ErlangImportFunction> importedInIncludes = ErlangPsiImplUtil.getImportsFromIncludes(file, false, myReferenceName, myArity); for (ErlangImportFunction importFromInclude : importedInIncludes) { ErlangFunction importedFunction = resolveImport(importFromInclude); if (importedFunction != null) return importedFunction; } if (!file.isNoAutoImport(myReferenceName, myArity)) { ErlangFunction implicitFunction = getExternalFunction("erlang"); if (implicitFunction != null) return implicitFunction; ErlangSdkRelease release = ErlangSdkType.getRelease(file); if ((release == null || release.needBifCompletion("erlang")) && ErlangBifTable.isBif("erlang", myReferenceName, myArity) || ErlangBifTable.isBif("", myReferenceName, myArity)) return getElement(); } return null; } @NotNull @Override public ResolveResult[] multiResolve(boolean incompleteCode) { if (suppressResolve()) return ResolveResult.EMPTY_ARRAY; // for #132 // todo: use incompleteCode if (resolve() != null && !incompleteCode) return ResolveResult.EMPTY_ARRAY; Collection<ErlangFunction> result; if (myModuleAtom != null) { result = getErlangFunctionsFromModule(ErlangPsiImplUtil.getName(myModuleAtom)); } else { PsiFile containingFile = getElement().getContainingFile(); if (containingFile instanceof ErlangFile) { ErlangFile erlangFile = (ErlangFile) containingFile; result = new ArrayList<>(); for (ErlangImportFunction importFunction : erlangFile.getImportedFunctions()) { if (myReferenceName.equals(ErlangPsiImplUtil.getName(importFunction))) { ContainerUtil.addIfNotNull(result, resolveImport(importFunction)); } } result.addAll(erlangFile.getFunctionsByName(myReferenceName)); result.addAll(getErlangFunctionsFromModule("erlang")); } else { result = ContainerUtil.emptyList(); } } return PsiElementResolveResult.createResults(result); } private Collection<ErlangFunction> getErlangFunctionsFromModule(String moduleFileName) { Project project = getElement().getProject(); Collection<ErlangFunction> result = new ArrayList<>(); for (ErlangFile file : ErlangModuleIndex.getFilesByName(project, moduleFileName, GlobalSearchScope.allScope(project))) { result.addAll(file.getFunctionsByName(myReferenceName)); } return result; } private boolean suppressResolve() { return PsiTreeUtil.getParentOfType(myElement, ErlangCallbackSpec.class) != null; } @Override public boolean isReferenceTo(PsiElement element) { return getElement().getManager().areElementsEquivalent(resolve(), element); } @Nullable private ErlangFunction getExternalFunction(@NotNull String moduleFileName) { Project project = getElement().getProject(); List<ErlangFunction> result = new ArrayList<>(); for (ErlangFile file : ErlangModuleIndex.getFilesByName(project, moduleFileName, GlobalSearchScope.allScope(project))) { ContainerUtil.addAllNotNull(result, file.getFunction(myReferenceName, myArity)); ContainerUtil.addAllNotNull(result, ErlangPsiImplUtil.getErlangFunctionsFromIncludes(file, false, myReferenceName, myArity)); } return ContainerUtil.getFirstItem(result); } @NotNull @Override public Object[] getVariants() { if (PsiTreeUtil.getParentOfType(myElement, ErlangExportFunction.class) != null) return EMPTY_ARRAY; return ArrayUtil.toObjectArray(ErlangPsiImplUtil.getFunctionLookupElements(getElement().getContainingFile(), true, myModuleAtom)); } @Override public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { ErlangPsiImplUtil.renameAtom(getElement().getAtom(), newElementName); return getElement(); } @Override public String getSignature() { return ErlangPsiImplUtil.createFunctionPresentation(myReferenceName, myArity); } @Override public String getName() { return myReferenceName; } @Override public int getArity() { return myArity; } @Nullable private static ErlangFunction resolveImport(@Nullable ErlangImportFunction importFunction) { PsiReference reference = importFunction != null ? importFunction.getReference() : null; PsiElement resolve = reference != null ? reference.resolve() : null; return ObjectUtils.tryCast(resolve, ErlangFunction.class); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ErlangFunctionReferenceImpl)) return false; if (!super.equals(o)) return false; return !(myModuleAtom != null && !myModuleAtom.equals(((ErlangFunctionReferenceImpl) o).myModuleAtom)); } @Override public int hashCode() { return myModuleAtom != null ? 31 * super.hashCode() + myModuleAtom.hashCode() : super.hashCode(); } }