From 95a77bf4b5f8ede5053496c47f26751ce7de7974 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Fri, 30 Oct 2026 17:56:03 +0100 Subject: [PATCH] cpp: include exported resource class headers after interface types (#1658) Previously, user class header files (`exports-*.h`) were included at the top of `_cpp.h`. When an exported resource method or constructor accepted or returned records or variants defined in the interface, the user class header failed to compile because the WIT types had not been declared yet. This commit resolves the issue by: 1. Moving user class file includes (`exports-*.h`) to the bottom of the header, ensuring all records and types are fully defined beforehand. 2. Forward-declaring exported resource classes in their respective namespaces so types and functions in the world header can reference them. 3. Referring to owned handles of exported resources as `wit::ResourceExportBase::Owned` instead of `Resource::Owned` in the header, which is valid for incomplete forward-declared types. 4. Merging dependencies from user class files so helper headers like `wit.h` are included and emitted. Closes #1658. --- crates/cpp/src/lib.rs | 83 +++++++++++++++++++++++++++++++++--- tests/codegen/issue-1658.wit | 16 +++++++ 2 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 tests/codegen/issue-1658.wit diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index bb9c623c6..bd350a026 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -18,7 +18,7 @@ use wit_bindgen_core::{ wit_parser::{ Alignment, ArchitectureSize, Docs, Function, FunctionKind, Handle, Int, InterfaceId, Param, Resolve, SizeAlign, Stability, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, WorldId, - WorldKey, + WorldItem, WorldKey, }, }; use wit_parser::TypeIdVisitor; @@ -79,6 +79,27 @@ struct Includes { needs_array: bool, } +impl Includes { + fn merge(&mut self, other: &Includes) { + self.needs_vector |= other.needs_vector; + self.needs_expected |= other.needs_expected; + self.needs_string |= other.needs_string; + self.needs_string_view |= other.needs_string_view; + self.needs_optional |= other.needs_optional; + self.needs_cstring |= other.needs_cstring; + self.needs_imported_resources |= other.needs_imported_resources; + self.needs_exported_resources |= other.needs_exported_resources; + self.needs_variant |= other.needs_variant; + self.needs_tuple |= other.needs_tuple; + self.needs_assert |= other.needs_assert; + self.needs_bit |= other.needs_bit; + self.needs_span |= other.needs_span; + self.needs_wit |= other.needs_wit; + self.needs_memory |= other.needs_memory; + self.needs_array |= other.needs_array; + } +} + #[derive(Default)] struct SourceWithState { src: Source, @@ -105,6 +126,7 @@ struct Cpp { imported_interfaces: HashSet, instance_names: HashMap, user_class_files: HashMap, + user_class_includes: Vec, defined_types: HashSet<(Vec, String)>, types: Types, @@ -460,6 +482,7 @@ impl Cpp { includes: std::mem::take(&mut self.includes), src: std::mem::take(&mut self.h_src), dependencies: std::mem::take(&mut self.dependencies), + user_class_includes: std::mem::take(&mut self.user_class_includes), } } else { Default::default() @@ -476,6 +499,9 @@ impl Cpp { uwriteln!(header, "#include {include}"); } header.push_str(&self.h_src.src); + for include in self.user_class_includes.iter() { + uwriteln!(header, "#include {include}"); + } let mut filename = namespace.join("-"); filename.push_str(".h"); if self.opts.format { @@ -485,8 +511,13 @@ impl Cpp { let _ = std::mem::replace(&mut self.includes, store.includes); let _ = std::mem::replace(&mut self.h_src, store.src); - let _ = std::mem::replace(&mut self.dependencies, store.dependencies); - self.includes.push(String::from("\"") + &filename + "\""); + let file_deps = std::mem::replace(&mut self.dependencies, store.dependencies); + self.dependencies.merge(&file_deps); + let _ = std::mem::replace(&mut self.user_class_includes, store.user_class_includes); + let inc = String::from("\"") + &filename + "\""; + if !self.user_class_includes.contains(&inc) { + self.user_class_includes.push(inc); + } } } } @@ -496,6 +527,7 @@ struct FileContext { includes: Vec, src: SourceWithState, dependencies: Includes, + user_class_includes: Vec, } impl WorldGenerator for Cpp { @@ -504,6 +536,27 @@ impl WorldGenerator for Cpp { self.world = name.to_string(); self.types.analyze(resolve); self.world_id = Some(world); + for (name, item) in &resolve.worlds[world].exports { + if let WorldItem::Interface { id, .. } = item { + self.update_instance_name(resolve, name, *id); + } + } + for (_name, item) in &resolve.worlds[world].exports { + if let WorldItem::Interface { id, .. } = item { + let namespc = namespace(resolve, &TypeOwner::Interface(*id), true, self); + for &type_id in resolve.interfaces[*id].types.values() { + let ty = &resolve.types[type_id]; + if matches!(ty.kind, TypeDefKind::Resource) { + if let Some(name) = &ty.name { + let pascal = name.to_upper_camel_case(); + self.h_src.change_namespace(&namespc); + uwriteln!(self.h_src.src, "class {pascal};"); + } + } + } + } + } + self.h_src.change_namespace(&Vec::default()); uwriteln!( self.c_src_head, r#"#include "{}_cpp.h" @@ -759,6 +812,10 @@ impl WorldGenerator for Cpp { self.h_src.change_namespace(&Vec::default()); h_str.src.push_str(&self.h_src.src); + for include in self.user_class_includes.iter() { + uwriteln!(h_str.src, "#include {include}"); + } + uwriteln!(c_str.src, "\n// Component Adapters"); uwriteln!( @@ -916,6 +973,16 @@ impl CppInterfaceGenerator<'_> { fn types(&mut self, iface: InterfaceId) { let iface_data = &self.resolve().interfaces[iface]; + for &id in iface_data.types.values() { + let ty = &self.resolve().types[id]; + if matches!(ty.kind, TypeDefKind::Resource) { + if let Some(name) = &ty.name { + let pascal = name.to_upper_camel_case(); + uwriteln!(self.r#gen.h_src.src, "class {pascal};"); + } + } + } + // Here we sort the types topologically before emitting code for them, // taking into consideration the parameter and return types of functions // associated with resource types. This ensures each type is declared @@ -1685,7 +1752,10 @@ impl CppInterfaceGenerator<'_> { | (true, Flavor::Result(AbiVariant::GuestImport)) => { // Only exported resources have ::Owned typedef if is_exported { - typename.push_str(&format!("::{OWNED_CLASS_NAME}")) + self.r#gen.dependencies.needs_wit = true; + typename = format!( + "wit::{RESOURCE_EXPORT_BASE_CLASS_NAME}<{typename}>::{OWNED_CLASS_NAME}" + ); } else { typename.push_str("&&") } @@ -1697,7 +1767,10 @@ impl CppInterfaceGenerator<'_> { (_, _) => todo!(), } if matches!(flavor, Flavor::InStruct) && is_exported { - typename.push_str(&format!("::{OWNED_CLASS_NAME}")) + self.r#gen.dependencies.needs_wit = true; + typename = format!( + "wit::{RESOURCE_EXPORT_BASE_CLASS_NAME}<{typename}>::{OWNED_CLASS_NAME}" + ); } typename } diff --git a/tests/codegen/issue-1658.wit b/tests/codegen/issue-1658.wit new file mode 100644 index 000000000..9a780d8a6 --- /dev/null +++ b/tests/codegen/issue-1658.wit @@ -0,0 +1,16 @@ +package test:issue1658; + +interface options { + record options { + flag: bool, + } + + resource client { + constructor(o: options); + get-flag: func() -> bool; + } +} + +world test { + export options; +}