Receiver-binding phantom: a read of pkg.digest binds a same-named pub field in another crate, and localising the receiver does not move it #270

Open
opened 2026-09-13 17:32:35 +02:00 by buildagent · 0 comments
Member

Found while dogfooding during I066 (#269 follow-up). Not blocking that branch — it was worked around — but the defect is live on master.

What happened

crates/indexer/tests/manifest_layering.rs refuses a RESOLVED edge across a crate boundary Cargo does not declare. It went red on a new test file in crates/cli/tests/ because a read of pkg.digest resolved to Staged::digest, a pub field in crates/plugin-host/tests/support/pkg.rs.

crates/cli does not depend on crates/plugin-host. The bind is a phantom.

Why it is a receiver-binding defect and not a name collision

Two measurements make that specific:

  1. Other reads of the same expression, in the same file, resolved correctly. So it is not "the name digest is ambiguous" — the resolver got it right at some sites and wrong at another.
  2. Binding the receiver to a local first did not move it. let pin = pkg.digest.clone(); produced the same phantom. If the receiver's type were being carried through the local, the bind would have followed it.

That points at tier-1R receiver resolution rather than at the bare-name pools.

Why it matters beyond one test

The gate that caught this exists to refuse cross-crate resolved edges, so in this instance the tree was protected. The general case is not: a read/member_access that binds to a same-named field in an unrelated crate is a wrong answer that find_references and change_impact will repeat with no signal, and manifest_layering only sees it when the two crates have no declared edge. Two crates that do have an edge would hide it entirely.

Repro shape

  • a pub field named <name> on a struct in crate A's test support;
  • a struct in crate B's tests with a field of the same name;
  • crate B does not depend on crate A;
  • read receiver.<name> in crate B, in a file where other reads of the same expression bind correctly.

Workaround applied on the I066 branch

Fixture::digest renamed to package_digest, which is also the honest vocabulary — it is what plugin pack prints and what the distribution catalog calls it. The measurement is recorded on the field's doc so the rename is not read as cosmetic.

Suggested first step

Reproduce on master with a minimal fixture, then inspect the binds rather than the counts — diff (path, line, col, kind, occurrence) against the prior binary and split by resolved_by, since position alone fans out. If the wrong bind is attributed to a tier-1R rule, the question is which evidence admitted a receiver whose type is not in scope.

Filed by the I066 distribution-truth work; the gate that caught it is crates/indexer/tests/manifest_layering.rs.

Found while dogfooding during I066 (#269 follow-up). Not blocking that branch — it was worked around — but the defect is live on master. ## What happened `crates/indexer/tests/manifest_layering.rs` refuses a RESOLVED edge across a crate boundary Cargo does not declare. It went red on a new test file in `crates/cli/tests/` because a `read` of `pkg.digest` resolved to **`Staged::digest`**, a `pub` field in `crates/plugin-host/tests/support/pkg.rs`. `crates/cli` does not depend on `crates/plugin-host`. The bind is a phantom. ## Why it is a receiver-binding defect and not a name collision Two measurements make that specific: 1. **Other reads of the same expression, in the same file, resolved correctly.** So it is not "the name `digest` is ambiguous" — the resolver got it right at some sites and wrong at another. 2. **Binding the receiver to a local first did not move it.** `let pin = pkg.digest.clone();` produced the same phantom. If the receiver's type were being carried through the local, the bind would have followed it. That points at tier-1R receiver resolution rather than at the bare-name pools. ## Why it matters beyond one test The gate that caught this exists to refuse cross-crate resolved edges, so in this instance the tree was protected. The general case is not: a `read`/`member_access` that binds to a same-named field in an unrelated crate is a wrong answer that `find_references` and `change_impact` will repeat with no signal, and `manifest_layering` only sees it when the two crates have no declared edge. Two crates that *do* have an edge would hide it entirely. ## Repro shape - a `pub` field named `<name>` on a struct in crate A's test support; - a struct in crate B's tests with a field of the same name; - crate B does not depend on crate A; - read `receiver.<name>` in crate B, in a file where other reads of the same expression bind correctly. ## Workaround applied on the I066 branch `Fixture::digest` renamed to `package_digest`, which is also the honest vocabulary — it is what `plugin pack` prints and what the distribution catalog calls it. The measurement is recorded on the field's doc so the rename is not read as cosmetic. ## Suggested first step Reproduce on master with a minimal fixture, then inspect the binds rather than the counts — diff `(path, line, col, kind, occurrence)` against the prior binary and split by `resolved_by`, since position alone fans out. If the wrong bind is attributed to a tier-1R rule, the question is which evidence admitted a receiver whose type is not in scope. Filed by the I066 distribution-truth work; the gate that caught it is `crates/indexer/tests/manifest_layering.rs`.
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.

Dependencies

No dependencies set

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