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:
Martin Vogel committed 2026-08-02 17:01:53 +02:00
1 parent e47d932c89
commit c4c6005cde
4 files changed
+39 -6

No files matched your search

+11
View File
@@ -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;
}
+17 -5
View File
@@ -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);
+4 -1
View File
@@ -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) {
+7
View File
@@ -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"