Skip to content

Commit

Permalink
Don't generate references for map key optionality (required/1 and opt…
Browse files Browse the repository at this point in the history
…ional/1)
  • Loading branch information
KronicDeth committed Jun 2, 2021
1 parent d38d55a commit 7170153
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 59 deletions.
63 changes: 63 additions & 0 deletions gen/org/elixir_lang/psi/scope/Type.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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
10 changes: 10 additions & 0 deletions src/org/elixir_lang/TargetElementEvaluator.kt
Expand Up @@ -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) {
Expand All @@ -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)
}
}
67 changes: 8 additions & 59 deletions src/org/elixir_lang/psi/impl/call/CallImpl.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand Down

0 comments on commit 7170153

Please sign in to comment.