From 71701532532601d2608d66d36d56192c7bee7a6f Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Sun, 4 Apr 2021 10:05:07 -0500 Subject: [PATCH] Don't generate references for map key optionality (required/1 and optional/1) --- gen/org/elixir_lang/psi/scope/Type.kt | 63 +++++++++++++++++ src/org/elixir_lang/TargetElementEvaluator.kt | 10 +++ src/org/elixir_lang/psi/impl/call/CallImpl.kt | 67 +++---------------- 3 files changed, 81 insertions(+), 59 deletions(-) diff --git a/gen/org/elixir_lang/psi/scope/Type.kt b/gen/org/elixir_lang/psi/scope/Type.kt index 6c4eccaf8..cf7fabc42 100644 --- a/gen/org/elixir_lang/psi/scope/Type.kt +++ b/gen/org/elixir_lang/psi/scope/Type.kt @@ -10,10 +10,13 @@ import com.intellij.util.xml.Resolve import org.elixir_lang.psi.* import org.elixir_lang.psi.call.Call import org.elixir_lang.psi.impl.ElixirPsiImplUtil.ENTRANCE +import org.elixir_lang.psi.impl.ElixirPsiImplUtil.functionName import org.elixir_lang.psi.impl.call.finalArguments import org.elixir_lang.psi.impl.call.macroChildCalls import org.elixir_lang.psi.impl.identifierName +import org.elixir_lang.psi.operation.Type import org.elixir_lang.psi.stub.type.UnmatchedUnqualifiedNoArgumentsCall +import org.elixir_lang.reference.ModuleAttribute import org.elixir_lang.reference.ModuleAttribute.Companion.isSpecificationName import org.elixir_lang.reference.ModuleAttribute.Companion.isTypeName import org.elixir_lang.structure_view.element.modular.Module @@ -82,3 +85,63 @@ abstract class Type : PsiScopeProcessor { .lastOrNull() ?: true } + +internal tailrec fun PsiElement.ancestorTypeSpec(): AtUnqualifiedNoParenthesesCall<*>? = + when (this) { + is AtUnqualifiedNoParenthesesCall<*> -> { + val identifierName = this.atIdentifier.identifierName() + + if (ModuleAttribute.isCallbackName(identifierName) || isTypeName(identifierName) || isSpecificationName(identifierName)) { + this + } else { + null + } + } + is Arguments, + is ElixirAccessExpression, + is ElixirKeywords, + is ElixirKeywordPair, + is ElixirMatchedParenthesesArguments, + is ElixirStructOperation, + is ElixirNoParenthesesOneArgument, + is ElixirNoParenthesesArguments, + is ElixirNoParenthesesKeywords, + is ElixirNoParenthesesKeywordPair, + is ElixirNoParenthesesManyStrictNoParenthesesExpression, + // For function type + is ElixirParentheticalStab, is ElixirStab, is ElixirStabOperation, is ElixirStabNoParenthesesSignature, + // containers + is ElixirList, is ElixirTuple, + // maps + is ElixirMapOperation, is ElixirMapArguments, is ElixirMapConstructionArguments, + is ElixirAssociations, is ElixirAssociationsBase, is ElixirContainerAssociationOperation, + // types + is Type, is Call -> parent.ancestorTypeSpec() + // `fn` anonymous function type just uses parentheses and `->`, like `(type1, type2 -> type3)` + is ElixirAnonymousFunction, is ElixirStabParenthesesSignature, + // BitStrings use `::` like types, but cannot contain type parameters or declarations + is ElixirBitString, + // Types can't be declared inside of bracket operations where they would be used as keys + is BracketOperation, is ElixirBracketArguments, + // Types cannot be declared in `else`, `rescue`, or `after` + is ElixirBlockList, is ElixirBlockItem, + is ElixirDoBlock, + // No types in EEx + is ElixirEex, is ElixirEexTag, + // No types in interpolation + is ElixirInterpolation, + // Map updates aren't used in type specifications unlike `ElixirMapConstructionArguments` + is ElixirMapUpdateArguments, + // types can't be defined at the file level and must be inside modules. + is ElixirFile, + // Any stab body has to be parent of a type + is ElixirStabBody -> null + else -> { + TODO() + } + } + +val OPTIONALITIES = arrayOf("optional", "required") + +fun Call.hasMapFieldOptionalityName(): Boolean = + parent is ElixirContainerAssociationOperation && functionName() in OPTIONALITIES && resolvedFinalArity() == 1 diff --git a/src/org/elixir_lang/TargetElementEvaluator.kt b/src/org/elixir_lang/TargetElementEvaluator.kt index 892b01859..0b6b516fa 100644 --- a/src/org/elixir_lang/TargetElementEvaluator.kt +++ b/src/org/elixir_lang/TargetElementEvaluator.kt @@ -4,6 +4,9 @@ import com.intellij.codeInsight.TargetElementEvaluatorEx2 import com.intellij.psi.PsiElement import org.elixir_lang.psi.AtNonNumericOperation import org.elixir_lang.psi.UnqualifiedNoArgumentsCall +import org.elixir_lang.psi.call.Call +import org.elixir_lang.psi.scope.ancestorTypeSpec +import org.elixir_lang.psi.scope.hasMapFieldOptionalityName class TargetElementEvaluator : TargetElementEvaluatorEx2() { override fun isAcceptableNamedParent(parent: PsiElement): Boolean = when (parent) { @@ -12,6 +15,13 @@ class TargetElementEvaluator : TargetElementEvaluatorEx2() { is AtNonNumericOperation -> false else -> super.isAcceptableNamedParent(parent) } + is Call -> { + if (parent.hasMapFieldOptionalityName() && parent.ancestorTypeSpec() != null) { + false + } else { + super.isAcceptableNamedParent(parent) + } + } else -> super.isAcceptableNamedParent(parent) } } diff --git a/src/org/elixir_lang/psi/impl/call/CallImpl.kt b/src/org/elixir_lang/psi/impl/call/CallImpl.kt index 8d9b558a2..f3bf4b20b 100644 --- a/src/org/elixir_lang/psi/impl/call/CallImpl.kt +++ b/src/org/elixir_lang/psi/impl/call/CallImpl.kt @@ -24,11 +24,11 @@ import org.elixir_lang.psi.impl.ElixirPsiImplUtil.* import org.elixir_lang.psi.operation.* import org.elixir_lang.psi.qualification.Qualified import org.elixir_lang.psi.qualification.Unqualified +import org.elixir_lang.psi.scope.ancestorTypeSpec +import org.elixir_lang.psi.scope.hasMapFieldOptionalityName import org.elixir_lang.psi.stub.call.Stub import org.elixir_lang.reference.Callable import org.elixir_lang.reference.Callable.Companion.isBitStreamSegmentOption -import org.elixir_lang.reference.ModuleAttribute.Companion.isSpecificationName -import org.elixir_lang.reference.ModuleAttribute.Companion.isTypeName import org.jetbrains.annotations.Contract import java.util.* import org.elixir_lang.psi.impl.macroChildCallList as psiElementToMacroChildCallList @@ -112,71 +112,20 @@ private fun PsiElement.isSlashInCaptureNameSlashArity(): Boolean = } -private fun Call.computeCallableReference(): PsiReference = +private fun Call.computeCallableReference(): PsiReference? = if (Callable.isDefiner(this)) { Callable.definer(this) } else { val ancestorTypeSpec = this.ancestorTypeSpec() if (ancestorTypeSpec != null) { - org.elixir_lang.reference.Type(ancestorTypeSpec, this) - } else { - Callable(this) - } - } - -private tailrec fun PsiElement.ancestorTypeSpec(): AtUnqualifiedNoParenthesesCall<*>? = - when (this) { - is AtUnqualifiedNoParenthesesCall<*> -> { - val identifierName = this.atIdentifier.identifierName() - - if (isTypeName(identifierName) || isSpecificationName(identifierName)) { - this - } else { + if (this.hasMapFieldOptionalityName()) { null + } else { + org.elixir_lang.reference.Type(ancestorTypeSpec, this) } - } - is Arguments, - is ElixirAccessExpression, - is ElixirKeywords, - is ElixirKeywordPair, - is ElixirMatchedParenthesesArguments, - is ElixirStructOperation, - is ElixirNoParenthesesOneArgument, - is ElixirNoParenthesesArguments, - is ElixirNoParenthesesKeywords, - is ElixirNoParenthesesKeywordPair, - is ElixirNoParenthesesManyStrictNoParenthesesExpression, - // For function type - is ElixirParentheticalStab, is ElixirStab, is ElixirStabOperation, is ElixirStabNoParenthesesSignature, - // containers - is ElixirList, is ElixirTuple, - // maps - is ElixirMapOperation, is ElixirMapArguments, is ElixirMapConstructionArguments, - is ElixirAssociations, is ElixirAssociationsBase, is ElixirContainerAssociationOperation, - // types - is Type, is Call -> parent.ancestorTypeSpec() - // `fn` anonymous function type just uses parentheses and `->`, like `(type1, type2 -> type3)` - is ElixirAnonymousFunction, is ElixirStabParenthesesSignature, - // BitStrings use `::` like types, but cannot contain type parameters or declarations - is ElixirBitString, - // Types can't be declared inside of bracket operations where they would be used as keys - is BracketOperation, is ElixirBracketArguments, - // Types cannot be declared in `else`, `rescue`, or `after` - is ElixirBlockList, is ElixirBlockItem, - is ElixirDoBlock, - // No types in EEx - is ElixirEex, is ElixirEexTag, - // No types in interpolation - is ElixirInterpolation, - // Map updates aren't used in type specifications unlike `ElixirMapConstructionArguments` - is ElixirMapUpdateArguments, - // types can't be defined at the file level and must be inside modules. - is ElixirFile, - // Any stab body has to be parent of a type - is ElixirStabBody -> null - else -> { - TODO() + } else { + Callable(this) } }