mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 18:58:00 +00:00
fix(cpp): namespace-qualified QNs so ADL and namespace lookups resolve
A symbol declared in `namespace ns` was collapsed to the file scope
(`proj.file.serialize`, not `proj.file.ns.serialize`) because the def walk
treated namespace_definition as a module-level container that contributes no QN
segment. With the namespace absent from the QN, an unqualified `serialize(d)`
resolved DIRECTLY to the top-level name and the argument-dependent-lookup path
(c_adl_resolve, which reads the namespace out of the argument type's QN) was
never reached.
Make a namespace contribute a QN segment in the def walk (it extends the
enclosing scope like a class but emits no def of its own; its members — both
free functions and nested classes — are walked under the extended scope). Mirror
it in c_lsp:
- c_process_function qualifies a namespace-scoped free function with
current_namespace instead of the file module, matching the def QN.
- c_lookup_member gains a short-name fallback so a type referenced by its
file-scoped or bare name (e.g. the return type of a namespace-scoped factory
used outside that namespace) still resolves to its namespace-qualified
registered QN.
- c_adl_resolve also tries the module-prefixed form of an argument type's
namespace, since `ns::Data` evaluates to the bare namespace `ns` while the
overload is registered under `<module>.ns.serialize`.
C#/PHP need the same treatment paired with their own resolvers (a def-only change
breaks their existing namespace handling) and are done separately.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
5b60045c55
commit
e1bf7cc68e
2 files changed
+98
-3
No files matched your search
@@ -2899,6 +2899,18 @@ static void extract_func_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
* proj.myapp.db.Func, not proj.myapp.db.conn.Func). Other langs unchanged. */
|
||||
def.qualified_name =
|
||||
cbm_fqn_compute_source_lang(a, ctx->project, ctx->rel_path, name, ctx->language);
|
||||
/* A free function declared inside a namespace (C++/C#/PHP) is qualified by
|
||||
* the namespace scope the def walk carries (enclosing_class_qn was extended
|
||||
* by is_namespace_scope_kind), so `ns::serialize` is `proj.file.ns.serialize`
|
||||
* — without this it collapses to the file scope and namespace-aware
|
||||
* resolution (ADL, namespace-function lookup) can never see it. Class methods
|
||||
* never reach here (they go through extract_class_methods), so a set
|
||||
* enclosing scope here is always a namespace. The out-of-line method path
|
||||
* below overrides this for `Ns::Cls::method` definitions. */
|
||||
if (ctx->enclosing_class_qn &&
|
||||
(ctx->language == CBM_LANG_CPP || ctx->language == CBM_LANG_CUDA)) {
|
||||
def.qualified_name = cbm_arena_sprintf(a, "%s.%s", ctx->enclosing_class_qn, name);
|
||||
}
|
||||
def.label = "Function";
|
||||
def.file_path = ctx->rel_path;
|
||||
def.start_line = ts_node_start_point(node).row + TS_LINE_OFFSET;
|
||||
@@ -5453,6 +5465,20 @@ static bool is_template_class_node(TSNode node, CBMLanguage lang) {
|
||||
}
|
||||
|
||||
// Compute the enclosing class QN for a class node (for nested class context).
|
||||
/* A namespace contributes a QN segment so a symbol declared in `namespace ns`
|
||||
* is `proj.file.ns.sym`, not a top-level `proj.file.sym`. Without the namespace
|
||||
* in the QN, namespace-aware resolution (C++ ADL) is starved: a bare call
|
||||
* collapses to the file scope and resolves directly instead. Unlike a class, a
|
||||
* namespace emits no def of its own — it only extends the enclosing scope for
|
||||
* its members. C#/PHP need the same treatment paired with their LSP resolvers
|
||||
* (a def-only change breaks their existing namespace handling), done separately. */
|
||||
static bool is_namespace_scope_kind(CBMLanguage lang, const char *kind) {
|
||||
if (lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA) {
|
||||
return strcmp(kind, "namespace_definition") == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *compute_class_qn(CBMExtractCtx *ctx, TSNode node, const char *saved_enclosing) {
|
||||
TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("name"));
|
||||
if (ts_node_is_null(name_node) && ctx->language == CBM_LANG_OBJC) {
|
||||
@@ -6002,6 +6028,21 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
|
||||
continue;
|
||||
}
|
||||
|
||||
/* A namespace extends the enclosing scope (so members are QN-qualified by
|
||||
* it) without being a def itself. Push its children (its declaration_list
|
||||
* body and any nested namespaces) under the extended scope so each member
|
||||
* is walked normally — functions AND classes, unlike a class body which
|
||||
* routes methods through extract_class_methods. Do NOT emit a def or run
|
||||
* the class/func paths on the namespace node itself. */
|
||||
if (is_namespace_scope_kind(ctx->language, kind)) {
|
||||
const char *new_enclosing = compute_class_qn(ctx, node, frame.enclosing_class_qn);
|
||||
uint32_t nsc = ts_node_child_count(node);
|
||||
for (int i = (int)nsc - SKIP_CHAR; i >= 0 && top < CBM_WALK_DEFS_STACK_CAP; i--) {
|
||||
stack[top++] = (walk_defs_frame_t){ts_node_child(node, (uint32_t)i), new_enclosing};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cbm_kind_in_set(node, spec->class_node_types)) {
|
||||
extract_class_def(ctx, node, spec);
|
||||
const char *new_enclosing = compute_class_qn(ctx, node, frame.enclosing_class_qn);
|
||||
|
||||
@@ -744,9 +744,17 @@ static const char *c_adl_resolve(CLSPContext *ctx, const char *name, TSNode call
|
||||
namespaces[ns_count++] = ns;
|
||||
}
|
||||
|
||||
// Try each namespace
|
||||
// Try each namespace, then the module-prefixed form of it. An argument type
|
||||
// written as `ns::Data` evaluates to the namespace QN `ns`, but the function
|
||||
// is registered under the module-qualified `<module>.ns.serialize`; without
|
||||
// the module-prefixed retry the namespace-scoped overload is never found.
|
||||
for (int i = 0; i < ns_count; i++) {
|
||||
const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, namespaces[i], name);
|
||||
if (!f && ctx->module_qn) {
|
||||
const char *prefixed =
|
||||
cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, namespaces[i]);
|
||||
f = cbm_registry_lookup_symbol(ctx->registry, prefixed, name);
|
||||
}
|
||||
if (f)
|
||||
return f->qualified_name;
|
||||
}
|
||||
@@ -2559,6 +2567,45 @@ static const CBMRegisteredFunc *c_lookup_member_depth(CLSPContext *ctx, const ch
|
||||
}
|
||||
}
|
||||
|
||||
/* Namespaced-type short-name fallback: a type name that resolves nowhere may
|
||||
* be a type declared inside a namespace whose registered QN carries the
|
||||
* namespace ("<module>.<ns>.Logger"), while the use site only knew the
|
||||
* file-scoped "<module>.Logger" or the bare "Logger" (e.g. the return type of
|
||||
* a namespace-scoped factory used outside that namespace). Resolve by the
|
||||
* SHORT name (last segment) against the registry and retry with the full QN.
|
||||
* Reached only after the direct/module/alias/base lookups all miss; prefers
|
||||
* an in-module match. Mirrors the C# short-name type fallback. */
|
||||
if (depth == 0 && ctx->registry) {
|
||||
const char *dot = strrchr(type_qn, '.');
|
||||
const char *shortn = dot ? dot + 1 : type_qn;
|
||||
size_t slen = strlen(shortn);
|
||||
const char *best_qn = NULL;
|
||||
for (int i = 0; i < ctx->registry->type_count; i++) {
|
||||
const char *q = ctx->registry->types[i].qualified_name;
|
||||
if (!q) {
|
||||
continue;
|
||||
}
|
||||
size_t qlen = strlen(q);
|
||||
if (qlen <= slen + 1 || q[qlen - slen - 1] != '.' ||
|
||||
strcmp(q + qlen - slen, shortn) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(q, type_qn) == 0) {
|
||||
continue; // already tried as-is above
|
||||
}
|
||||
best_qn = q;
|
||||
if (ctx->module_qn && strncmp(q, ctx->module_qn, strlen(ctx->module_qn)) == 0) {
|
||||
break; // prefer a match in the current module
|
||||
}
|
||||
}
|
||||
if (best_qn) {
|
||||
f = c_lookup_member_depth(ctx, best_qn, member_name, depth + 1);
|
||||
if (f) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -4204,8 +4251,15 @@ static void c_process_function(CLSPContext *ctx, TSNode func_node) {
|
||||
if (ctx->enclosing_class_qn && saved_class_qn == ctx->enclosing_class_qn &&
|
||||
!strchr(func_qn, '.')) {
|
||||
func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->enclosing_class_qn, func_qn);
|
||||
} else if (ctx->module_qn && !strchr(func_qn, '.')) {
|
||||
func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, func_qn);
|
||||
} else if (!strchr(func_qn, '.')) {
|
||||
/* A free function in a namespace is qualified by the namespace scope
|
||||
* (current_namespace is module_qn.ns), matching the def QN the extractor
|
||||
* now produces; outside any namespace this falls back to the file module
|
||||
* so non-namespaced free functions are unchanged. */
|
||||
const char *scope = ctx->current_namespace ? ctx->current_namespace : ctx->module_qn;
|
||||
if (scope) {
|
||||
func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", scope, func_qn);
|
||||
}
|
||||
}
|
||||
ctx->enclosing_func_qn = func_qn;
|
||||
|
||||
|
||||
Reference in new issue
Block a user