package org.elixir_lang.psi.scope; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiElement; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.util.Function; import org.elixir_lang.errorreport.Logger; import org.elixir_lang.psi.ElixirFile; import org.elixir_lang.psi.Import; import org.elixir_lang.psi.Modular; import org.elixir_lang.psi.call.Call; import org.elixir_lang.structure_view.element.modular.Module; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import static org.elixir_lang.psi.call.name.Module.KERNEL; import static org.elixir_lang.psi.call.name.Module.KERNEL_SPECIAL_FORMS; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.macroChildCalls; public abstract class CallDefinitionClause implements PsiScopeProcessor { /* * CONSTANTS */ protected static final Key<Call> IMPORT_CALL = new Key<Call>("IMPORT_CALL"); public static final Key<String> MODULAR_CANONICAL_NAME = new Key<String>("MODULAR_CANONICAL_NAME"); /* * Public Instance Methods */ /** * @param element candidate element. * @param state current state of resolver. * @return false to stop processing. */ @Override public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { boolean keepProcessing = true; if (element instanceof Call) { keepProcessing = execute((Call) element, state); } else if (element instanceof ElixirFile) { keepProcessing = implicitImports(element, state); } return keepProcessing; } @Nullable @Override public <T> T getHint(@NotNull Key<T> hintKey) { return null; } @Override public void handleEvent(@NotNull Event event, @Nullable Object associated) { } /* * Protected Instance Methods */ /** * Called on every {@link Call} where {@link org.elixir_lang.structure_view.element.CallDefinitionClause#is} is * {@code true} when checking tree with {@link #execute(Call, ResolveState)} * * @return {@code true} to keep searching up tree; {@code false} to stop searching. */ protected abstract boolean executeOnCallDefinitionClause(Call element, ResolveState state); /** * Whether to continue searching after each Module's children have been searched. * * @return {@code true} to keep searching up the PSI tree; {@code false} to stop searching. */ protected abstract boolean keepProcessing(); /* * Private Instance Methods */ private boolean execute(@NotNull Call element, @NotNull final ResolveState state) { boolean keepProcessing = true; if (org.elixir_lang.structure_view.element.CallDefinitionClause.is(element)) { keepProcessing = executeOnCallDefinitionClause(element, state); } else if (Import.is(element)) { final ResolveState importState = state.put(IMPORT_CALL, element); try { Import.callDefinitionClauseCallWhile( element, new Function<Call, Boolean>() { @Override public Boolean fun(Call callDefinitionClause) { return executeOnCallDefinitionClause(callDefinitionClause, importState); } } ); } catch (StackOverflowError stackOverflowError) { Logger.error(CallDefinitionClause.class, "StackOverflowError while processing import", element); } } else if (Module.is(element)) { Call[] childCalls = macroChildCalls(element); if (childCalls != null) { for (Call childCall : childCalls) { if (!execute(childCall, state)) { break; } } } // Only check MultiResolve.keepProcessing at the end of a Module to all multiple arities keepProcessing = keepProcessing(); if (keepProcessing) { // the implicit `import Kernel` and `import Kernel.SpecialForms` keepProcessing = implicitImports(element, state); } } return keepProcessing; } private boolean implicitImports(@NotNull PsiElement element, @NotNull ResolveState state) { Project project = element.getProject(); boolean keepProcessing = org.elixir_lang.reference.Module.forEachNavigationElement( project, KERNEL, new Function<PsiElement, Boolean>() { @Override public Boolean fun(PsiElement navigationElement) { boolean keepProcessingNavigationElements = true; if (navigationElement instanceof Call) { Call modular = (Call) navigationElement; keepProcessingNavigationElements = Modular.callDefinitionClauseCallWhile( modular, new Function<Call, Boolean>() { @Override public Boolean fun(Call callDefinitionClause) { return executeOnCallDefinitionClause(callDefinitionClause, state); } } ); } return keepProcessingNavigationElements; } } ); // the implicit `import Kernel.SpecialForms` if (keepProcessing) { ResolveState modularCanonicalNameState = state.put(MODULAR_CANONICAL_NAME, KERNEL_SPECIAL_FORMS); keepProcessing = org.elixir_lang.reference.Module.forEachNavigationElement( project, KERNEL_SPECIAL_FORMS, new Function<PsiElement, Boolean>() { @Override public Boolean fun(PsiElement navigationElement) { boolean keepProcessingNavigationElements = true; if (navigationElement instanceof Call) { Call modular = (Call) navigationElement; keepProcessingNavigationElements = Modular.callDefinitionClauseCallWhile( modular, new Function<Call, Boolean>() { @Override public Boolean fun(Call callDefinitionClause) { return executeOnCallDefinitionClause( callDefinitionClause, modularCanonicalNameState ); } } ); } return keepProcessingNavigationElements; } } ); } return keepProcessing; } }