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,
)
}