mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-27 06:53:11 +08:00
fix(kotlin): resolve default-package project calls
Index Java and Kotlin definitions with an internal <jvm-default-package> key in src/pipeline/pass_lsp_cross.c so cbm_pxc_filter_defs_for_file() retains sibling-file declarations when no package header exists. The hash table rejects empty keys, which previously removed Algo.kt::maxOf before cross-file resolution. Initialize cbm_run_kotlin_lsp_cross() with the file module QN so default-package caller identities match extracted graph QNs such as <project>.Main.run. In pxc_append_results(), index resolutions by caller and callee short name and let an equal-confidence project-wide result replace the earlier per-file guess; this changes kotlin.comparisons.maxOf to <project>.Algo.maxOf without adding O(n^2) scans. ASan/UBSan validation: lsp_resolution_probe passed 83/83, kotlin_lsp passed 79/79, java_lsp passed 379/379, and parallel passed 36/36. The focused lrp_kotlin_s7_generic debug run resolved one CALLS edge from <project>.Main.run to <project>.Algo.maxOf. git diff --check passed. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -642,26 +642,22 @@ const char *kotlin_resolve_function_name(KotlinLSPContext *ctx, const char *name
|
||||
}
|
||||
}
|
||||
|
||||
/* Default imports */
|
||||
const char *via_default = kt_resolve_in_default_imports(ctx, name, CBM_KT_USE_FUNCTION);
|
||||
if (via_default) {
|
||||
return via_default;
|
||||
}
|
||||
|
||||
/* Cross-file sole-definer fallback. In the default package (no `package`
|
||||
/* Cross-file sole-definer fallback. Kotlin declarations in the current
|
||||
* project/package shadow default imports, so this lookup must run before
|
||||
* kotlin.*, java.*, and other implicit-import packages. In the default
|
||||
* package (no `package`
|
||||
* declaration) a top-level `double()` in Util.kt is callable bare from
|
||||
* Main.kt, but its registered QN embeds the defining file's path
|
||||
* ("<project>.Util.double") which the caller can't reconstruct. When the
|
||||
* project-wide registry holds EXACTLY ONE top-level function (receiver_type
|
||||
* == NULL) whose short name matches, resolve to it. Bounded to a single
|
||||
* candidate so an ambiguous name (>1 definer) is left unresolved — sound,
|
||||
* mirroring the registry's "unique_name" strategy. Runs only after the
|
||||
* package/import/bare/default-import lookups miss, so it never overrides a
|
||||
* more specific match; in the per-file pass the registry holds just this
|
||||
* file's defs, so the candidate is the file's own sole top-level fun. */
|
||||
* project-wide registry holds EXACTLY ONE project top-level function
|
||||
* (receiver_type == NULL) whose short name matches, resolve to it. Bounded
|
||||
* to a single candidate so an ambiguous name (>1 definer) is unresolved.
|
||||
* Filtering on project_name prevents a same-named stdlib function such as
|
||||
* kotlin.comparisons.maxOf from making the project declaration ambiguous. */
|
||||
if (ctx->registry && ctx->registry->funcs) {
|
||||
const char *only_qn = NULL;
|
||||
int matches = 0;
|
||||
size_t project_len = ctx->project_name ? strlen(ctx->project_name) : 0;
|
||||
for (int i = 0; i < ctx->registry->func_count && matches < 2; i++) {
|
||||
const CBMRegisteredFunc *f = &ctx->registry->funcs[i];
|
||||
if (!f->qualified_name || !f->short_name) {
|
||||
@@ -670,7 +666,10 @@ const char *kotlin_resolve_function_name(KotlinLSPContext *ctx, const char *name
|
||||
if (f->receiver_type) { /* method / extension — not a bare top-level fun */
|
||||
continue;
|
||||
}
|
||||
if (strcmp(f->short_name, name) == 0) {
|
||||
bool project_def = project_len > 0 &&
|
||||
strncmp(f->qualified_name, ctx->project_name, project_len) == 0 &&
|
||||
f->qualified_name[project_len] == '.';
|
||||
if (project_def && strcmp(f->short_name, name) == 0) {
|
||||
only_qn = f->qualified_name;
|
||||
matches++;
|
||||
}
|
||||
@@ -680,7 +679,9 @@ const char *kotlin_resolve_function_name(KotlinLSPContext *ctx, const char *name
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
/* Default imports are the final function-name fallback after explicit,
|
||||
* same-package, wildcard, and unique project declarations. */
|
||||
return kt_resolve_in_default_imports(ctx, name, CBM_KT_USE_FUNCTION);
|
||||
}
|
||||
|
||||
static const char *kt_resolve_in_default_imports(KotlinLSPContext *ctx, const char *name,
|
||||
@@ -4346,8 +4347,12 @@ void cbm_run_kotlin_lsp_cross(CBMArena *arena, const char *source, int source_le
|
||||
}
|
||||
|
||||
KotlinLSPContext ctx;
|
||||
kotlin_lsp_init(&ctx, arena, source, source_len, ®, "", module_qn ? module_qn : "",
|
||||
project_name, /*rel_path=*/NULL, out);
|
||||
/* Start from the file module QN just like cbm_run_kotlin_lsp(). A source
|
||||
* package_header replaces this during kotlin_lsp_process_file(); default-
|
||||
* package files retain it so emitted caller_qn values still match the
|
||||
* textual extractor's <project>.<File>.<function> identity. */
|
||||
kotlin_lsp_init(&ctx, arena, source, source_len, ®, module_qn ? module_qn : "",
|
||||
module_qn ? module_qn : "", project_name, /*rel_path=*/NULL, out);
|
||||
|
||||
/* Apply caller-supplied imports (resolved IMPORTS edges). */
|
||||
for (int i = 0; i < import_count; i++) {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "yyjson/yyjson.h"
|
||||
#include "foundation/compat_fs.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -42,6 +43,11 @@ enum {
|
||||
PXC_ITOA_BUF = 16,
|
||||
};
|
||||
|
||||
/* Hash-table keys reject empty strings, but Java/Kotlin's default package is a
|
||||
* real shared namespace: sibling files can reference its declarations without
|
||||
* imports. Use an internal key that cannot be a legal JVM package name. */
|
||||
static const char PXC_DEFAULT_JVM_NAMESPACE[] = "<jvm-default-package>";
|
||||
|
||||
/* Format an int into a thread-local rotating buffer for log key=value emission.
|
||||
* Mirrors the itoa_log helper in pass_calls.c — kept local so passes don't
|
||||
* grow a foundation-wide formatting API just for log output. */
|
||||
@@ -467,14 +473,27 @@ static void pxc_append_results(CBMArena *dst_arena, CBMResolvedCallArray *dst_ca
|
||||
CBMArena keys;
|
||||
cbm_arena_init(&keys);
|
||||
CBMHashTable *seen = cbm_ht_create((uint32_t)(dst_calls->count + src_out->count + 1));
|
||||
CBMHashTable *by_call = cbm_ht_create((uint32_t)(dst_calls->count + src_out->count + 1));
|
||||
|
||||
if (!seen || !by_call) {
|
||||
cbm_ht_free(seen);
|
||||
cbm_ht_free(by_call);
|
||||
cbm_arena_destroy(&keys);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < dst_calls->count; i++) {
|
||||
const CBMResolvedCall *rc = &dst_calls->items[i];
|
||||
CBMResolvedCall *rc = &dst_calls->items[i];
|
||||
if (rc->caller_qn && rc->callee_qn) {
|
||||
char *k = cbm_arena_sprintf(&keys, "%s\x1f%s", rc->caller_qn, rc->callee_qn);
|
||||
if (k) {
|
||||
cbm_ht_set(seen, k, (void *)1);
|
||||
}
|
||||
char *call_key = cbm_arena_sprintf(&keys, "%s\x1f%s", rc->caller_qn,
|
||||
pxc_last_component(rc->callee_qn));
|
||||
if (call_key && !cbm_ht_has(by_call, call_key)) {
|
||||
cbm_ht_set(by_call, call_key, (void *)(uintptr_t)(i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,6 +501,25 @@ static void pxc_append_results(CBMArena *dst_arena, CBMResolvedCallArray *dst_ca
|
||||
const CBMResolvedCall *src = &src_out->items[j];
|
||||
if (!src->caller_qn || !src->callee_qn)
|
||||
continue;
|
||||
char *call_key = cbm_arena_sprintf(&keys, "%s\x1f%s", src->caller_qn,
|
||||
pxc_last_component(src->callee_qn));
|
||||
uintptr_t encoded_index = call_key ? (uintptr_t)cbm_ht_get(by_call, call_key) : 0;
|
||||
if (encoded_index > 0) {
|
||||
CBMResolvedCall *existing = &dst_calls->items[encoded_index - 1];
|
||||
/* The source array is produced by the cross-file pass and has a
|
||||
* project-wide registry. On equal confidence it must replace the
|
||||
* earlier per-file guess (for example stdlib maxOf versus a local
|
||||
* maxOf); strictly weaker cross results never displace it. */
|
||||
if (src->confidence >= existing->confidence &&
|
||||
strcmp(src->callee_qn, existing->callee_qn) != 0) {
|
||||
existing->callee_qn = cbm_arena_strdup(dst_arena, src->callee_qn);
|
||||
existing->strategy =
|
||||
src->strategy ? cbm_arena_strdup(dst_arena, src->strategy) : NULL;
|
||||
existing->confidence = src->confidence;
|
||||
existing->reason = src->reason ? cbm_arena_strdup(dst_arena, src->reason) : NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
char *k = cbm_arena_sprintf(&keys, "%s\x1f%s", src->caller_qn, src->callee_qn);
|
||||
if (k && cbm_ht_has(seen, k))
|
||||
continue;
|
||||
@@ -496,9 +534,13 @@ static void pxc_append_results(CBMArena *dst_arena, CBMResolvedCallArray *dst_ca
|
||||
dst.confidence = src->confidence;
|
||||
dst.reason = src->reason ? cbm_arena_strdup(dst_arena, src->reason) : NULL;
|
||||
cbm_resolvedcall_push(dst_calls, dst_arena, dst);
|
||||
if (call_key) {
|
||||
cbm_ht_set(by_call, call_key, (void *)(uintptr_t)dst_calls->count);
|
||||
}
|
||||
}
|
||||
|
||||
cbm_ht_free(seen);
|
||||
cbm_ht_free(by_call);
|
||||
cbm_arena_destroy(&keys);
|
||||
}
|
||||
|
||||
@@ -1273,8 +1315,11 @@ CBMModuleDefIndex *cbm_pxc_build_module_def_index(CBMLSPDef *all_defs, int def_c
|
||||
for (int i = 0; i < def_count; i++) {
|
||||
pxc_module_entry_add_index(pxc_module_entry_get_or_create(ht, all_defs[i].def_module_qn),
|
||||
i);
|
||||
pxc_module_entry_add_index(
|
||||
pxc_module_entry_get_or_create(namespace_ht, all_defs[i].namespace_name), i);
|
||||
const char *namespace_key = all_defs[i].namespace_name;
|
||||
if (pxc_is_jvm_lang(all_defs[i].lang) && (!namespace_key || !namespace_key[0])) {
|
||||
namespace_key = PXC_DEFAULT_JVM_NAMESPACE;
|
||||
}
|
||||
pxc_module_entry_add_index(pxc_module_entry_get_or_create(namespace_ht, namespace_key), i);
|
||||
}
|
||||
|
||||
CBMModuleDefIndex *idx = (CBMModuleDefIndex *)calloc(1, sizeof(*idx));
|
||||
@@ -1327,10 +1372,12 @@ CBMLSPDef *cbm_pxc_filter_defs_for_file(const CBMModuleDefIndex *idx, CBMLSPDef
|
||||
for (int i = 0; i < imp_count; i++) {
|
||||
pxc_mark_module_defs(idx, selected, all_defs, caller_lang, imp_qns[i], &total);
|
||||
}
|
||||
if (pxc_is_jvm_lang(caller_lang) && caller_namespace && caller_namespace[0] &&
|
||||
idx->namespace_ht) {
|
||||
if (pxc_is_jvm_lang(caller_lang) && idx->namespace_ht) {
|
||||
const char *namespace_key =
|
||||
(caller_namespace && caller_namespace[0]) ? caller_namespace
|
||||
: PXC_DEFAULT_JVM_NAMESPACE;
|
||||
pxc_module_entry_t *e =
|
||||
(pxc_module_entry_t *)cbm_ht_get(idx->namespace_ht, caller_namespace);
|
||||
(pxc_module_entry_t *)cbm_ht_get(idx->namespace_ht, namespace_key);
|
||||
total += pxc_mark_entry_defs(selected, e, all_defs, caller_lang);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user