Skip to content

Fix panic from cross-crate FnDef impl-path collision#2559

Merged
mmcloughlin merged 2 commits into
verus-lang:mainfrom
mmcloughlin:derive-copy-panic
Jun 14, 2026
Merged

Fix panic from cross-crate FnDef impl-path collision#2559
mmcloughlin merged 2 commits into
verus-lang:mainfrom
mmcloughlin:derive-copy-panic

Conversation

@mmcloughlin

Copy link
Copy Markdown
Collaborator

The synthetic FnDef trait-impl that Verus generates for each clone method built its impl path from the function's path segments only, dropping the crate and placing it under CrateId::Internal.

Two crate-root impls in different crates can share a rustc disambiguator. For example verus_builtin has Clone for Ghost assigned to impl&%7, which can collide with a definition in a user crate.

This PR uses the function's krate in the path instead of CrateId::Internal. Adds a regression test.

Related: #2474


By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

Comment on lines +2045 to +2047
// The test defines 12 `Clone` impls in the crate root, which are enough to
// collide with the `Clone` impls for `Ghost` and `Tracked` in
// `verus_builtin` at the same disambiguator (brought in by `["vstd"]`).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite a fragile test in that it relies on a disambiguator collision with definitions in verus_builtin.

The ideal test would setup two synthetic crates to force a collision. However, I'm not sure there's an easy way to setup multi-crate tests? Most rust_verify_test tests are single file.

@tjhance tjhance left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definition of impl_fndef still seems a little dubious to me:

pub(crate) fn impl_fndef(fun: &Fun, kind: ClosureKind) -> Ident {
    let joined =
        fun.path.segments.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(PATH_SEPARATOR);
    Arc::new(format!("{}{}{}", PREFIX_IMPL_FNDEF, kind, joined))
}

e.g. it seems like there could be a collision if the first segment of joined starts with "Once" or "Mut"?

Can we come up with a different strategy entirely here? There doesnt' seem to be any reason to collapse the segments. We could do something like:

fun.path.push_segment(format!("{}{}", PREFIX_IMPL_FNDEF, kind))

mmcloughlin added a commit to mmcloughlin/verus that referenced this pull request Jun 14, 2026
Per review feedback on verus-lang#2559: rather than flattening the function's path
segments into a single `impl_fndef` ident and stamping the crate back on,
push the per-kind marker as its own segment onto the function's path. This
keeps the crate in `Path.krate` and avoids ambiguity at the kind/segment
boundary (e.g. kind `Fn` followed by a segment starting with `Once` vs kind
`FnOnce`).
The synthetic `FnDef : Fn{,Mut,Once}` trait-impl that Verus generates for
each `clone` method built its impl path from the function's path segments
only, dropping the crate and placing it under CrateId::Internal. Two
crate-root impls in different crates can share a rustc disambiguator
(e.g. `impl&%7` is both verus_builtin's `Clone for Ghost` and a user
crate's clone impl), so their synthetic paths collided, tripping the
`!trait_impl_map.contains_key(..)` assertion in GlobalCtx::new.

Carry the function's own crate in the impl path instead. Adds a
regression test.
Per review feedback on verus-lang#2559: rather than flattening the function's path
segments into a single `impl_fndef` ident and stamping the crate back on,
push the per-kind marker as its own segment onto the function's path. This
keeps the crate in `Path.krate` and avoids ambiguity at the kind/segment
boundary (e.g. kind `Fn` followed by a segment starting with `Once` vs kind
`FnOnce`).
@mmcloughlin

Copy link
Copy Markdown
Collaborator Author

Can we come up with a different strategy entirely here? There doesnt' seem to be any reason to collapse the segments. We could do something like:

fun.path.push_segment(format!("{}{}", PREFIX_IMPL_FNDEF, kind))

Much nicer! Implemented. Please take another look @tjhance.

@mmcloughlin mmcloughlin requested a review from tjhance June 14, 2026 19:02
@mmcloughlin mmcloughlin added this pull request to the merge queue Jun 14, 2026
Merged via the queue into verus-lang:main with commit 4ea7d0f Jun 14, 2026
13 checks passed
@mmcloughlin mmcloughlin deleted the derive-copy-panic branch June 14, 2026 20:05
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.

2 participants