refactor: handle impls correctly by moving instead of dropping them - #1929
Conversation
a7914dd to
ec26c55
Compare
ec26c55 to
1eeef4c
Compare
1eeef4c to
1a14511
Compare
1a14511 to
8e0fdc6
Compare
8e0fdc6 to
bc21f43
Compare
bc21f43 to
9b9adb2
Compare
9b9adb2 to
96ca5e6
Compare
96ca5e6 to
0739482
Compare
0739482 to
2fca703
Compare
2fca703 to
52cf39f
Compare
52cf39f to
1ff0704
Compare
1ff0704 to
1f4904f
Compare
1f4904f to
f675e52
Compare
f675e52 to
90c2469
Compare
9857a1f to
7d232db
Compare
7d232db to
7e42a9d
Compare
7e42a9d to
15af7d6
Compare
15af7d6 to
ad6ed94
Compare
384403a to
b568db2
Compare
3c8e5b6 to
b1d194d
Compare
b1d194d to
0a6c948
Compare
thedataking
left a comment
There was a problem hiding this comment.
Review of stack 1953: two compile regressions in deferred impl handling, reproduced with compiling inputs and compared against the earlier transform.
| pending_impl_items | ||
| .entry(dest_module_id) | ||
| .or_default() | ||
| .push(item); |
There was a problem hiding this comment.
[P2] Account for deferred impls when merging their self types
Pending impls bypass the declaration deduplication logic and are all appended after path_mapping has unified their self types. If two equivalent header types each have two const-only impl blocks, the first impl is carried and deduplicated with the type, but both copies of the second impl are appended to the same resulting type. The output then fails with E0592 (duplicate associated constant B).
The input below compiles, and the earlier transform produced compiling output. Please include the deferred impls in type-equivalence decisions and deduplicate equivalent blocks when their self types merge. If the deferred impls differ, those differences also need to affect whether the self types can be unified.
Reproducer
Run c2rust-refactor reorganize_definitions --rewrite-mode alongside -- repro.rs --edition=2021 and compile the resulting repro.new.
#![feature(register_tool)]
#![register_tool(c2rust)]
#![allow(dead_code, non_camel_case_types)]
pub mod a {
#[c2rust::header_src = "/tmp/types.h:1"]
pub mod types_h {
pub struct Thing { pub x: i32 }
impl Thing { pub const A: i32 = 1; }
impl Thing { pub const B: i32 = 2; }
}
pub fn go() -> i32 { types_h::Thing::A }
}
pub mod b {
#[c2rust::header_src = "/tmp/types.h:1"]
pub mod types_h {
pub struct Thing { pub x: i32 }
impl Thing { pub const A: i32 = 1; }
impl Thing { pub const B: i32 = 2; }
}
pub fn go() -> i32 { types_h::Thing::A }
}
fn main() {}| } | ||
| }; | ||
| let mut item = moved_impl.item; | ||
| self.canonicalize_moved_decl_paths(&mut item, dest_module_id, &matching_defs); |
There was a problem hiding this comment.
[P2] Rewrite the trait reference before moving a trait impl
canonicalize_moved_decl_paths uses fold_resolved_paths_with_id, which does not visit TraitRef paths. When an impl moves to its self type's module, its trait name can therefore remain relative to the old header scope.
In the input below, the trait moves to crate::user::MyTrait and the impl moves to defs, but the emitted impl still says impl MyTrait for crate::defs::Thing. The input compiles; the output fails with E0405 because MyTrait is not in scope in defs. Please rewrite the impl's trait reference, including any trait paths in bounds, as part of relocation.
This exercises the trait-impl support introduced here with edited Rust code.
Reproducer
Run c2rust-refactor reorganize_definitions --rewrite-mode alongside -- repro.rs --edition=2021 and compile the resulting repro.new.
#![feature(register_tool)]
#![register_tool(c2rust)]
#![allow(dead_code, non_camel_case_types)]
pub mod defs {
pub struct Thing { pub x: i32 }
}
pub mod user {
#[c2rust::header_src = "/tmp/user.h:1"]
pub mod user_h {
pub trait MyTrait { fn val(&self) -> i32; }
impl MyTrait for crate::defs::Thing {
fn val(&self) -> i32 { self.x }
}
}
pub fn go() {}
}
fn main() {}0a6c948 to
b5f3ef2
Compare
b5f3ef2 to
5194bc7
Compare
Regression test (documents the current broken behavior): `test_reorganize_orphaned_impls` (`tests/snapshots/reorganize_orphaned_impls.rs`). It covers both loss paths — a const-only impl whose self type is defined outside the headers (orphaned `impls` entry) and an impl containing a method (never saved) — and asserts via snapshot that both vanish; the output no longer compiles, marked with `new_expect_compile_error(true)`. When this finding is fixed, drop that flag and update the snapshot to show the impls surviving.
At most one const-only inherent impl per self-type `DefId` can be carried alongside a moved declaration (through `MovedDecl::r#impl`, unchanged from before). Everything else that `remove_header_items` pulls out of a header module — trait impls, impls with non-`const` items, extra impls beyond the first for a given type, and const-only impls whose self type is never itself individually processed as a moved declaration (e.g. because it's defined outside any header module) — is now collected into a new `Reorganizer::pending_impls: Vec<(DefId, MovedDeclImpl)>` field instead of being dropped. An impl whose self type doesn't resolve to a plain path to a definition at all (generic, reference, etc.) is left exactly where it was, with a warning, since there's nothing to key it on. `move_items` resolves each pending impl's destination once `path_mapping` is complete: if the self type itself moved, its destination module is already recorded there; otherwise the self type is a pre-existing definition, and the module it's already defined in is looked up via `parent_module_from_def_id`. The impl's item is then run through the same `canonicalize_moved_decl_paths` used for #5 (now taking a `NodeId` instead of a borrowed `&ModuleInfo`, so it also works for destinations outside `self.modules`) and appended directly into that module's items — at both places a destination module's items can end up populated (the "module already exists in the crate" sweep and the "create a new module" fallback), so a pending impl can land correctly regardless of which path its destination module takes. Note this fix does *not* leave non-qualifying impls physically in place in their original header submodule: an earlier version of the fix did that, but it collides with `find_destination_id`, which independently creates a same-named *new* destination module for any other content from the same header that still needs relocating (since `unique_ident` doesn't know about the surviving header submodule) — two modules end up with the same name. Reattaching every impl to its self type's actual destination avoids this. Regression test: `test_reorganize_orphaned_impls` (`tests/snapshots/reorganize_orphaned_impls.rs`) covers both loss paths — a const-only impl whose self type is defined outside the headers, and an impl containing a non-const method — attached to a struct defined in an ordinary, non-header module. Verified against the pre-fix transform (this session's starting commit) that removing the fix reproduces the original failure (`E0599: no associated item named DEFAULT_X` / `no method named reset`); with the fix, both impls are reattached next to their self type and the output compiles. `:345-404`. The `retain` closure returns `false` for *every* `ItemKind::Impl`, but only const-only inherent impls with a resolvable simple self-type path get saved into the `impls` map. Trait impls, impls containing anything but consts, and impls whose self type can't be resolved are dropped with no warning. Additionally, saved impls are only re-attached when the matching decl flows through `insert_item` (`impls.remove(&new_def_id)` at `:469`). If the impl's self type resolves to a def that is not itself a moved header item (e.g. resolved through a `use` to a type defined in a source module, or the decl was dropped as an unused non-exported import at `:447-461`), the entry is never consumed and the impl — including its constants — vanishes from the crate.
5194bc7 to
4efad87
Compare
Stack created with GitHub Stacks CLI • Give Feedback 💬