Resolved references can name unrelated methods, global names and function-local types #531

Open
opened 2026-11-09 08:35:46 +01:00 by buildagent · 9 comments
Member

Scope (consolidated 2026-11-12)

Caller and impact results can name a project declaration that the source cannot reach. A matching method name, file stem or qualifier is insufficient evidence of receiver identity. Generic qualifiers can also be mistaken for concrete types, exposing declarations local to another function. This ticket covers wrong targets; unresolved uses and missing extraction are tracked in #536 and #540.

Priority: Critical · Risk: wrong answer · Size: L
Merged into this ticket: #566 (Residual audited wrong binds), #579 (Generic qualifier binds local type)

Done

  • Recorded receiver contradictions, undeclared JS/TS globals and independent Cargo roots are guarded in crates/indexer/src/index/name_only.rs and cargo_scope.rs (e1420994; released v0.41.48).
  • PHP parent/base and rooted call/constructor fixes shipped in v0.41.49; rooted_paths_require_exact_global_identity and a_global_receiver_keeps_its_proven_inherited_member in crates/indexer/tests/global_roots.rs cover identity and inheritance.
  • Import/module contradiction and route rules are in crates/indexer/src/index/qualifier_modules.rs; an_imported_qualifier_never_binds_what_its_file_cannot_reach, a_namespace_import_never_names_a_class_member and another_types_impl_block_is_on_no_chain in crates/indexer/tests/qualifier_scope.rs cover the v0.41.50 subset.
  • The #579 test parameter was renamed to Vis in crates/indexer/tests/configuration_reference.rs; this avoids the gate failure and does not fix the resolver.

Remaining

Receiver identity

  • Emit facts for closure/macro parameters, enum/Option patterns, captures and match/builder chains before enabling the evidence-less std/prelude refusal (ENFORCE_EVIDENCELESS_STD_SURFACE = false in name_only.rs); reproduce the remaining receiver cases in the audit. (#531, #566)
  • Require method import evidence for the impl self type; the broad implementation was dropped after losing correct binds, so requalify it with the missing receiver facts. (#531)

Namespace and lexical identity

  • Dispose of Ruby ::Logger, Rust exact module/type-path and private-alias rows, and residual file-isolation/receiver rows individually; reproduction and audit table in #566. Coordinate shared scope mechanisms with #536. (#566)
  • Restrict declarations inside a function/method to references inside that body; reject a qualifier naming an enclosing type parameter as a concrete type. Reproduction in #579; package type-parameter facts are needed for the second rule. (#579)

Regression qualification

  • Port the receiver_file_anchor regression from builtins to guests with #515 and recheck the two residual auth_tests user.email sites by bind diff. (#531)

Acceptance

  • Each residual audit row has a source-backed disposition; no historical residual count is treated as a fresh precision estimate.
  • All listed phantom sites, including ReadPool::get, Capture::flush, generic as_ref/clone, Ruby absolute names and V::Value, resolve correctly or remain unresolved; precision decoys include global APIs and function-local/type-parameter collisions.
  • Before any baseline change, join all nine corpus bind sets on (path,line,col,kind,occurrence), split by resolved_by, audit removals/additions/retargets and sample unchanged binds. Preserve the operator’s zero-correct-bind-loss condition for enabling deferred guards.

Suggested order

  1. Finish the residual row ledger and lexical containment guard; host change, bind account required.
  2. Emit receiver and type-parameter facts; Rust and other affected language package releases required, with bind accounts.
  3. Qualify self-type import proof and the disabled std/prelude guard; enable only after a loss-free bind account.

Original report

Summary

Name-only tiers bind method calls whose real target is a builtin, std or global API to the only project method of the same name. These are wrong binds. They show up in find_callers and change_impact, including callers in other crates and in test-only code. Reproduced in Rust and TypeScript on pinned corpus repositories (installed build 2dcfc615, which is behind master only by XAML/CI commits).

Rust: rust-ripgrep and rust-analyzer

  • crates/cli/src/decompress.rs:137,139: program.as_ref() / a.as_ref() on a generic P: AsRef<OsStr> binds to impl AsRef<Glob> for Glob in another crate (crates/globset/src/glob.rs:84), with resolved_by: tier1b_container_import. All 15 resolved refs of that impl are phantoms.
  • ignore::Error::clone (crates/ignore/src/lib.rs:123): 73 binds, mostly .clone() on Ignore/Arc, via tier1b_file_key_import.
  • label.as_bytes() on &str (searcher/mod.rs:138) binds to a #[cfg(test)] KitchenSink::as_bytes. err.kind() on io::Error binds to SinkContext::kind. ent.file_type() on fs::DirEntry binds via tier1r_receiver.
  • rust-analyzer: projections.push on a Vec binds to CaptureSourceStack::push, and crate_ids.iter() to MemDocs::iter.
  • Sampled precision of tier3_import_boost on method calls: RA 9/20, ripgrep (non-test) 9/15.
  • Upper bound on the population (std-named method_call binds by name-only rules with a receiver other than self): ripgrep 182, RA 1,903.

TypeScript: ts-zod

  • JSON.parse(...) (v4/core/json-schema-processors.ts:501,509, to-json-schema.ts:507, classic/from-json-schema.ts:641) binds to $ZodTypeInternals.parse / ZodType.parse via tier3_import_boost.
  • Object.defineProperty(this, ...) ×4 and Reflect.defineProperty (v4/core/util.ts:230,261,274,293,562) bind to a Proxy-handler literal method via tier1a_unique_own_file.

Not seen

C#, Python, Ruby, JS and PHP samples showed 0 wrong binds of this shape (C#: 318 checked; Python: ~360; Ruby: 617; JS: 29).

Why this is one defect

Every case has the same shape: a method_call whose receiver evidence does not name the candidate's container, with a name that also exists on the language's builtin/prelude/global surface, bound by a tier that proves only "the name is unique here" or "the file is imported". tier1b_file_key_import/tier1b_container_import accept an import of the candidate's file or container module, not of its self type.

Proposal (one generic rule)

  • A tier that proves only by name (unique-own-file, file-key import, container import, tier-3 import boost) must not decide a method_call when either of these holds:
    • (a) the receiver head is an undeclared ambient global (JSON, Object, Reflect, Math, …); the profile declares the global set, extending is_ts_builtin_global beyond type heads; or
    • (b) the name is on the profile's builtin-method surface (Rust prelude/std traits: clone, as_ref, iter, push, kind, len, …). The influence: builtin_only census already exists for this.
  • In both cases, receiver evidence that names the candidate's container (a declared type, self/this, a proven receiver) may still decide.
  • tier1b_*_import for a method requires the import to name the method's impl self type, not merely its file.

Acceptance

  • Every phantom listed above becomes unresolved or name_fallback.
  • A bind diff of the 9 corpus repos against the prior binary, joined on (path,line,col,kind,occurrence) and split by resolved_by (per CLAUDE.md), shows only removals of this shape. Sample the unchanged binds too.
  • precision_gate gains decoy sites: JSON.parse against a project parse, a generic .as_ref() against a project AsRef impl, and .clone() against a project Clone impl.

Related: #270 (receiver-binding phantom: the same family, a different tier). Evidence: lang-out/rust/ph_*.json, sample_mc.txt, ra_sample_rule30.txt; lang-out/typescript/builtin_phantoms.txt.


Filed from the per-language review on 2026-11-09: 8 agents, one per language plus one for Vue/Svelte/XAML/timeline, run against copies of the pinned corpus repos (tests/corpus/corpus.toml) using installed build 2dcfc615, which differs from master only by XAML/CI commits. The top claim of every issue was spot-checked in the main session. Artifacts live under the review session's scratchpad lang-out/<lang>/ and are available on request.

## Scope (consolidated 2026-11-12) Caller and impact results can name a project declaration that the source cannot reach. A matching method name, file stem or qualifier is insufficient evidence of receiver identity. Generic qualifiers can also be mistaken for concrete types, exposing declarations local to another function. This ticket covers wrong targets; unresolved uses and missing extraction are tracked in #536 and #540. **Priority:** Critical · **Risk:** wrong answer · **Size:** L **Merged into this ticket:** #566 (Residual audited wrong binds), #579 (Generic qualifier binds local type) ## Done - Recorded receiver contradictions, undeclared JS/TS globals and independent Cargo roots are guarded in `crates/indexer/src/index/name_only.rs` and `cargo_scope.rs` (e1420994; released v0.41.48). - PHP parent/base and rooted call/constructor fixes shipped in v0.41.49; `rooted_paths_require_exact_global_identity` and `a_global_receiver_keeps_its_proven_inherited_member` in `crates/indexer/tests/global_roots.rs` cover identity and inheritance. - Import/module contradiction and route rules are in `crates/indexer/src/index/qualifier_modules.rs`; `an_imported_qualifier_never_binds_what_its_file_cannot_reach`, `a_namespace_import_never_names_a_class_member` and `another_types_impl_block_is_on_no_chain` in `crates/indexer/tests/qualifier_scope.rs` cover the v0.41.50 subset. - The #579 test parameter was renamed to `Vis` in `crates/indexer/tests/configuration_reference.rs`; this avoids the gate failure and does not fix the resolver. ## Remaining ### Receiver identity - Emit facts for closure/macro parameters, enum/Option patterns, captures and match/builder chains before enabling the evidence-less std/prelude refusal (`ENFORCE_EVIDENCELESS_STD_SURFACE = false` in `name_only.rs`); reproduce the remaining receiver cases in the audit. (#531, #566) - Require method import evidence for the impl self type; the broad implementation was dropped after losing correct binds, so requalify it with the missing receiver facts. (#531) ### Namespace and lexical identity - Dispose of Ruby `::Logger`, Rust exact module/type-path and private-alias rows, and residual file-isolation/receiver rows individually; reproduction and audit table in #566. Coordinate shared scope mechanisms with #536. (#566) - Restrict declarations inside a function/method to references inside that body; reject a qualifier naming an enclosing type parameter as a concrete type. Reproduction in #579; package type-parameter facts are needed for the second rule. (#579) ### Regression qualification - Port the receiver_file_anchor regression from builtins to guests with #515 and recheck the two residual auth_tests user.email sites by bind diff. (#531) ## Acceptance - Each residual audit row has a source-backed disposition; no historical residual count is treated as a fresh precision estimate. - All listed phantom sites, including ReadPool::get, Capture::flush, generic as_ref/clone, Ruby absolute names and V::Value, resolve correctly or remain unresolved; precision decoys include global APIs and function-local/type-parameter collisions. - Before any baseline change, join all nine corpus bind sets on (path,line,col,kind,occurrence), split by resolved_by, audit removals/additions/retargets and sample unchanged binds. Preserve the operator’s zero-correct-bind-loss condition for enabling deferred guards. ## Suggested order 1. Finish the residual row ledger and lexical containment guard; host change, bind account required. 2. Emit receiver and type-parameter facts; Rust and other affected language package releases required, with bind accounts. 3. Qualify self-type import proof and the disabled std/prelude guard; enable only after a loss-free bind account. ## Original report ## Summary Name-only tiers bind **method calls whose real target is a builtin, std or global API** to the only project method of the same name. These are wrong binds. They show up in `find_callers` and `change_impact`, including callers in other crates and in test-only code. Reproduced in Rust and TypeScript on pinned corpus repositories (installed build `2dcfc615`, which is behind master only by XAML/CI commits). ### Rust: rust-ripgrep and rust-analyzer - `crates/cli/src/decompress.rs:137,139`: `program.as_ref()` / `a.as_ref()` on a **generic `P: AsRef<OsStr>`** binds to `impl AsRef<Glob> for Glob` in **another crate** (`crates/globset/src/glob.rs:84`), with `resolved_by: tier1b_container_import`. All 15 resolved refs of that impl are phantoms. - `ignore::Error::clone` (`crates/ignore/src/lib.rs:123`): 73 binds, mostly `.clone()` on `Ignore`/`Arc`, via `tier1b_file_key_import`. - `label.as_bytes()` on `&str` (`searcher/mod.rs:138`) binds to a `#[cfg(test)] KitchenSink::as_bytes`. `err.kind()` on `io::Error` binds to `SinkContext::kind`. `ent.file_type()` on `fs::DirEntry` binds via `tier1r_receiver`. - rust-analyzer: `projections.push` on a `Vec` binds to `CaptureSourceStack::push`, and `crate_ids.iter()` to `MemDocs::iter`. - Sampled precision of `tier3_import_boost` on method calls: RA **9/20**, ripgrep (non-test) 9/15. - Upper bound on the population (std-named `method_call` binds by name-only rules with a receiver other than `self`): ripgrep 182, RA 1,903. ### TypeScript: ts-zod - `JSON.parse(...)` (`v4/core/json-schema-processors.ts:501,509`, `to-json-schema.ts:507`, `classic/from-json-schema.ts:641`) binds to `$ZodTypeInternals.parse` / `ZodType.parse` via `tier3_import_boost`. - `Object.defineProperty(this, ...)` ×4 and `Reflect.defineProperty` (`v4/core/util.ts:230,261,274,293,562`) bind to a Proxy-handler literal method via `tier1a_unique_own_file`. ### Not seen C#, Python, Ruby, JS and PHP samples showed **0** wrong binds of this shape (C#: 318 checked; Python: ~360; Ruby: 617; JS: 29). ## Why this is one defect Every case has the same shape: a `method_call` whose receiver evidence does **not** name the candidate's container, with a name that also exists on the language's builtin/prelude/global surface, bound by a tier that proves only "the name is unique here" or "the file is imported". `tier1b_file_key_import`/`tier1b_container_import` accept an import of the candidate's **file** or **container module**, not of its **self type**. ## Proposal (one generic rule) - A tier that proves only by name (unique-own-file, file-key import, container import, tier-3 import boost) must **not** decide a `method_call` when either of these holds: - (a) the receiver head is an undeclared ambient global (`JSON`, `Object`, `Reflect`, `Math`, …); the profile declares the global set, extending `is_ts_builtin_global` beyond type heads; or - (b) the name is on the profile's builtin-method surface (Rust prelude/std traits: `clone`, `as_ref`, `iter`, `push`, `kind`, `len`, …). The `influence: builtin_only` census already exists for this. - In both cases, receiver evidence that names the candidate's container (a declared type, `self`/`this`, a proven receiver) may still decide. - `tier1b_*_import` for a method requires the import to name the method's **impl self type**, not merely its file. ## Acceptance - Every phantom listed above becomes unresolved or name_fallback. - A bind diff of the 9 corpus repos against the prior binary, joined on `(path,line,col,kind,occurrence)` and split by `resolved_by` (per CLAUDE.md), shows only removals of this shape. Sample the unchanged binds too. - `precision_gate` gains decoy sites: `JSON.parse` against a project `parse`, a generic `.as_ref()` against a project `AsRef` impl, and `.clone()` against a project `Clone` impl. Related: #270 (receiver-binding phantom: the same family, a different tier). Evidence: `lang-out/rust/ph_*.json`, `sample_mc.txt`, `ra_sample_rule30.txt`; `lang-out/typescript/builtin_phantoms.txt`. --- _Filed from the per-language review on 2026-11-09: 8 agents, one per language plus one for Vue/Svelte/XAML/timeline, run against copies of the pinned corpus repos (`tests/corpus/corpus.toml`) using installed build `2dcfc615`, which differs from master only by XAML/CI commits. The top claim of every issue was spot-checked in the main session. Artifacts live under the review session's scratchpad `lang-out/<lang>/` and are available on request._
buildagent changed title from Wrong binds: name-only tiers bind builtin/std/global method calls to project methods (Rust .as_ref/.clone/.push, TS JSON.parse/Object.defineProperty) to Wrong binds: builtin/std/global-named method calls (Rust, TS), PHP parent:: and \Foo, residual django binds 2026-11-09 09:20:55 +01:00
Author
Member

Field case (dogfood 2026-11-09): tier3_import_boost binds std Write::flush to a test-local impl in ANOTHER test crate

find_callers(<Capture::flush in crates/daemon/tests/activation_offer_licence.rs:172>) returns 4 resolved callers:

  • builtin_displacement_e2e.rs:725 input.flush() on a ChildStdin
  • builtin_displacement_e2e.rs:737
  • doctor_path_claims_e2e.rs:156 stdin.flush()
  • release_gate_e2e.rs:796

All four are resolved_by: tier3_import_boost, influence.class: builtin_only, qualifier input/stdin, and the importing file has use std::io::Write;. A worker found it (it cost three source reads to disprove); the lead reproduced it. It appeared the moment a test added impl Write for Capture { fn flush }.

This adds two generic constraints to this issue's rule:

  1. flush is on Rust's std/prelude method surface (io::Write), so per the rule above a name-only tier must not decide it without receiver evidence naming the candidate's container.
  2. Crate boundary: every Rust tests/*.rs (and examples/*.rs, benches/*.rs, src/bin/*.rs) is its own crate root. A ref in one can bind to an item in another only through a shared mod file (e.g. tests/common/mod.rs). Name-only tiers must never cross crate roots. Precision decoy: two integration test files, one with a local impl Write for X { fn flush }, the other calling stdin.flush().
## Field case (dogfood 2026-11-09): `tier3_import_boost` binds std `Write::flush` to a test-local impl in ANOTHER test crate `find_callers(<Capture::flush in crates/daemon/tests/activation_offer_licence.rs:172>)` returns **4 resolved** callers: - `builtin_displacement_e2e.rs:725` `input.flush()` on a `ChildStdin` - `builtin_displacement_e2e.rs:737` - `doctor_path_claims_e2e.rs:156` `stdin.flush()` - `release_gate_e2e.rs:796` All four are `resolved_by: tier3_import_boost`, `influence.class: builtin_only`, qualifier `input`/`stdin`, and the importing file has `use std::io::Write;`. A worker found it (it cost three source reads to disprove); the lead reproduced it. It appeared the moment a test added `impl Write for Capture { fn flush }`. This adds two generic constraints to this issue's rule: 1. `flush` is on Rust's std/prelude method surface (`io::Write`), so per the rule above a name-only tier must not decide it without receiver evidence naming the candidate's container. 2. **Crate boundary:** every Rust `tests/*.rs` (and `examples/*.rs`, `benches/*.rs`, `src/bin/*.rs`) is its own crate root. A ref in one can bind to an item in another only through a shared `mod` file (e.g. `tests/common/mod.rs`). Name-only tiers must never cross crate roots. Precision decoy: two integration test files, one with a local `impl Write for X { fn flush }`, the other calling `stdin.flush()`.
Author
Member

Second field case from the lead's review (2026-11-09): ReadPool::get has 43 "resolved" callers, almost all wrong

find_callers(ReadPool::get) (crates/daemon/src/read_pool.rs:112) → 43 resolved rows, including:

  • crates/daemon/src/server.rs:585,587,593,598,600: p.get(k) on serde_json::Value params;
  • crates/daemon/tests/content_snapshot.rs:10,31,36,…: r.get(0) on a rusqlite Row.

get is the canonical std/prelude-surface name (HashMap/Value/Row/Option…). A worker's caller census for #512 was wrong because of this and had to be rebuilt from compiler errors. Add it to this issue's acceptance: after the fix, ReadPool::get's resolved callers are only self.conn.get()/pool receivers.

## Second field case from the lead's review (2026-11-09): `ReadPool::get` has 43 "resolved" callers, almost all wrong `find_callers(ReadPool::get)` (`crates/daemon/src/read_pool.rs:112`) → 43 resolved rows, including: - `crates/daemon/src/server.rs:585,587,593,598,600`: `p.get(k)` on `serde_json::Value` params; - `crates/daemon/tests/content_snapshot.rs:10,31,36,…`: `r.get(0)` on a rusqlite `Row`. `get` is the canonical std/prelude-surface name (`HashMap`/`Value`/`Row`/`Option`…). A worker's caller census for #512 was wrong because of this and had to be rebuilt from compiler errors. Add it to this issue's acceptance: after the fix, `ReadPool::get`'s resolved callers are only `self.conn.get()`/pool receivers.
Author
Member

Recall audit and owner decision, 2026-11-09.

codex-531 implemented the full rule. The bind diff ran on all 9 repos (shipped/shipped), and every removal was audited:

Repo Removed Audit
rust-ripgrep 189 full audit: 144 wrong / 40 correct
rust-analyzer ~2,200 sample: 8 wrong / 12 correct
ts-zod 16 16 wrong (JSON.parse, Object.defineProperty)
other 6 repos 0 unchanged

Every correct bind that was lost has no receiver fact yet: closure and macro-closure parameters, enum/Option patterns, captures, match chains. On ripgrep, all 28 sites whose RECORDED receiver binding contradicted the old target were wrong.

Decision (operator): ship the loss-free subset now.

  • Refuse when a recorded receiver binding contradicts the candidate.
  • JS/TS undeclared ambient globals.
  • A method import must name the impl self type.
  • The cross-crate root guard only if its removals audit 100% wrong.

The std/prelude method-surface rule for evidence-less receivers is DEFERRED. It stays in the code, switched off, until the guests emit receiver facts for those shapes, and that is the follow-up here. Acceptance for the subset is zero correct binds removed, audited per guard. A test pins that the evidence-less dir.remove(...) call stays bound.

Recall audit and owner decision, 2026-11-09. codex-531 implemented the full rule. The bind diff ran on all 9 repos (shipped/shipped), and every removal was audited: | Repo | Removed | Audit | |---|---:|---| | rust-ripgrep | 189 | full audit: 144 wrong / 40 correct | | rust-analyzer | ~2,200 | sample: 8 wrong / 12 correct | | ts-zod | 16 | 16 wrong (`JSON.parse`, `Object.defineProperty`) | | other 6 repos | 0 | unchanged | Every correct bind that was lost has no receiver fact yet: closure and macro-closure parameters, enum/Option patterns, captures, match chains. On ripgrep, all 28 sites whose RECORDED receiver binding contradicted the old target were wrong. **Decision (operator): ship the loss-free subset now.** - Refuse when a recorded receiver binding contradicts the candidate. - JS/TS undeclared ambient globals. - A method import must name the impl self type. - The cross-crate root guard only if its removals audit 100% wrong. The std/prelude method-surface rule for evidence-less receivers is DEFERRED. It stays in the code, switched off, until the guests emit receiver facts for those shapes, and that is the follow-up here. Acceptance for the subset is zero correct binds removed, audited per guard. A test pins that the evidence-less `dir.remove(...)` call stays bound.
Author
Member

Loss-free subset merged into integration-1109 (e1420994, branch commit b56e06ac). Nothing is pushed yet.

Guard Removed Audited Wrong Correct
(a) recorded receiver binding contradicts the candidate (decide_typed) 15 15 15 0
(b) undeclared JS/TS ambient global (name_only.rs ambient_globals) 16 16 16 0
(c) method import must name the impl self type: DROPPED 992 67 20 47
(d) independent Cargo crate roots (cargo_scope.rs refuse_name_roots) 0 in pins (4 live field cases) 4 4 0
  • Bind diff on all 9 repos, integration binary vs. branch: 31 removals, all wrong; 0 additions; 0 retargets. Six repos do not move at all.
  • 30 unchanged resolved method calls were also sampled: all correct.
  • Lead spot check: globset/src/lib.rs:343 (path: P: AsRef<Path>), ignore/src/walk.rs:331 (fs::DirEntry) and zod v3/types.ts:1439 (Number.isFinite) are all wrong binds and correctly removed.
  • Field case: Capture::flush goes from 4 resolved callers to 0.
  • precision_gate 7/7, phantom 0.
  • Mutations ran red: guards a, b and d each, plus "enable the broad rule", which the recall fixture catches (dir.remove stays bound).

Still open:

  • The evidence-less std-surface refusal is behind ENFORCE_EVIDENCELESS_STD_SURFACE = false. It needs guest receiver facts first: closure and macro-closure params, enum/Option patterns, captures, match/builder chains. decompress.rs:139 and the Error::clone guesses stay until then.
  • Guard (c) needs the same facts. On rust-analyzer, 40 of the 40 sampled removals were correct binds.
  • The PHP items.
Loss-free subset merged into `integration-1109` (`e1420994`, branch commit `b56e06ac`). Nothing is pushed yet. | Guard | Removed | Audited | Wrong | Correct | |---|---:|---:|---:|---:| | (a) recorded receiver binding contradicts the candidate (`decide_typed`) | 15 | 15 | 15 | 0 | | (b) undeclared JS/TS ambient global (`name_only.rs` `ambient_globals`) | 16 | 16 | 16 | 0 | | (c) method import must name the impl self type: **DROPPED** | 992 | 67 | 20 | **47** | | (d) independent Cargo crate roots (`cargo_scope.rs` `refuse_name_roots`) | 0 in pins (4 live field cases) | 4 | 4 | 0 | - Bind diff on all 9 repos, integration binary vs. branch: 31 removals, all wrong; 0 additions; 0 retargets. Six repos do not move at all. - 30 unchanged resolved method calls were also sampled: all correct. - Lead spot check: `globset/src/lib.rs:343` (`path: P: AsRef<Path>`), `ignore/src/walk.rs:331` (`fs::DirEntry`) and zod `v3/types.ts:1439` (`Number.isFinite`) are all wrong binds and correctly removed. - Field case: `Capture::flush` goes from 4 resolved callers to 0. - `precision_gate` 7/7, phantom 0. - Mutations ran red: guards a, b and d each, plus "enable the broad rule", which the recall fixture catches (`dir.remove` stays bound). **Still open:** - The evidence-less std-surface refusal is behind `ENFORCE_EVIDENCELESS_STD_SURFACE = false`. It needs guest receiver facts first: closure and macro-closure params, enum/Option patterns, captures, match/builder chains. `decompress.rs:139` and the `Error::clone` guesses stay until then. - Guard (c) needs the same facts. On rust-analyzer, 40 of the 40 sampled removals were correct binds. - The PHP items.
Author
Member

Status (local integration-1109 @ 60368c0d): py-django tier-3 resolved count is −42 in tier3-baseline.json.

  • The account: 56 binds were removed and all 56 were wrong; 14 were added and all 14 are correct.
  • The record was re-recorded with that reason, and the full gate run is green.
  • Still open: the deferred std-surface rule and the PHP items.
Status (local `integration-1109` @ 60368c0d): py-django tier-3 resolved count is −42 in `tier3-baseline.json`. - The account: 56 binds were removed and all 56 were wrong; 14 were added and all 14 are correct. - The record was re-recorded with that reason, and the full gate run is green. - Still open: the deferred std-surface rule and the PHP items.
Author
Member

Released in v0.41.48 (source PR #564). Shipped: refusal of method binds contradicted by recorded Rust receiver evidence, undeclared JS/TS runtime globals, and name-only cross-crate decisions.

Still open here: the evidence-less std-surface rule (kept disabled) and the PHP parent:: and leading-backslash items.

🤖 Generated with Claude Code

Released in [v0.41.48](https://git.h-dv.de/h-dv/code-index/releases/tag/v0.41.48) (source PR #564). Shipped: refusal of method binds contradicted by recorded Rust receiver evidence, undeclared JS/TS runtime globals, and name-only cross-crate decisions. Still open here: the evidence-less std-surface rule (kept disabled) and the PHP `parent::` and leading-backslash items. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Member

Status with v0.41.49 (released, PHP package 0.1.6 bundled)

  • PHP parent:: binds to the superclass declaration instead of the overriding method, and a leading-backslash name keeps its global identity for calls and constructors.
  • Bind account against v0.41.48 on the nine pinned repositories: only php-guzzle moved. 55 binds removed (all audited wrong), 57 added and 5 retargeted (all audited correct), no correct bind lost.

Stays open for: the refusal of evidence-less std/prelude method binds, which remains disabled until the receiver facts it needs are available.

**Status with v0.41.49** (released, PHP package 0.1.6 bundled) - PHP `parent::` binds to the superclass declaration instead of the overriding method, and a leading-backslash name keeps its global identity for calls and constructors. - Bind account against v0.41.48 on the nine pinned repositories: only php-guzzle moved. 55 binds removed (all audited wrong), 57 added and 5 retargeted (all audited correct), no correct bind lost. **Stays open for:** the refusal of evidence-less std/prelude method binds, which remains disabled until the receiver facts it needs are available.
buildagent changed title from Wrong binds: builtin/std/global-named method calls (Rust, TS), PHP parent:: and \Foo, residual django binds to Resolved references can name unrelated methods, global names and function-local types 2026-11-12 22:22:53 +01:00
Author
Member

Step 1, lexical containment: done on a lane, not yet merged (2026-11-13)

Merged into the train branch after v0.41.52 (integration-1114); nothing of it is in v0.41.52.

Rule. A declaration written inside a function, method or lambda body (function, class, struct, enum, interface, trait, type alias, const, static, module, namespace, and in Rust the impl block of a body-local type with its associated items) is a resolved target only for references inside the enclosing declaration's span. A reference outside loses the target and stays unresolved; it does not fall to another same-named candidate. Methods and fields of a local type are not covered, because a value of a local type can escape and be used elsewhere.

Coverage, decided by language semantics. Rust, C# (local functions), JavaScript and TypeScript, through a new profile field body_local_names. Not PHP (a function declared inside a function becomes global when the outer one runs) and not Ruby (a def inside a def defines a method on the enclosing class). Python is not covered yet: nested functions are returned and called elsewhere, and the package emits no fact that separates that from a wrong bind.

Measured before the fix (nine pinned repositories plus this one): references that resolve to a body-local declaration from outside its body: Rust 16, Python 124, TypeScript 2, others 0. Judged from the source: all 16 Rust binds first judged wrong (for the eight method calls among them that judgement is not certain, see below); of 20 sampled Python binds 13 correct (calls of returned or yielded functions) and 7 wrong; both TypeScript binds correct (methods of an escaping object, which the rule leaves alone).

Bind account, base against lane, joined on (path, line, col, kind, occurrence) over 1,172,552 reference keys: 1 removed (rust-analyzer: a callback parameter f bound to a local helper of another function, judged wrong), 0 added, 0 retargeted, 334,292 unchanged; 50 unchanged binds sampled per repository. No corpus baseline moves.

What the rule clears and what it leaves (all 16 Rust rows re-checked on the final tree, this repository included): 8 are cleared (1 in rust-analyzer, 7 in this repository; all targets are functions declared inside another function). 8 survive, and all 8 are METHODS of a body-local type reached by a method call from elsewhere (span_for of a local FakeSpanMap, try_fold_region of a local ErrorReplacer<_>, id of a local Probe in another crate). The rule deliberately leaves methods alone: a value of a local type can escape (returned as impl Trait, passed to a generic parameter) and its methods are then called outside, which a fixture test now pins. Whether such a call really reaches the local type is a receiver-identity question; it stays under "Receiver identity" below. The final account over the nine repositories plus this one: 10 binds removed (all judged wrong, two of them the new decoys), 0 added, 0 retargeted, 445,133 unchanged.

Cost. The first version raised full-scan steps by 6–10 % on every tier-1 repository and was sent back. The final statement starts from the body-local declarations and reaches references through idx_refs_target: all seven tier-1 records are within 0.1 % of cost-baseline.json on all four measures.

Checked by the lead on the shipped build shape with the released language packages: V::Value with a generic parameter V no longer resolves to the associated type of a function-local struct V (the #579 case); a Rust local type and a C# local function are resolved inside their body and unresolved outside.

Gates added: five fixture tests and four decoys in the precision gate (Rust, C#, JavaScript, TypeScript), each with its mutation run; populations grew, none shrank.

Still open under this ticket: the residual row ledger of #566; Python nested declarations and the generic-parameter qualifier rule, which need facts from the packages (step 2); receiver identity and the disabled std/prelude guard (step 3).

## Step 1, lexical containment: done on a lane, not yet merged (2026-11-13) Merged into the train branch after v0.41.52 (`integration-1114`); nothing of it is in v0.41.52. **Rule.** A declaration written inside a function, method or lambda body (function, class, struct, enum, interface, trait, type alias, const, static, module, namespace, and in Rust the `impl` block of a body-local type with its associated items) is a resolved target only for references inside the enclosing declaration's span. A reference outside loses the target and stays unresolved; it does not fall to another same-named candidate. Methods and fields of a local type are not covered, because a value of a local type can escape and be used elsewhere. **Coverage, decided by language semantics.** Rust, C# (local functions), JavaScript and TypeScript, through a new profile field `body_local_names`. Not PHP (a function declared inside a function becomes global when the outer one runs) and not Ruby (a `def` inside a `def` defines a method on the enclosing class). Python is not covered yet: nested functions are returned and called elsewhere, and the package emits no fact that separates that from a wrong bind. **Measured before the fix** (nine pinned repositories plus this one): references that resolve to a body-local declaration from outside its body: Rust 16, Python 124, TypeScript 2, others 0. Judged from the source: all 16 Rust binds first judged wrong (for the eight method calls among them that judgement is not certain, see below); of 20 sampled Python binds 13 correct (calls of returned or yielded functions) and 7 wrong; both TypeScript binds correct (methods of an escaping object, which the rule leaves alone). **Bind account**, base against lane, joined on (path, line, col, kind, occurrence) over 1,172,552 reference keys: 1 removed (rust-analyzer: a callback parameter `f` bound to a local helper of another function, judged wrong), 0 added, 0 retargeted, 334,292 unchanged; 50 unchanged binds sampled per repository. No corpus baseline moves. **What the rule clears and what it leaves** (all 16 Rust rows re-checked on the final tree, this repository included): 8 are cleared (1 in rust-analyzer, 7 in this repository; all targets are functions declared inside another function). 8 survive, and all 8 are METHODS of a body-local type reached by a method call from elsewhere (`span_for` of a local `FakeSpanMap`, `try_fold_region` of a local `ErrorReplacer<_>`, `id` of a local `Probe` in another crate). The rule deliberately leaves methods alone: a value of a local type can escape (returned as `impl Trait`, passed to a generic parameter) and its methods are then called outside, which a fixture test now pins. Whether such a call really reaches the local type is a receiver-identity question; it stays under "Receiver identity" below. The final account over the nine repositories plus this one: 10 binds removed (all judged wrong, two of them the new decoys), 0 added, 0 retargeted, 445,133 unchanged. **Cost.** The first version raised full-scan steps by 6–10 % on every tier-1 repository and was sent back. The final statement starts from the body-local declarations and reaches references through `idx_refs_target`: all seven tier-1 records are within 0.1 % of `cost-baseline.json` on all four measures. **Checked by the lead** on the shipped build shape with the released language packages: `V::Value` with a generic parameter `V` no longer resolves to the associated type of a function-local `struct V` (the #579 case); a Rust local type and a C# local function are resolved inside their body and unresolved outside. **Gates added:** five fixture tests and four decoys in the precision gate (Rust, C#, JavaScript, TypeScript), each with its mutation run; populations grew, none shrank. **Still open under this ticket:** the residual row ledger of #566; Python nested declarations and the generic-parameter qualifier rule, which need facts from the packages (step 2); receiver identity and the disabled std/prelude guard (step 3).
Author
Member

Declarations inside function bodies in Rust, C#, JavaScript and TypeScript are no longer resolved as targets of references outside those bodies. Method calls on function-local types, Python nested declarations and receiver identity remain.

Declarations inside function bodies in Rust, C#, JavaScript and TypeScript are no longer resolved as targets of references outside those bodies. Method calls on function-local types, Python nested declarations and receiver identity remain.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
h-dv/code-index#531
No description provided.