in gdscript/src/main/kotlin/gdscript/psi/utils/GdClassMemberUtil.kt [286:428]
fun listLocalDeclarationsUpward(
element: PsiElement,
onlyLocalScope: Boolean = false,
hitLocal: BoolVal? = null,
): HashMap<String, PsiElement> {
val locals: HashMap<String, PsiElement> = hashMapOf()
// Special handling: inside an enum value initializer, only previously declared enum values are visible
run {
val enumValue = PsiTreeUtil.getParentOfType(element, GdEnumValue::class.java)
if (enumValue != null) {
val enumDecl = PsiTreeUtil.getParentOfType(enumValue, GdEnumDeclTl::class.java)
if (enumDecl != null) {
for (v in enumDecl.enumValueList) {
if (v == enumValue) break
val nmi = v.enumValueNmi
val name = nmi.name
if (!locals.containsKey(name)) locals[name] = v
}
}
}
}
// If inside a match branch GUARD (before ':'), bindings from the pattern list are visible
run {
var cur: PsiElement? = element
while (cur != null) {
val p = cur.parent
if (p is GdMatchBlock) {
val suiteStart = p.stmtOrSuite?.textRange?.startOffset ?: Int.MAX_VALUE
val refStart = element.textRange.startOffset
if (refStart < suiteStart) {
val vars = PsiTreeUtil.findChildrenOfType(p.patternList, GdVarNmi::class.java)
vars.forEach { v ->
val name = v.name
if (!locals.containsKey(name)) locals[name] = v
}
}
break
}
cur = p
}
}
var it: PsiElement = element
// To avoid matching self
when (it.parent) {
is GdClassVarDeclTl,
is GdVarDeclSt,
is GdConstDeclTl,
is GdConstDeclSt,
is GdEnumDeclTl,
is GdSetDecl,
is GdSignalDeclTl,
is GdMethodDeclTl,
is GdParam,
is GdForSt,
is GdBindingPattern,
-> {
it = it.parent
}
}
var isParam = false
when (it) {
is GdParam,
-> {
isParam = true
it = it.prevSibling ?: it.parent
}
}
while (true) {
val movedToParent = it.prevSibling == null
it = it.prevSibling ?: it.parent ?: break
if (it is PsiFile) break // avoid directory traversal
when (it) {
is GdClassVarDeclTl -> if (!locals.contains(it.name)) locals[it.name] = it
is GdVarDeclSt -> if (!locals.contains(it.name)) locals[it.name] = it
is GdConstDeclTl -> if (!locals.contains(it.name)) locals[it.name] = it
is GdConstDeclSt -> if (!locals.contains(it.name)) locals[it.name] = it
is GdEnumDeclTl -> if (!locals.contains(it.name)) locals[it.name] = it
is GdSignalDeclTl -> if (!locals.contains(it.name)) locals[it.name] = it
is GdParam -> {
if (!locals.contains(it.varNmi.name)) locals[it.varNmi.name] = it
}
is GdForSt -> if (movedToParent && !locals.contains(it.varNmi?.name ?: "")) locals[it.varNmi?.name
?: ""] = it
is GdPatternList -> {
// Pattern binding variables are visible both in the guard (when ...) and in the branch body.
// Collect them whenever we encounter the pattern list while walking upwards.
PsiTreeUtil.findChildrenOfType(it, GdVarNmi::class.java)
.forEach { v -> if (!locals.contains(v.name)) locals[v.name] = v }
}
is GdMatchBlock -> {
// Be robust: when reaching the match block, also collect bindings from its pattern list
PsiTreeUtil.findChildrenOfType(it.patternList, GdVarNmi::class.java)
.forEach { v -> if (!locals.contains(v.name)) locals[v.name] = v }
}
is GdSetDecl -> {
if (movedToParent) {
if (!locals.contains(it.varNmi?.name.orEmpty())) locals[it.varNmi?.name.orEmpty()] = it.varNmi!!
}
}
is GdFuncDeclEx -> {
if (movedToParent && !isParam) {
it.paramList?.paramList?.forEach { p ->
if (!locals.contains(p.varNmi.name)) locals[p.varNmi.name] = p
}
}
}
is GdMethodDeclTl -> {
if (onlyLocalScope) {
if (!isParam) {
it.paramList?.paramList?.forEach { p ->
if (!locals.contains(p.varNmi.name)) locals[p.varNmi.name] = p
}
}
if (hitLocal != null) hitLocal.value = true
break
}
if (movedToParent) {
it.paramList?.paramList?.forEach { p ->
if (!locals.contains(p.varNmi.name)) locals[p.varNmi.name] = p
}
} else {
if (!locals.contains(it.name)) locals[it.name] = it
}
}
// End of scope
is GdClassDeclTl -> { if (movedToParent) { break } }
}
}
return locals
}