fun resolveDeclaration()

in gdscript/src/main/kotlin/gdscript/reference/GdClassMemberReference.kt [68:195]


    fun resolveDeclaration(): PsiElement? {
        val cache = ResolveCache.getInstance(element.project)
        return cache.resolveWithCaching(
            this,
            ResolveCache.Resolver { _, _ ->
                val qualifierExpr = GdClassMemberUtil.calledUpon(element)

                // Helper to detect if a string like "A.B.C" refers to a (possibly nested) class available in the current file/scope
                val resolvesToClassChain = fun(name: String): Boolean {
                    if (name.isEmpty()) return false
                    if (GdClassUtil.getClassIdElement(name, element, element.project) != null) return true
                    val parts = name.split('.')
                    if (parts.isEmpty()) return false
                    var parent: PsiElement = GdClassUtil.getOwningClassElement(element)
                    // Start from file scope
                    parent = parent as? GdFile ?: element.containingFile
                    var current = PsiTreeUtil.getStubChildrenOfTypeAsList(parent, GdClassDeclTl::class.java)
                        .firstOrNull { it.name == parts[0] }
                    var i = 1
                    while (current != null && i < parts.size) {
                        current = PsiTreeUtil.getStubChildrenOfTypeAsList(current, GdClassDeclTl::class.java)
                            .firstOrNull { it.name == parts[i] }
                        i++
                    }
                    return current != null && i == parts.size
                }
                val targetClassDecl = qualifierExpr?.let {
                    val type = it.getReturnType()
                    if (type.isNotEmpty()) {
                        val target = GdClassUtil.getClassIdElement(type, element, element.project)
                        if (target != null) GdClassUtil.getOwningClassElement(target) as? GdClassDeclTl else null
                    } else null
                }

                // Determine if the access is static (on a class) based on the qualifier's declaration
                var isStaticAccess: Boolean? = null
                run {
                    val allRefs = PsiTreeUtil.getChildrenOfType(qualifierExpr, GdRefIdRef::class.java)
                    val leftRef = allRefs?.firstOrNull { it.text != element.text } ?: allRefs?.firstOrNull()
                    val decl = leftRef?.let { GdClassMemberUtil.findDeclaration(it)?.psi() }
                    isStaticAccess = inferStaticAccessFromDecl(decl)
                }

                // If qualifier is a simple identifier bound to a class-typed variable (initializer is a class id without .new/.instance),
                // disallow resolving any non-constructor members on it.
                run {
                    if (qualifierExpr != null) {
                        val refs = PsiTreeUtil.getChildrenOfType(qualifierExpr, GdRefIdRef::class.java)
                        val singleRef = refs?.singleOrNull()
                        if (singleRef != null) {
                            val d = GdClassMemberUtil.findDeclaration(singleRef)?.psi()
                            val init = when (d) {
                                is GdClassVarDeclTl -> d.expr
                                is GdVarDeclSt -> d.expr
                                else -> null
                            }
                            if (init != null && init !is GdCallEx) {
                                val initText = init.text.orEmpty()
                                if (initText.isNotEmpty() && resolvesToClassChain(initText)) {
                                    val name = element.text
                                    if (name != "new" && name != "instance") return@Resolver null
                                }
                            }
                        }
                    }
                }

                // If qualifier points to a class (directly or via class-typed variable) but target class couldn't be inferred,
                // do NOT block resolution here; we'll enforce static/instance rules after resolving the candidate.

                val resolved = GdClassMemberUtil.findDeclaration(element)?.psi()

                // If statically accessed, disallow resolving non-static members even if target class couldn't be inferred
                if (isStaticAccess == true) {
                    when (resolved) {
                        is GdMethodDeclTl -> if (!resolved.isStatic) return@Resolver null
                        is GdClassVarDeclTl -> if (!resolved.isStatic) return@Resolver null
                    }
                }

                // Additional guard: if qualifier denotes a class (directly or via class-typed var), block non-static members
                if (qualifierExpr != null && qualifierQualifiesAsClass(qualifierExpr)) {
                    when (resolved) {
                        is GdClassDeclTl -> { /* ok */ }
                        is GdMethodDeclTl -> if (!resolved.isStatic) return@Resolver null
                        is GdClassVarDeclTl -> if (!resolved.isStatic) return@Resolver null
                    }
                }

                if (targetClassDecl != null && resolved is PsiElement) {
                    val owner = GdClassUtil.getOwningClassElement(resolved)

                    // Enforce static vs instance access rules
                    when (resolved) {
                        is GdMethodDeclTl -> {
                            if (isStaticAccess == true && !resolved.isStatic) return@Resolver null
                        }
                        is GdClassVarDeclTl -> {
                            if (isStaticAccess == true && !resolved.isStatic) return@Resolver null
                        }
                        is GdClassDeclTl -> {
                            // Accessing a class via instance (obj.ClassName) is invalid
                            if (isStaticAccess == false) return@Resolver null
                        }
                    }

                    // Allow accessing inner classes via their direct parent class (e.g., A1.B1)
                    if (resolved is GdClassDeclTl) {
                        val enclosing = PsiTreeUtil.getStubOrPsiParentOfType(resolved, GdClassDeclTl::class.java)
                        if (enclosing != null && enclosing == targetClassDecl) {
                            // OK: accessing inner class on its parent
                        } else if (owner is GdClassDeclTl && owner != targetClassDecl) {
                            return@Resolver null
                        }
                    } else {
                        // For non-class members, require exact owning class match
                        if (owner is GdClassDeclTl && owner != targetClassDecl) {
                            return@Resolver null
                        }
                    }
                }

                resolved
            },
            false,
            false,
        )
    }