fix(lsp): join method-dispatch resolutions by bare callee short-name

The LSP-resolution join compared the LSP's class-qualified callee short-name
(e.g. "inc" from "Counter.inc") against the call's RAW callee_name, which is
receiver-qualified for method/qualified calls ("c.inc", "A.Helper", "c.Add").
That comparison always missed for method dispatch, so every type-aware LSP
resolution (lsp_type_dispatch, cs_static_typed, lsp_method_dispatch, ...) was
silently dropped and the call fell through to the weaker textual registry
resolver — the LSP strategy never reached the CALLS edge's properties_json.

Reduce the call's callee_name to its bare last dot-separated segment before the
comparison, mirroring what is already done to callee_qn. The LSP has already
performed receiver->type resolution, so matching bare method names is correct;
free-function calls (already bare) are unaffected. This lets the more accurate
type-aware LSP resolution win over textual registry matching for method calls.

Reproduced by the repro_lsp_* per-pass strategy suites (java/cs/go/rust/kotlin/
php/ts), which asserted the LSP strategy is present on a CALLS edge and were RED.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-06-27 01:40:41 +02:00
parent 6e6b34a9bb
commit f8a7e8b715
+11 -1
View File
@@ -67,7 +67,17 @@ static inline const CBMResolvedCall *cbm_pipeline_find_lsp_resolution(
}
const char *short_name = strrchr(rc->callee_qn, '.');
short_name = short_name ? short_name + SKIP_ONE : rc->callee_qn;
if (strcmp(short_name, call->callee_name) != 0) {
/* The call's callee_name is receiver-qualified for method/qualified
* calls ("c.inc", "A.Helper"); the LSP records the resolved
* class-qualified callee_qn ("Class.inc"). Compare the bare last
* segment on BOTH sides so method-dispatch resolutions join — the LSP
* already did the receiver->type resolution, and matching the full
* "c.inc" against "inc" would always miss, silently dropping the
* type-aware LSP strategy to the weaker textual registry. Free-function
* calls (bare callee_name) are unaffected. */
const char *call_short = strrchr(call->callee_name, '.');
call_short = call_short ? call_short + SKIP_ONE : call->callee_name;
if (strcmp(short_name, call_short) != 0) {
continue;
}
if (!best || rc->confidence > best->confidence) {