fix(derive): #23 R1 — gate rust_calls free-fn arm off method calls (rust-calls violations → 0) - #467
Merged
Merged
Conversation
… 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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Follow-up to #466. The 10 residual
rust-callsresolution-is-a-function violations were an R1 (scope-walk) nuance, not R2: inparser.rs, a methodimpl Parser { fn parse }and a freefn parseshare a name, soself.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 itsCALL -READS_FROM-> receiveredge (the analyzer's method-call discriminator — the same signalrust_cross_methods_ctorDELTA 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)
No legitimate resolution lost. New test
rust_calls_r1_method_call_excludes_free_fn_of_same_name; allrust_*derive tests green.#431 status
With #466 + this,
rust-callscontributes ZEROresolution-is-a-functionviolations — it is now a true function. The only remaining violators arerust-dyn-dispatch(324, semantic dispatch — already excluded) andrust-cross-method(237, parity-ceiling — carve-out candidate). Soresolution-is-a-functioncan shipseverity:erroronce those two are carved out.🤖 Generated with Claude Code