Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions internal/cbm/lsp/php_lsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1760,8 +1760,32 @@ static void resolve_static_call(PHPLSPContext *ctx, TSNode call, CBMResolvedKind
}
}
}
if (!class_qn)
return;
if (!class_qn) {
/* Fallback: enclosing_class_qn is NULL (shouldn't happen in well-formed
* code, but may occur if process_class_decl didn't run). Emit a low-
* confidence self-reference so pass_calls can still attempt registry
* resolution instead of dropping the call entirely. */
if (ctx->enclosing_func_qn && strcmp(strategy, "php_self_static") == 0) {
/* Extract class QN from enclosing_func_qn: "Proj.path.Class.method" → "Proj.path.Class" */
const char *last_dot = strrchr(ctx->enclosing_func_qn, '.');
if (last_dot && last_dot > ctx->enclosing_func_qn) {
size_t class_len = (size_t)(last_dot - ctx->enclosing_func_qn);
char *inferred_class = (char *)cbm_arena_alloc(ctx->arena, class_len + 1);
if (inferred_class) {
memcpy(inferred_class, ctx->enclosing_func_qn, class_len);
inferred_class[class_len] = '\0';
class_qn = inferred_class;
/* Continue to method lookup with inferred class */
} else {
return;
}
} else {
return;
}
} else {
return;
}
}

const CBMRegisteredFunc *f = php_lookup_method(ctx, class_qn, method_name);
if (f) {
Expand Down
26 changes: 11 additions & 15 deletions src/pipeline/registry.c
Original file line number Diff line number Diff line change
Expand Up @@ -1029,32 +1029,28 @@ static cbm_resolution_t resolve_name_lookup(const cbm_registry_t *r, const char
}
}

/* Strategy 3: unique name */
/* Strategy 3: unique name - require imports for weak name-only matching.
* Without explicit imports, unique_name/suffix_match can fabricate spurious
* cross-language edges (e.g., Python builtin 'get' matching every unrelated
* project 'get'). Strict import requirement reduces false positives at the
* cost of missing legitimate same-language calls in import-sparse codebases. */
if (!import_vals || import_count == 0) {
return empty_result();
}

if (arr->count == SKIP_ONE) {
if (!receiver_chain_admits(callee_name, arr->items[0])) {
return empty_result();
}
double conf = CONF_UNIQUE_NAME;
if (import_vals && import_count > 0 &&
!is_import_reachable(arr->items[0], import_vals, import_count)) {
if (!is_import_reachable(arr->items[0], import_vals, import_count)) {
conf *= DEFAULT_CONFIDENCE;
}
return (cbm_resolution_t){arr->items[0], "unique_name", conf, REG_RESOLVED};
}

/* Strategy 4: multiple candidates */
if (import_vals && import_count > 0) {
return resolve_multi_with_imports(arr, module_qn, import_vals, import_count);
}
const char *best = best_by_import_distance((const char **)arr->items, arr->count, module_qn);
if (best) {
if (!receiver_chain_admits(callee_name, best)) {
return empty_result();
}
double conf = candidate_count_penalty(CONF_SUFFIX_MATCH, arr->count);
return (cbm_resolution_t){best, "suffix_match", conf, arr->count};
}
return empty_result();
return resolve_multi_with_imports(arr, module_qn, import_vals, import_count);
}

/* The strategy chain shared by both public resolve variants (no caching here —
Expand Down
Loading