mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
feat(python): a proven callable value on an assignment RHS is a CALL_REFERENCE
Maintainer decision closing the last open RED from PR item 2. `callback = handler` contains a genuine value occurrence of handler, and its exact callable identity is proven -- the alias binding below could not be created otherwise. The policy of this PR is proven exact value => CALL_REFERENCE, so that occurrence now emits a resolved reference row instead of remaining an ordinary USAGE. Bare identifier RHS only, and the usage-carrier rule in extract_usages.c is extended in lockstep, Python-gated -- a candidate the LSP never matches would change nothing, but the asymmetry would be a trap. The strategy distinction mirrors the argument path: a source name that is itself a local alias claims lsp_callable_alias, so the local-shadow guard admits exactly what it admits for arguments and refuses everything else. In the graph the two occurrences inside `argument` -- the RHS and the alias use in accept(callback) -- surface as ONE CALL_REFERENCE edge and zero USAGE edges, because edge dedup is by (src, tgt, type) and collapsing multiple sites between the same pair is its existing, intended behavior. That is precisely what the fixture always asserted, so it needed no relaxation, only the comment recording the decision. Also: GCC rejects repro_call_argument_matrix_b's _Static_assert comparing enumerators of two different anonymous enums (-Werror=enum-compare); clang accepts it, so only the Linux leg saw it. Cast both sides to int. repro_reference_precision + call_reference_contract: 100 passed, 0 failed. This closes all five item-2 REDs. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
e47d932c89
commit
c4c6005cde
4 files changed
+39
-6
No files matched your search
@@ -1558,6 +1558,17 @@ static TSNode call_reference_candidate_site(CBMExtractCtx *ctx, TSNode node, con
|
||||
if (strcmp(kind, "identifier") != 0 && strcmp(kind, "simple_identifier") != 0) {
|
||||
return (TSNode){0};
|
||||
}
|
||||
/* Python: the right-hand side of `callback = handler` is a genuine value
|
||||
* occurrence of handler, and when the LSP proves its exact callable
|
||||
* identity it becomes a CALL_REFERENCE like any proven argument value
|
||||
* (maintainer decision on the local-alias fixture). Bare identifier RHS
|
||||
* only, in lockstep with the row py_lsp.c emits -- a candidate the LSP
|
||||
* never matches would change nothing, but the asymmetry would be a trap. */
|
||||
if (ctx->language == CBM_LANG_PYTHON && !ts_node_is_null(parent) &&
|
||||
strcmp(ts_node_type(parent), "assignment") == 0) {
|
||||
TSNode right = ts_node_child_by_field_name(parent, TS_FIELD("right"));
|
||||
return !ts_node_is_null(right) && ts_node_eq(right, node) ? node : (TSNode){0};
|
||||
}
|
||||
if (is_direct_argument_value_walk(node, state)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -992,10 +992,6 @@ static const char *py_exact_callable_target_ex(PyLSPContext *ctx, TSNode node,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *py_exact_callable_target(PyLSPContext *ctx, TSNode node) {
|
||||
return py_exact_callable_target_ex(ctx, node, NULL, NULL);
|
||||
}
|
||||
|
||||
static void py_resolve_value_references_at(PyLSPContext *ctx, TSNode call) {
|
||||
if (!ctx || ctx->callable_value_proof_disabled)
|
||||
return;
|
||||
@@ -2225,8 +2221,24 @@ static void py_process_statement(PyLSPContext *ctx, TSNode node) {
|
||||
|
||||
const CBMType *rhs_type =
|
||||
ts_node_is_null(right) ? cbm_type_unknown() : py_eval_expr_type(ctx, right);
|
||||
bool rhs_lexical_alias = false;
|
||||
const char *rhs_callable =
|
||||
ts_node_is_null(right) ? NULL : py_exact_callable_target(ctx, right);
|
||||
ts_node_is_null(right)
|
||||
? NULL
|
||||
: py_exact_callable_target_ex(ctx, right, NULL, &rhs_lexical_alias);
|
||||
/* A proven callable value on the right of an assignment is a reference
|
||||
* to that callable, exactly as it would be as a call argument -- the
|
||||
* policy is proven exact value => CALL_REFERENCE, and `cb = handler`
|
||||
* proves it or the alias binding below could not be created. Bare
|
||||
* identifier RHS only, in lockstep with the usage-carrier rule in
|
||||
* extract_usages.c; the strategy distinction mirrors the argument path
|
||||
* so a shadowed source name still satisfies the local-shadow guard. */
|
||||
if (rhs_callable && strcmp(ts_node_type(right), "identifier") == 0) {
|
||||
char *rhs_name = py_node_text(ctx, right);
|
||||
py_emit_resolved_reference(ctx, rhs_callable, rhs_name, right,
|
||||
rhs_lexical_alias ? "lsp_callable_alias"
|
||||
: "lsp_callable_value_reference");
|
||||
}
|
||||
|
||||
// Annotated assignment: x: T = expr — annotation wins.
|
||||
bool has_annotation = !ts_node_is_null(ann);
|
||||
|
||||
@@ -1308,7 +1308,10 @@ _Static_assert(MATRIX_LANGUAGE_COUNT == 46,
|
||||
enum { MATRIX_B_ROW_COUNT = 0 MATRIX_B_LANGUAGE_ROWS(MATRIX_B_COUNT_ROW) };
|
||||
#undef MATRIX_B_COUNT_ROW
|
||||
|
||||
_Static_assert(MATRIX_B_ROW_COUNT == MATRIX_LANGUAGE_COUNT,
|
||||
/* Cast both sides: they are enumerators of two different anonymous enums, and
|
||||
* GCC rejects comparing those under -Werror=enum-compare (clang accepts it, so
|
||||
* only the Linux leg sees the difference). */
|
||||
_Static_assert((int)MATRIX_B_ROW_COUNT == (int)MATRIX_LANGUAGE_COUNT,
|
||||
"matrix B suite and language-row count must stay synchronized");
|
||||
|
||||
size_t repro_call_argument_matrix_b_copy_language_ids(CBMLanguage *language_ids, size_t capacity) {
|
||||
|
||||
@@ -1548,6 +1548,13 @@ TEST(repro_python_parenthesized_function_value_is_exact_reference) {
|
||||
}
|
||||
|
||||
TEST(repro_python_local_callable_alias_value_is_exact_reference) {
|
||||
/* Both occurrences of handler inside `argument` are proven exact callable
|
||||
* values -- the alias use in accept(callback) AND the right-hand side of
|
||||
* `callback = handler` (maintainer decision: a proven RHS is a
|
||||
* CALL_REFERENCE, not an ordinary USAGE). The graph deduplicates edges by
|
||||
* (src, tgt, type), so the two occurrences surface as ONE CALL_REFERENCE
|
||||
* edge and zero USAGE edges, which is exactly what the shared helper
|
||||
* asserts. */
|
||||
static const char source[] = "def handler():\n"
|
||||
" pass\n"
|
||||
"def accept(fn):\n"
|
||||
|
||||
Reference in new issue
Block a user