Skip to content

fix(derive): #23 R1 — gate rust_calls free-fn arm off method calls (rust-calls violations → 0) - #467

Merged
Disentinel merged 1 commit into
mainfrom
fix/rust-calls-r1-method-receiver
Jun 21, 2026
Merged

fix(derive): #23 R1 — gate rust_calls free-fn arm off method calls (rust-calls violations → 0)#467
Disentinel merged 1 commit into
mainfrom
fix/rust-calls-r1-method-receiver

Conversation

@Disentinel

Copy link
Copy Markdown
Owner

What

Follow-up to #466. The 10 residual rust-calls resolution-is-a-function violations were an R1 (scope-walk) nuance, not R2: in parser.rs, a method impl Parser { fn parse } and a free fn parse share a name, so self.parse() resolved to both — the method (owner arm) and the free fn (file-MODULE arm).

In Rust, self.foo() is always the impl method, never a same-named free fn.

How

Gate the file-MODULE free-fn arm with \+ has_receiver(C), where a method call is identified by its CALL -READS_FROM-> receiver edge (the analyzer's method-call discriminator — the same signal rust_cross_methods_ctor DELTA 6 keys on). Free-fn calls (the legitimate target of that arm) carry no receiver and are unaffected.

Evidence (fresh rust graph, 70,029 CALLs)

before (#466) after
rust-calls violations 10 0
rust_calls total edges 2381 2371 (−10, exactly the spurious free-fn dups)

No legitimate resolution lost. New test rust_calls_r1_method_call_excludes_free_fn_of_same_name; all rust_* derive tests green.

#431 status

With #466 + this, rust-calls contributes ZERO resolution-is-a-function violations — it is now a true function. The only remaining violators are rust-dyn-dispatch (324, semantic dispatch — already excluded) and rust-cross-method (237, parity-ceiling — carve-out candidate). So resolution-is-a-function can ship severity:error once those two are carved out.

🤖 Generated with Claude Code

… method calls

R1's file-MODULE free-function arm fired for `self.method()` calls too: in a file
with both `impl Parser { fn parse }` and a free `fn parse`, a `self.parse()` call
resolved to BOTH (the method via the owner arm + the free fn via the file-MODULE
arm) — the 10 residual resolution-is-a-function violations left after #466 (all in
parser.rs). In Rust `self.foo()` is always the impl method, never a same-named free
fn. Gate the free-fn arm with `\+ has_receiver(C)`: a method call carries a
CALL -READS_FROM-> receiver (the analyzer's method-call discriminator, the same
signal rust_cross_methods_ctor DELTA 6 keys on); a free call never does. Free-fn
calls (the legitimate target of this arm) are unaffected.

Measured on a fresh rust graph (70,029 CALLs): rust-calls resolution-is-a-function
violations 10 → 0 (now a true function); rust_calls total edges 2381 → 2371 (−10,
exactly the spurious free-fn duplicates — no legitimate resolution lost). All
rust_* derive tests green; new test
rust_calls_r1_method_call_excludes_free_fn_of_same_name.

With this + #466, rust-calls contributes ZERO #431 violations. The remaining
violators are rust-dyn-dispatch (324, semantic dispatch — already excluded) and
rust-cross-method (237, parity-ceiling carve-out).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Disentinel
Disentinel merged commit 1ed322e into main Jun 21, 2026
13 checks passed
@Disentinel
Disentinel deleted the fix/rust-calls-r1-method-receiver branch June 21, 2026 08:01
Disentinel added a commit that referenced this pull request Jun 21, 2026
…ck resolver soundness (#479)

A new datalog guarantee: a CALL must not resolve to two distinct targets under
the same resolvedVia tag — resolution must be a function. Carves out the two
resolvers that are one-to-many BY DESIGN: rust-dyn-dispatch (trait-object
semantic dispatch) and rust-cross-method (the heuristic parity ceiling; precise
resolution = the team-server SCIP tier).

This is the enforcement layer for the #23 rust_calls rework (#466 + #467): with
rust-calls now a true function, the guarantee locks the property and catches
regressions. Verified on a fresh rust graph via the derive engine
(backend.checkGuarantee): 0 violations WITH the carve-outs, 561 WITHOUT them
(= rust-cross-method 237 + rust-dyn-dispatch 324 — exactly the two excluded
one-to-many resolvers, nothing else over-resolves). Uses the derive builtin
edge_attr to read the CALLS edge's resolvedVia; the edge() generator legs lead so
the planner binds before the edge_attr point-probes (E-PLAN-002 otherwise).

severity:error. `grafema check` is NOT wired into CI, so this is opt-in
enforcement (grafema check → process.exit(1) on violation), not a per-PR CI
blocker. Known scope: the JS resolvers (runtime-globals residual, cross-file-calls,
same-file-calls) have their own over-resolution not yet closed — the JS analogue
of the #23 rust work; on a graph where they fire, this rule reports them
(correctly, as real over-resolutions to fix). Supersedes the stale vm #431 branch
(which carried no actual rule).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant