Getting declared variables within Kotlin scope

Answered

I'm able to get Java/Groovy variables within a given scope using PsiScopesUtil.treeWalkUp(). I'm also able to get Python variables within a given scope using ControlFlowCache.getScope().getNamedElements().

What are the equivalent methods for doing this in Kotlin? I get no results when I use PsiScopesUtil.treeWalkUp() inside a Kotlin function. Are there more universal ways of doing this? PsiElement.processDeclarations() looks promising, but I can't get it to return anything either.

6 comments
Comment actions Permalink

Hi Brandon,

Wow did you use PsiScopesUtil.treeWalkUp() inside Kotlin function?

0
Comment actions Permalink

Apologies for the late reply.

I have a fairly simple action that I would like to print the declared variables up to that point. Here is an example that attempts to do that but the PsiScopeProcessor is never executed:

class GetKotlinDeclaredVariables : AnAction() {

override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
val psiFile = e.getData(CommonDataKeys.PSI_FILE) ?: return
val currentElement = PsiUtilCore.getElementAtOffset(psiFile, editor.caretModel.offset)
val scopeProcessor = PsiScopeProcessor { element, state ->
println(element)
true
}
PsiScopesUtil.treeWalkUp(scopeProcessor, currentElement, null)
}
}
0
Comment actions Permalink

Hi Brandon,

Notice that PsiScopesUtil is Java-specific class (it's in java-psi-impl module) and that's probably why it's not executed (I didn't verify it, please check).

I would implement KtVisitor which collects the information you are interested in.

0
Comment actions Permalink

Hi Karol,

I tried using PsiTreeUtil.treeWalkUp which belongs to platform-api.jar, but it has the same effect. Looking closer, it seems most (if not all) Kotlin elements do not implement processDeclarations. For example, KtNamedFunction does not extend processDeclarations from PsiElementBase, which simply returns true. Do you know why Kotlin elements do not implement that function?

Also, I understand a KtVisitor could work, but doesn't that mean I would have to implement the logic which limits the visiting based on the scope? I would think that's already implemented since Kotlin already has the ability to recommend local variables during auto-completion.

0
Comment actions Permalink

Hi Brandon,

There are various ways, for example

fun function(element: PsiElement, ktElement: KtElement) {
element.parents(true).filterIsInstance<KtNamedFunction>().forEach {
println(it)
it.bodyBlockExpression?.statements?.filterIsInstance<KtNamedFunction>()?.let(::println)
}

val project = ktElement.project
val processor = fun(descriptor: DeclarationDescriptor) {
val psiElement = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) ?: return
println(psiElement)
}

val lexicalScope = ktElement.getResolutionScope()
lexicalScope.getContributedDescriptors().forEach(processor)
lexicalScope.getAllAccessibleVariables(Name.identifier("foo")).forEach(processor)

   val function: KtNamedFunction
  function.accept(namedFunctionVisitor {  })
  function.accept(namedFunctionRecursiveVisitor {  })
}

or you can try to convert an element to a light class (e.g. `org.jetbrains.kotlin.asJava.LightClassUtilsKt#toLightElements`) and pass it to `treeWalkUp`

0
Comment actions Permalink

Thanks Dmitry, the elements.parents(true) solution seems to be the easiest to understand/implement. The only change I made was to filter on KtBlockExpression instead of KtNamedFunction. This allows me to grab more declarations (e.g those inside Ktor get("/") expression).

I'm sure I'm not grabbing all the variables available at a specific location, but this is getting a decent amount of them:

val currentElement = PsiUtilCore.getElementAtOffset(psiFile, editor.caretModel.offset)
currentElement.parents(true).filterIsInstance<KtBlockExpression>().forEach {
it.statements.filterIsInstance<KtProperty>().forEach {
if (it.startOffset < currentElement.startOffset) {
println("property: " + it.text)
}
}
}
currentElement.parents(true).filterIsInstance<KtCatchClause>().forEach {
it.parameterList?.parameters?.forEach {
println("param: " + it.text)
}
}
0

Please sign in to leave a comment.