diff --git a/c2rust-refactor/src/command.rs b/c2rust-refactor/src/command.rs index 746b02965c..fffa488ab6 100644 --- a/c2rust-refactor/src/command.rs +++ b/c2rust-refactor/src/command.rs @@ -735,6 +735,14 @@ impl CommandState { self.node_id_counter.next() } + /// Register a `NodeId` handed out by `next_node_id` as belonging to a node + /// this command built itself, the same way ids of parsed nodes are + /// registered. Without this the `NodeMap` never learns the node exists, so + /// anything keyed on it - `add_comment` in particular - is dropped. + pub fn register_new_node_id(&self, id: NodeId) { + self.new_parsed_node_ids.borrow_mut().push(id); + } + /// Transfer marks on `old` to a fresh NodeId, and return that fresh NodeId. pub fn transfer_marks(&self, old: NodeId) -> NodeId { let new = self.next_node_id(); diff --git a/c2rust-refactor/src/transform/reorganize_definitions.rs b/c2rust-refactor/src/transform/reorganize_definitions.rs index d77b30678a..0c7cf6b735 100644 --- a/c2rust-refactor/src/transform/reorganize_definitions.rs +++ b/c2rust-refactor/src/transform/reorganize_definitions.rs @@ -1987,49 +1987,60 @@ impl<'a, 'tcx> HeaderDeclarations<'a, 'tcx> { } }); - let mut items: Vec
> = Vec::new();
- let mut foreign_items: HashMap >> = IndexMap::new();
for item in all_items {
let cur_mod_name = item.parent_header.ident;
match item.kind {
- DeclKind::Item(i) => {
- if last_item_mod != Some(cur_mod_name) {
- st.add_comment(i.id, make_header_comment(last_item_mod, cur_mod_name));
- last_item_mod = Some(cur_mod_name);
- }
- items.push(i);
- }
+ DeclKind::Item(i) => items.push((cur_mod_name, i)),
DeclKind::ForeignItem(fi, abi) => {
- if last_foreign_item_mod != Some(cur_mod_name) {
- st.add_comment(
- fi.id,
- make_header_comment(last_foreign_item_mod, cur_mod_name),
- );
- last_foreign_item_mod = Some(cur_mod_name);
- }
- foreign_items.entry(abi).or_default().push(fi);
+ foreign_items
+ .entry((cur_mod_name, abi))
+ .or_default()
+ .push(fi);
}
}
// If there is an impl item, add it now.
if let Some(r#impl) = item.r#impl {
- let cur_mod_name = r#impl.parent_header.ident;
- let i = r#impl.item;
- if last_item_mod != Some(cur_mod_name) {
- st.add_comment(i.id, make_header_comment(last_item_mod, cur_mod_name));
- last_item_mod = Some(cur_mod_name);
- }
- items.push(i);
+ items.push((r#impl.parent_header.ident, r#impl.item));
}
}
- let foreign_mods = foreign_items
- .into_iter()
- .map(|(abi, items)| mk().extern_(abi).foreign_items(items));
+ let foreign_mods = foreign_items.into_iter().map(|((header, abi), items)| {
+ // A fresh, registered `NodeId` so the header comment below has
+ // something to attach to; comments on the foreign items inside the
+ // block never reach the output, since the whole block is
+ // pretty-printed as one new item.
+ let id = st.next_node_id();
+ st.register_new_node_id(id);
+ (header, mk().id(id).extern_(abi).foreign_items(items))
+ });
- foreign_mods.chain(items.into_iter()).collect()
+ // Assign the header comments while walking the items in the order they
+ // are emitted. The `extern` blocks are hoisted ahead of every other
+ // item, so a header can be entered, left, and entered again in the
+ // final layout; walking the declarations in their pre-hoisting order
+ // instead would describe a layout that never gets written out.
+ let mut last_mod = None;
+ foreign_mods
+ .chain(items)
+ .map(|(header, item)| {
+ if last_mod != Some(header) {
+ st.add_comment(item.id, make_header_comment(last_mod, header));
+ last_mod = Some(header);
+ }
+ item
+ })
+ .collect()
}
fn find_item<'b>(
diff --git a/c2rust-refactor/tests/snapshots.rs b/c2rust-refactor/tests/snapshots.rs
index 031bbe56f5..13a603f494 100644
--- a/c2rust-refactor/tests/snapshots.rs
+++ b/c2rust-refactor/tests/snapshots.rs
@@ -463,6 +463,20 @@ fn test_reorganize_bitfield_ty() {
.test();
}
+/// Foreign items are grouped into `extern` blocks by header and ABI, and the
+/// groups are emitted in the order they are first encountered rather than in
+/// `HashMap` iteration order, so the output is the same from run to run. The
+/// `BEGIN`/`END` header comments are assigned over the final item sequence, so
+/// they describe the layout that is actually emitted: `aa_h` is entered twice
+/// here, once for its `extern` blocks and again for its struct, because the
+/// blocks are hoisted ahead of every regular item.
+#[test]
+fn test_reorganize_extern_block_order() {
+ refactor("reorganize_definitions")
+ .named("reorganize_extern_block_order.rs")
+ .test();
+}
+
/// Two foreign declarations of the same function that differ in the number
/// of parameters are not interchangeable and must not be merged.
#[test]
diff --git a/c2rust-refactor/tests/snapshots/reorganize_extern_block_order.rs b/c2rust-refactor/tests/snapshots/reorganize_extern_block_order.rs
new file mode 100644
index 0000000000..dac3a6979b
--- /dev/null
+++ b/c2rust-refactor/tests/snapshots/reorganize_extern_block_order.rs
@@ -0,0 +1,69 @@
+#![feature(register_tool)]
+#![register_tool(c2rust)]
+#![allow(non_camel_case_types)]
+#![allow(dead_code)]
+
+// The destination all the standard library headers below are merged into.
+// It has to already exist for the `BEGIN`/`END` header comments to make it
+// into the output.
+pub mod stdlib {
+ pub const STDLIB_MARKER: i32 = 0;
+}
+
+pub mod api {
+ // Both headers are under `/usr/include`, so they are treated as standard
+ // library headers and all of their declarations land in the same `stdlib`
+ // destination module. Each header mixes a regular item with foreign items,
+ // and `aa.h` declares two different ABIs.
+ //
+ // `into_items` groups the foreign items by ABI into one `extern` block
+ // each and hoists every block ahead of every regular item, so:
+ //
+ // - the relative order of the two `extern` blocks comes from iterating a
+ // `HashMap