fun listLocalDeclarationsUpward()

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
    }