From da177159cab3a6833a9b0cfa866ffbf971e1a46f Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 6 Mar 2026 11:39:11 -0800 Subject: [PATCH 1/4] Basic setup of `wit-bindgen-d` --- Cargo.lock | 15 + Cargo.toml | 6 +- crates/d/Cargo.toml | 33 ++ crates/d/LICENSE-APACHE | 1 + .../d/LICENSE-Apache-2.0_WITH_LLVM-exception | 1 + crates/d/LICENSE-MIT | 1 + crates/d/README.md | 17 + crates/d/src/lib.rs | 327 ++++++++++++++++++ src/bin/wit-bindgen.rs | 11 + 9 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 crates/d/Cargo.toml create mode 120000 crates/d/LICENSE-APACHE create mode 120000 crates/d/LICENSE-Apache-2.0_WITH_LLVM-exception create mode 120000 crates/d/LICENSE-MIT create mode 100644 crates/d/README.md create mode 100644 crates/d/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index afc846c84..9d7f3eed6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1496,6 +1496,7 @@ dependencies = [ "wit-bindgen-core", "wit-bindgen-cpp", "wit-bindgen-csharp", + "wit-bindgen-d", "wit-bindgen-go", "wit-bindgen-markdown", "wit-bindgen-moonbit", @@ -1544,6 +1545,20 @@ dependencies = [ "wit-parser", ] +[[package]] +name = "wit-bindgen-d" +version = "0.53.1" +dependencies = [ + "anyhow", + "clap", + "heck", + "indexmap", + "wasm-encoder 0.245.1", + "wasm-metadata 0.245.1", + "wit-bindgen-core", + "wit-component", +] + [[package]] name = "wit-bindgen-go" version = "0.53.1" diff --git a/Cargo.toml b/Cargo.toml index 86e224127..489ce7fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ wit-bindgen-csharp = { path = 'crates/csharp', version = '0.53.1' } wit-bindgen-markdown = { path = 'crates/markdown', version = '0.53.1' } wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.53.1' } wit-bindgen-go = { path = 'crates/go', version = '0.53.1' } +wit-bindgen-d = { path = 'crates/d', version = '0.53.1' } wit-bindgen = { path = 'crates/guest-rust', version = '0.53.1', default-features = false } wit-bindgen-test = { path = 'crates/test', version = '0.53.1' } @@ -94,6 +95,7 @@ wit-bindgen-markdown = { workspace = true, features = ['clap'], optional = true wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true } wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } wit-bindgen-go = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-d = { workspace = true, features = ['clap'], optional = true } wit-bindgen-test = { workspace = true } wit-component = { workspace = true } wasm-encoder = { workspace = true } @@ -108,7 +110,8 @@ default = [ 'csharp', 'cpp', 'moonbit', - 'async', + 'd', + 'async' ] c = ['dep:wit-bindgen-c'] cpp = ['dep:wit-bindgen-cpp'] @@ -118,4 +121,5 @@ go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] +d = ['dep:wit-bindgen-d'] async = [] diff --git a/crates/d/Cargo.toml b/crates/d/Cargo.toml new file mode 100644 index 000000000..e90a308c9 --- /dev/null +++ b/crates/d/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "wit-bindgen-d" +authors = ["Demetrius Kanios "] +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +homepage = 'https://github.com/bytecodealliance/wit-bindgen' +description = """ +D bindings generator for WIT and the component model, typically used through the +`wit-bindgen-cli` crate. +""" + +[lints] +workspace = true + +[lib] +doctest = false +test = false + +[dependencies] +wit-bindgen-core = { workspace = true } +wit-component = { workspace = true } +wasm-encoder = { workspace = true } +wasm-metadata = { workspace = true } +anyhow = { workspace = true } +heck = { workspace = true } +clap = { workspace = true, optional = true } +indexmap = { workspace = true } + +[features] +clap = ['dep:clap', 'wit-bindgen-core/clap'] diff --git a/crates/d/LICENSE-APACHE b/crates/d/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/crates/d/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/d/LICENSE-Apache-2.0_WITH_LLVM-exception b/crates/d/LICENSE-Apache-2.0_WITH_LLVM-exception new file mode 120000 index 000000000..3a28a354e --- /dev/null +++ b/crates/d/LICENSE-Apache-2.0_WITH_LLVM-exception @@ -0,0 +1 @@ +../../LICENSE-Apache-2.0_WITH_LLVM-exception \ No newline at end of file diff --git a/crates/d/LICENSE-MIT b/crates/d/LICENSE-MIT new file mode 120000 index 000000000..b2cfbdc7b --- /dev/null +++ b/crates/d/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT \ No newline at end of file diff --git a/crates/d/README.md b/crates/d/README.md new file mode 100644 index 000000000..ebb79098b --- /dev/null +++ b/crates/d/README.md @@ -0,0 +1,17 @@ +# `wit-bindgen` D Bindings Generator + +This tool generates [D](https://dlang.org) bindings for a chosen WIT world. + +## Usage + +To generate bindings with this crate, issue the `d` subcommand to `wit-bindgen`: + +```bash +$ wit-bindgen d [OPTIONS] +``` + +See the output of `wit-bindgen help d` for available options. + +------- + +TODO: Flesh out fuller docs (ownership, more usage, examples, etc.) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs new file mode 100644 index 000000000..162728983 --- /dev/null +++ b/crates/d/src/lib.rs @@ -0,0 +1,327 @@ +use anyhow::Result; +use heck::*; +use std::collections::{BTreeSet, HashMap}; +use std::path::PathBuf; +use wit_bindgen_core::{Files, Source, WorldGenerator, wit_parser::*}; + +#[derive(Default)] +struct D { + world_src: Source, + opts: Opts, + + cur_world_fqn: String, + interfaces: HashMap, +} + +#[derive(Default)] +struct InterfaceSource { + fqn: String, + src: Source, + imported: bool, + exported: bool, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "clap", derive(clap::Parser))] +pub struct Opts { + /// Where to place output files + #[cfg_attr(feature = "clap", arg(skip))] + out_dir: Option, +} + +impl Opts { + pub fn build(mut self, out_dir: Option<&PathBuf>) -> Box { + let mut r = D::default(); + self.out_dir = out_dir.cloned(); + r.opts = self.clone(); + Box::new(r) + } +} + +fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { + let mut ns = String::new(); + + let pkg = &resolve.packages[id]; + ns.push_str("wit."); + ns.push_str(&pkg.name.namespace.to_snake_case()); + ns.push_str("."); + ns.push_str(&pkg.name.name.to_snake_case()); + ns.push_str("."); + let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { + p.name.namespace == pkg.name.namespace + && p.name.name == pkg.name.name + && p.name.version != pkg.name.version + }); + if pkg_has_multiple_versions { + if let Some(version) = &pkg.name.version { + let version = version + .to_string() + .replace('.', "_") + .replace('-', "_") + .replace('+', "_"); + ns.push_str(&version); + ns.push_str("."); + } + } + ns +} + +fn get_interface_fqn( + interface_id: &WorldKey, + cur_world_fqn: &String, + resolve: &Resolve, + is_export: bool, +) -> String { + let mut ns = String::new(); + match interface_id { + WorldKey::Name(name) => { + ns.push_str(cur_world_fqn); + if is_export { + ns.push_str(".exports") + } else { + ns.push_str(".imports") + } + ns.push_str("."); + ns.push_str(&name.to_snake_case()) + } + WorldKey::Interface(id) => { + let iface = &resolve.interfaces[*id]; + ns.push_str(&get_package_fqn(iface.package.unwrap(), resolve)); + ns.push_str(&iface.name.as_ref().unwrap().to_snake_case()) + } + } + ns +} + +fn get_world_fqn(id: WorldId, resolve: &Resolve) -> String { + let mut ns = String::new(); + + let world = &resolve.worlds[id]; + ns.push_str(&get_package_fqn(world.package.unwrap(), resolve)); + ns.push_str(&world.name.to_snake_case()); + ns +} + +impl D { + fn prepare_interface_bindings( + &self, + id: InterfaceId, + fqn: &String, + cur_world_fqn: &String, + resolve: &Resolve, + ) -> Source { + let mut src = Source::default(); + let interface = &resolve.interfaces[id]; + + match &interface.docs.contents { + Some(docs) => src.push_str(&format!("/++\n{docs}\n+/\n")), + None => {} + } + + src.push_str(&format!("module {};\n\n", fqn)); + + let mut deps = BTreeSet::new(); + + for dep_id in resolve.interface_direct_deps(id) { + deps.insert(dep_id); + } + + for dep_id in deps { + let wrapped_dep_id = WorldKey::Interface(dep_id); + src.push_str(&format!( + "import {};\n", + get_interface_fqn(&wrapped_dep_id, cur_world_fqn, resolve, false) + )); + } + + src.push_str("\n// Type defines\n"); + + for (name, id) in &interface.types { + src.push_str(&format!("// Define type: {name}\n")); + } + + src + } +} + +impl WorldGenerator for D { + fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { + self.cur_world_fqn = get_world_fqn(world, resolve); + + let world = &resolve.worlds[world]; + match &world.docs.contents { + Some(docs) => self.world_src.push_str(&format!("/++\n{docs}\n+/\n")), + None => {} + } + self.world_src + .push_str(&format!("module {};\n\n", self.cur_world_fqn)); + + self.world_src.push_str("// Interface imports\n"); + } + + fn import_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + _files: &mut Files, + ) -> Result<()> { + let interface_src = match self.interfaces.get_mut(&id) { + Some(src) => src, + None => { + let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, false); + let new_src = + self.prepare_interface_bindings(id, &new_fqn, &self.cur_world_fqn, resolve); + + let mut result = InterfaceSource::default(); + result.fqn = new_fqn; + result.src = new_src; + + self.interfaces.insert(id, result); + self.interfaces.get_mut(&id).unwrap() + } + }; + + if interface_src.imported { + return Ok(()); + } + interface_src.imported = true; + + self.world_src + .push_str(&format!("public import {}\n", &self.interfaces[&id].fqn)); + + Ok(()) + } + + fn import_types( + &mut self, + resolve: &Resolve, + world: WorldId, + types: &[(&str, TypeId)], + _files: &mut Files, + ) { + self.world_src.push_str(&format!("\n// Type imports\n")); + for (name, id) in types { + self.world_src + .push_str(&format!("// Define type: {name}\n")); + } + } + + fn import_funcs( + &mut self, + resolve: &Resolve, + world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) { + self.world_src.push_str(&format!("\n// Function imports\n")); + for (name, func) in funcs { + self.world_src + .push_str(&format!("// Import function: {name}\n")); + } + } + + fn pre_export_interface(&mut self, resolve: &Resolve, files: &mut Files) -> Result<()> { + self.world_src.push_str("\n// Interface exports\n"); + self.world_src + .push_str("mixin template Exports(alias Impl) {\n"); + self.world_src.indent(1); + + Ok(()) + } + + fn export_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + _files: &mut Files, + ) -> Result<()> { + let interface = &resolve.interfaces[id]; + let interface_src = match self.interfaces.get_mut(&id) { + Some(src) => src, + None => { + let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, true); + let new_src = + self.prepare_interface_bindings(id, &new_fqn, &self.cur_world_fqn, resolve); + + let mut result = InterfaceSource::default(); + result.fqn = new_fqn; + result.src = new_src; + + self.interfaces.insert(id, result); + + self.interfaces.get_mut(&id).unwrap() + } + }; + + if interface_src.exported { + return Ok(()); + } + interface_src.exported = true; + + self.world_src.push_str(&format!( + "mixin imported!\"{}\".Exports!Impl;\n", + interface_src.fqn + )); + + interface_src + .src + .push_str("\nmixin template Exports(alias Impl) {\n"); + interface_src.src.indent(1); + + interface_src + .src + .push_str(&format!("// Function exports\n")); + for (name, func) in &interface.functions { + interface_src + .src + .push_str(&format!("// Export function: {name}\n")); + } + + interface_src.src.deindent(1); + interface_src.src.push_str("}\n"); + + Ok(()) + } + + fn export_funcs( + &mut self, + resolve: &Resolve, + world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) -> Result<()> { + self.world_src.push_str(&format!("\n// Function exports\n")); + for (name, func) in funcs { + self.world_src + .push_str(&format!("// Export function: {name}\n")); + } + Ok(()) + } + + fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { + // Close out interface exports + self.world_src.deindent(1); + self.world_src.push_str("}\n"); + + let mut world_filepath = PathBuf::from_iter(get_world_fqn(id, resolve).split(".")); + world_filepath.push("package.d"); + + files.push( + world_filepath.to_str().unwrap(), + self.world_src.as_str().as_bytes(), + ); + + for (_, interface_src) in &self.interfaces { + let mut interface_filepath = PathBuf::from_iter(interface_src.fqn.split(".")); + interface_filepath.add_extension("d"); + + files.push( + interface_filepath.to_str().unwrap(), + interface_src.src.as_bytes(), + ); + } + Ok(()) + } +} diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 04e38f7f0..6f0ff8fed 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -74,6 +74,15 @@ enum Opt { args: Common, }, + /// Generates bindings for D guest modules. + #[cfg(feature = "d")] + D { + #[clap(flatten)] + opts: wit_bindgen_d::Opts, + #[clap(flatten)] + args: Common, + }, + // doc-comments are present on `wit_bindgen_test::Opts` for clap to use. Test { #[clap(flatten)] @@ -150,6 +159,8 @@ fn main() -> Result<()> { Opt::Go { opts, args } => (opts.build(), args), #[cfg(feature = "csharp")] Opt::Csharp { opts, args } => (opts.build(), args), + #[cfg(feature = "d")] + Opt::D { opts, args } => (opts.build(args.out_dir.as_ref()), args), Opt::Test { opts } => return opts.run(std::env::args_os().nth(0).unwrap().as_ref()), }; From 8e1666e8367d1aa9abcdca8ca53384a77f0a496c Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 9 Mar 2026 23:12:29 -0700 Subject: [PATCH 2/4] Initial support for most types. --- crates/d/src/lib.rs | 609 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 573 insertions(+), 36 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 162728983..7187c6ccb 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1,5 +1,6 @@ use anyhow::Result; use heck::*; +use std::borrow::Cow; use std::collections::{BTreeSet, HashMap}; use std::path::PathBuf; use wit_bindgen_core::{Files, Source, WorldGenerator, wit_parser::*}; @@ -11,6 +12,8 @@ struct D { cur_world_fqn: String, interfaces: HashMap, + + cur_interface: Option, } #[derive(Default)] @@ -38,14 +41,141 @@ impl Opts { } } +fn escape_d_identifier(name: &str) -> &str { + match name { + // Escape D keywords. + // Source: https://dlang.org/spec/lex.html#keywords + "abstract" => "abstract_", + "alias" => "alias_", + "align" => "align_", + "asm" => "asm_", + "assert" => "assert_", + "auto" => "auto_", + + "body" => "body_", + "bool" => "bool_", + "break" => "break_", + "byte" => "byte_", + + "case" => "case_", + "cast" => "cast_", + "catch" => "catch_", + "cdouble" => "cdouble_", + "cent" => "cent_", + "cfloat" => "cfloat_", + "char" => "char_", + "class" => "class_", + "const" => "const_", + "continue" => "continue_", + "creal" => "creal_", + + "dchar" => "dchar_", + "debug" => "debug_", + "default" => "default_", + "delegate" => "delegate_", + "delete" => "delete_", + "deprecated" => "deprecated_", + "do" => "do_", + "double" => "double_", + + "else" => "else_", + "enum" => "enum_", + "export" => "export_", + "extern" => "extern_", + + "false" => "false_", + "final" => "final_", + "finally" => "finally_", + "float" => "float_", + "for" => "for_", + "foreach" => "foreach_", + "foreach_reverse" => "foreach_reverse_", + "function" => "function_", + + "goto" => "goto_", + + "idouble" => "idouble_", + "if" => "if_", + "ifloat" => "ifloat_", + "immutable" => "immutable_", + "import" => "import_", + "in" => "in_", + "inout" => "inout_", + "int" => "int_", + "interface" => "interface_", + "invariant" => "invariant_", + "ireal" => "ireal_", + "is" => "is_", + + "lazy" => "lazy_", + "long" => "long_", + + "macro" => "macro_", + "mixin" => "mixin_", + "module" => "module_", + + "new" => "new_", + "nothrow" => "nothrow_", + "null" => "null_", + + "out" => "out_", + "override" => "override_", + + "package" => "package_", + "pragma" => "pragma_", + "private" => "private_", + "protected" => "protected_", + "public" => "public_", + "pure" => "pure_", + + "real" => "real_", + "ref" => "ref_", + "return" => "return_", + + "scope" => "scope_", + "shared" => "shared_", + "short" => "short_", + "static" => "static_", + "struct" => "struct_", + "super" => "super_", + "switch" => "switch_", + "synchronized" => "synchronized_", + + "template" => "template_", + "this" => "this_", + "throw" => "throw_", + "true" => "true_", + "try" => "try_", + "typeid" => "typeid_", + "typeof" => "typeof_", + + "ubyte" => "ubyte_", + "ucent" => "ucent_", + "uint" => "uint_", + "ulong" => "ulong_", + "union" => "union_", + "unittest" => "unittest_", + "ushort" => "ushort_", + + "version" => "version_", + "void" => "void_", + + "wchar" => "wchar_", + "while" => "while_", + "with" => "with_", + + s => s, + } +} + fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { let mut ns = String::new(); let pkg = &resolve.packages[id]; ns.push_str("wit."); - ns.push_str(&pkg.name.namespace.to_snake_case()); + ns.push_str(escape_d_identifier(&pkg.name.namespace.to_snake_case())); ns.push_str("."); - ns.push_str(&pkg.name.name.to_snake_case()); + ns.push_str(escape_d_identifier(&pkg.name.name.to_snake_case())); ns.push_str("."); let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { p.name.namespace == pkg.name.namespace @@ -82,12 +212,14 @@ fn get_interface_fqn( ns.push_str(".imports") } ns.push_str("."); - ns.push_str(&name.to_snake_case()) + ns.push_str(escape_d_identifier(&name.to_snake_case())) } WorldKey::Interface(id) => { let iface = &resolve.interfaces[*id]; ns.push_str(&get_package_fqn(iface.package.unwrap(), resolve)); - ns.push_str(&iface.name.as_ref().unwrap().to_snake_case()) + ns.push_str(escape_d_identifier( + &iface.name.as_ref().unwrap().to_snake_case(), + )) } } ns @@ -98,11 +230,42 @@ fn get_world_fqn(id: WorldId, resolve: &Resolve) -> String { let world = &resolve.worlds[id]; ns.push_str(&get_package_fqn(world.package.unwrap(), resolve)); - ns.push_str(&world.name.to_snake_case()); + ns.push_str(escape_d_identifier(&world.name.to_snake_case())); ns } impl D { + fn get_type_fqn(&self, name: &str, owner: &TypeOwner) -> String { + match owner { + TypeOwner::None => String::from(name), + TypeOwner::Interface(id) => { + format!( + "{}.{}", + self.interfaces[id].fqn, + escape_d_identifier(&name.to_upper_camel_case()) + ) + } + TypeOwner::World(_) => format!( + "{}.{}", + self.cur_world_fqn, + escape_d_identifier(&name.to_upper_camel_case()) + ), + } + } + + fn get_type_name(&self, name: &str, owner: &TypeOwner) -> String { + match &owner { + TypeOwner::Interface(id) => Some(id), + _ => None, + } + .zip(self.cur_interface.as_ref()) + .filter(|(id, cur_id)| id == cur_id) + .map_or_else( + || self.get_type_fqn(name, owner), + |_| escape_d_identifier(&name.to_upper_camel_case()).into(), + ) + } + fn prepare_interface_bindings( &self, id: InterfaceId, @@ -113,13 +276,15 @@ impl D { let mut src = Source::default(); let interface = &resolve.interfaces[id]; - match &interface.docs.contents { - Some(docs) => src.push_str(&format!("/++\n{docs}\n+/\n")), - None => {} - } + src.push_str(&format!( + "/++\n{}\n+/\n", + interface.docs.contents.as_deref().unwrap_or_default() + )); src.push_str(&format!("module {};\n\n", fqn)); + src.push_str("import wit.common;\n\n"); + let mut deps = BTreeSet::new(); for dep_id in resolve.interface_direct_deps(id) { @@ -129,7 +294,7 @@ impl D { for dep_id in deps { let wrapped_dep_id = WorldKey::Interface(dep_id); src.push_str(&format!( - "import {};\n", + "static import {};\n", get_interface_fqn(&wrapped_dep_id, cur_world_fqn, resolve, false) )); } @@ -137,25 +302,384 @@ impl D { src.push_str("\n// Type defines\n"); for (name, id) in &interface.types { - src.push_str(&format!("// Define type: {name}\n")); + let type_src = self.generate_type_declaration(name, *id, resolve); + src.append_src(&type_src); } src } + + fn generate_type_use(&self, r#type: &Type, resolve: &Resolve) -> Cow<'static, str> { + match r#type { + Type::Bool => Cow::Borrowed("bool"), + Type::U8 => Cow::Borrowed("ubyte"), + Type::U16 => Cow::Borrowed("ushort"), + Type::U32 => Cow::Borrowed("uint"), + Type::U64 => Cow::Borrowed("ulong"), + Type::S8 => Cow::Borrowed("byte"), + Type::S16 => Cow::Borrowed("short"), + Type::S32 => Cow::Borrowed("int"), + Type::S64 => Cow::Borrowed("long"), + Type::F32 => Cow::Borrowed("float"), + Type::F64 => Cow::Borrowed("double"), + Type::Char => Cow::Borrowed("dchar"), + Type::String => Cow::Borrowed("String"), + Type::ErrorContext => { + todo!("use of `error_context`!"); + } + Type::Id(id) => { + let typedef = &resolve.types[*id]; + match &typedef.owner { + TypeOwner::None => match &typedef.kind { + TypeDefKind::Handle(handle) => todo!("use of `TypeDefKind::Handle`"), + TypeDefKind::Tuple(tuple) => Cow::Owned(format!( + "Tuple!({})", + tuple + .types + .iter() + .map(|ty| self.generate_type_use(ty, resolve).into_owned()) + .collect::>() + .join(", ") + )), + TypeDefKind::Option(opt_type) => Cow::Owned(format!( + "Option!({})", + self.generate_type_use(opt_type, resolve) + )), + TypeDefKind::Result(result) => Cow::Owned(format!( + "Result!({}, {})", + match result.ok { + Some(ok_type) => self.generate_type_use(&ok_type, resolve), + None => Cow::Borrowed("void"), + }, + match result.err { + Some(err_type) => self.generate_type_use(&err_type, resolve), + None => Cow::Borrowed("void"), + } + )), + TypeDefKind::List(list_type) => Cow::Owned(format!( + "List!({})", + self.generate_type_use(list_type, resolve) + )), + TypeDefKind::Map(_, _) => todo!("use of `TypeDefKind::Map`"), + TypeDefKind::FixedLengthList(list_type, length) => Cow::Owned(format!( + "{}[{length}]", + self.generate_type_use(list_type, resolve) + )), + TypeDefKind::Future(future_type) => todo!("use of `TypeDefKind::Future`"), + TypeDefKind::Stream(stream_type) => todo!("use of `TypeDefKind::Stream`"), + TypeDefKind::Type(target_type) => { + self.generate_type_use(target_type, resolve) + } + TypeDefKind::Unknown => { + panic!("Trying to emit type use for `TypeDefKind::Unknown`?"); + } + unhandled => { + panic!( + "Encountered unexpected use of ownerless typedef: {unhandled:?}." + ); + } + }, + _ => Cow::Owned( + self.get_type_name(typedef.name.as_ref().unwrap(), &typedef.owner), + ), + } + } + } + } + + fn generate_type_declaration(&self, name: &str, id: TypeId, resolve: &Resolve) -> Source { + let mut src = Source::default(); + + let typedef = &resolve.types[id]; + + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + src.push_str(&format!( + "\n/++\n{}\n+/\n", + typedef.docs.contents.as_deref().unwrap_or_default() + )); + match &typedef.kind { + TypeDefKind::Record(record) => { + src.push_str(&format!("struct {escaped_name} {{\n")); + + let mut is_first = true; + for field in &record.fields { + if is_first { + is_first = false; + } else { + src.push_str("\n"); + } + + src.push_str(&format!( + "/++\n{}\n+/\n", + field.docs.contents.as_deref().unwrap_or_default() + )); + src.push_str(&format!( + "{} {};\n", + self.generate_type_use(&field.ty, resolve), + field.name.to_lower_camel_case() + )); + } + + src.push_str("}\n"); + } + TypeDefKind::Resource => { + //src.push_str(&format!("// TODO: def of resource - {name}")) + todo!("def of `TypeDefKind::Resource`"); + } + TypeDefKind::Handle(handle) => { + todo!("def of `TypeDefKind::Handle`"); + } + TypeDefKind::Flags(flags) => { + let storage_type = match flags.repr() { + FlagsRepr::U8 => "ubyte", + FlagsRepr::U16 => "ushort", + FlagsRepr::U32(1) => "uint", + FlagsRepr::U32(2) => "ulong", + repr => todo!("flags {repr:?}"), + }; + + src.push_str(&format!("enum {escaped_name}_ : {storage_type} {{\n")); + for (index, flag) in flags.flags.iter().enumerate() { + if index != 0 { + src.push_str("\n"); + } + src.push_str(&format!( + "/++\n{}\n+/\n", + flag.docs.contents.as_deref().unwrap_or_default() + )); + src.push_str(&format!( + "{} = 1 << {index},\n", + escape_d_identifier(&flag.name.to_lower_camel_case()) + )); + } + src.push_str(&format!( + "}}\n/// ditto\nalias {escaped_name} = Flags!{escaped_name}_;" + )); + } + TypeDefKind::Tuple(tuple) => src.push_str(&format!( + "alias {escaped_name} = Tuple!({});", + tuple + .types + .iter() + .map(|ty| self.generate_type_use(ty, resolve).into_owned()) + .collect::>() + .join(", ") + )), + TypeDefKind::Variant(variant) => { + let storage_type = match variant.tag() { + Int::U8 => "ubyte", + Int::U16 => "ushort", + Int::U32 => "uint", + Int::U64 => "ulong", + }; + + src.push_str(&format!("struct {escaped_name} {{\n")); + src.deindent(1); + src.push_str(&format!("@safe @nogc nothrow:\n")); + src.indent(1); + + src.push_str(&format!("enum Tag : {storage_type} {{\n")); + + let mut is_first = true; + for case in &variant.cases { + if is_first { + is_first = false; + } else { + src.push_str("\n"); + } + src.push_str(&format!( + "/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + src.push_str(&format!( + "{},\n", + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + + src.push_str("}\n"); + + src.deindent(1); + src.push_str(&format!("\nprivate:\n")); + src.indent(1); + + if variant.cases.iter().any(|case| case.ty.is_some()) { + src.push_str(&format!("union Storage {{\n")); + src.push_str("ubyte __zeroinit = 0;\n"); + for case in &variant.cases { + if let Some(ty) = &case.ty { + src.push_str(&format!( + "{} {};\n", + self.generate_type_use(ty, resolve), + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + } + + src.push_str("}\n\n"); + + src.push_str("Tag _tag;\n"); + src.push_str("Storage _storage;\n\n"); + } else { + src.push_str("Tag _tag;\n\n"); + } + + src.push_str("@disable this();\n"); + src.push_str("this(Tag tag, Storage storage = Storage.init) {\n"); + src.push_str("_tag = tag;\n"); + src.push_str("_storage = storage;\n"); + src.push_str("}\n"); + + src.deindent(1); + src.push_str(&format!("\npublic:\n")); + src.indent(1); + + src.push_str("Tag tag() => _tag;\n"); + + for case in &variant.cases { + src.push_str(&format!( + "\n/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + let upper_case_name = case.name.to_upper_camel_case(); + let escaped_upper_case_name = escape_d_identifier(&upper_case_name); + + let lower_case_name = case.name.to_lower_camel_case(); + let escaped_lower_case_name = escape_d_identifier(&lower_case_name); + + if let Some(ty) = &case.ty { + src.push_str(&format!( + "static {escaped_name} {escaped_lower_case_name}({} val) {{\n", + self.generate_type_use(ty, resolve) + )); + src.push_str("Storage storage;\n"); + src.push_str(&format!("storage.{escaped_lower_case_name} = val;\n")); + src.push_str(&format!( + "return {escaped_name}(Tag.{escaped_lower_case_name}, storage);\n" + )); + src.push_str("}\n"); + + src.push_str(&format!( + "/// ditto\n ref inout({}) get{escaped_upper_case_name}() inout return\n", + self.generate_type_use(ty, resolve) + )); + + src.push_str(&format!("in (is{escaped_upper_case_name}) ")); + src.push_str(&format!( + "do {{ return _storage.{escaped_lower_case_name}; }}\n" + )); + } else { + src.push_str(&format!( + "static {escaped_name} {escaped_lower_case_name}() => {escaped_name}(Tag.{escaped_lower_case_name});\n", + )); + } + src.push_str(&format!( + "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", + )); + } + + src.push_str("}\n"); + } + TypeDefKind::Enum(r#enum) => { + let storage_type = match r#enum.tag() { + Int::U8 => "ubyte", + Int::U16 => "ushort", + Int::U32 => "uint", + Int::U64 => "ulong", + }; + + src.push_str(&format!("enum {escaped_name} : {storage_type} {{\n")); + + let mut is_first = true; + for case in &r#enum.cases { + if is_first { + is_first = false; + } else { + src.push_str("\n"); + } + src.push_str(&format!( + "/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + src.push_str(&format!( + "{},\n", + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + + src.push_str(&format!("}}")); + } + TypeDefKind::Option(opt_type) => src.push_str(&format!( + "alias {escaped_name} = Option!({});", + self.generate_type_use(opt_type, resolve) + )), + TypeDefKind::Result(result) => src.push_str(&format!( + "alias {escaped_name} = Result!({}, {});", + match result.ok { + Some(ok_type) => self.generate_type_use(&ok_type, resolve), + None => Cow::Borrowed("void"), + }, + match result.err { + Some(err_type) => self.generate_type_use(&err_type, resolve), + None => Cow::Borrowed("void"), + } + )), + TypeDefKind::List(list_type) => src.push_str(&format!( + "alias {escaped_name} = List!({});", + self.generate_type_use(list_type, resolve) + )), + TypeDefKind::Map(_, _) => { + todo!("def of `TypeDefKind::Map`"); + } + TypeDefKind::FixedLengthList(list_type, length) => { + src.push_str(&format!( + "alias {escaped_name} = {}[{length}];", + self.generate_type_use(&list_type, resolve), + )); + } + TypeDefKind::Future(future_type) => { + todo!("def of `TypeDefKind::Future`"); + } + TypeDefKind::Stream(stream_type) => { + todo!("def of `TypeDefKind::Stream`"); + } + TypeDefKind::Type(target_type) => { + src.push_str(&format!( + "alias {escaped_name} = {};", + self.generate_type_use(&target_type, resolve), + )); + } + TypeDefKind::Unknown => { + panic!("Trying to emit type declaration for `TypeDefKind::Unknown`?"); + } + } + src.push_str("\n"); + src + } } impl WorldGenerator for D { + fn uses_nominal_type_ids(&self) -> bool { + false + } + fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { self.cur_world_fqn = get_world_fqn(world, resolve); let world = &resolve.worlds[world]; - match &world.docs.contents { - Some(docs) => self.world_src.push_str(&format!("/++\n{docs}\n+/\n")), - None => {} - } + + self.world_src.push_str(&format!( + "/++\n{}\n+/\n", + world.docs.contents.as_deref().unwrap_or_default() + )); + self.world_src .push_str(&format!("module {};\n\n", self.cur_world_fqn)); + self.world_src.push_str("import wit.common;\n\n"); + self.world_src.push_str("// Interface imports\n"); } @@ -166,19 +690,27 @@ impl WorldGenerator for D { id: InterfaceId, _files: &mut Files, ) -> Result<()> { + self.cur_interface = Some(id); let interface_src = match self.interfaces.get_mut(&id) { Some(src) => src, None => { + eprintln!("Import {id:?}"); let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, false); - let new_src = - self.prepare_interface_bindings(id, &new_fqn, &self.cur_world_fqn, resolve); - let mut result = InterfaceSource::default(); - result.fqn = new_fqn; - result.src = new_src; + let mut result_init = InterfaceSource::default(); + result_init.fqn = new_fqn; + self.interfaces.insert(id, result_init); + + let new_src = self.prepare_interface_bindings( + id, + &self.interfaces.get(&id).unwrap().fqn, + &self.cur_world_fqn, + resolve, + ); - self.interfaces.insert(id, result); - self.interfaces.get_mut(&id).unwrap() + let result = self.interfaces.get_mut(&id).unwrap(); + result.src = new_src; + result } }; @@ -188,8 +720,9 @@ impl WorldGenerator for D { interface_src.imported = true; self.world_src - .push_str(&format!("public import {}\n", &self.interfaces[&id].fqn)); + .push_str(&format!("public import {};\n", &self.interfaces[&id].fqn)); + self.cur_interface = None; Ok(()) } @@ -202,8 +735,8 @@ impl WorldGenerator for D { ) { self.world_src.push_str(&format!("\n// Type imports\n")); for (name, id) in types { - self.world_src - .push_str(&format!("// Define type: {name}\n")); + let type_src = self.generate_type_declaration(name, *id, resolve); + self.world_src.append_src(&type_src); } } @@ -225,7 +758,6 @@ impl WorldGenerator for D { self.world_src.push_str("\n// Interface exports\n"); self.world_src .push_str("mixin template Exports(alias Impl) {\n"); - self.world_src.indent(1); Ok(()) } @@ -237,21 +769,28 @@ impl WorldGenerator for D { id: InterfaceId, _files: &mut Files, ) -> Result<()> { + self.cur_interface = Some(id); let interface = &resolve.interfaces[id]; let interface_src = match self.interfaces.get_mut(&id) { Some(src) => src, None => { + eprintln!("Export {id:?}"); let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, true); - let new_src = - self.prepare_interface_bindings(id, &new_fqn, &self.cur_world_fqn, resolve); - let mut result = InterfaceSource::default(); - result.fqn = new_fqn; - result.src = new_src; + let mut result_init = InterfaceSource::default(); + result_init.fqn = new_fqn; + self.interfaces.insert(id, result_init); - self.interfaces.insert(id, result); + let new_src = self.prepare_interface_bindings( + id, + &self.interfaces.get(&id).unwrap().fqn, + &self.cur_world_fqn, + resolve, + ); - self.interfaces.get_mut(&id).unwrap() + let result = self.interfaces.get_mut(&id).unwrap(); + result.src = new_src; + result } }; @@ -268,7 +807,6 @@ impl WorldGenerator for D { interface_src .src .push_str("\nmixin template Exports(alias Impl) {\n"); - interface_src.src.indent(1); interface_src .src @@ -279,9 +817,9 @@ impl WorldGenerator for D { .push_str(&format!("// Export function: {name}\n")); } - interface_src.src.deindent(1); interface_src.src.push_str("}\n"); + self.cur_interface = None; Ok(()) } @@ -302,7 +840,6 @@ impl WorldGenerator for D { fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { // Close out interface exports - self.world_src.deindent(1); self.world_src.push_str("}\n"); let mut world_filepath = PathBuf::from_iter(get_world_fqn(id, resolve).split(".")); From bc9d5b11551178d3064dceb0aad43fb0715d0fd0 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 10 Mar 2026 16:43:36 -0700 Subject: [PATCH 3/4] Basic implementation of `wit.common` type templates. [skip ci] --- crates/d/src/lib.rs | 2 + crates/d/src/wit_common.d | 275 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 crates/d/src/wit_common.d diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 7187c6ccb..4d6e8cead 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -850,6 +850,8 @@ impl WorldGenerator for D { self.world_src.as_str().as_bytes(), ); + files.push("wit/common.d", include_bytes!("wit_common.d")); + for (_, interface_src) in &self.interfaces { let mut interface_filepath = PathBuf::from_iter(interface_src.fqn.split(".")); interface_filepath.add_extension("d"); diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d new file mode 100644 index 000000000..f5d3f67f5 --- /dev/null +++ b/crates/d/src/wit_common.d @@ -0,0 +1,275 @@ +module wit.common; + +/// Thin CABI compliant wrapper over `T[]` +struct List(T) { +@safe @nogc pure nothrow: + T* ptr; + size_t length; + + this(T[] slice) @trusted { + this = slice; + } + + void opAssign(T[] slice) @trusted { + ptr = slice.ptr; + length = slice.length; + } + + alias asSlice this; + inout(T)[] asSlice() @trusted inout { + return (ptr && length) ? ptr[0..length] : null; + } +} + +// WIT ABI for string matches List, +// except list in WIT is actually List!(dchar) +// +// We assume UTF-8 data (as D native strings are UTF-8) +alias String = List!(immutable char); + +// TODO: split this file up and give Tuple a full port of the Phobos version? +/// adapted from Phobos std.typecons.Tuple +/// No support for naming members. +struct Tuple(Types...) if (is(Types)) { + Types expand; + alias expand this; +} + +/// adapted from Phobos std.bitmanip.BitFlags +struct Flags(Enum) if (is(Enum == enum)) { +@safe @nogc pure nothrow: + public alias E = Enum; + +private: + template allAreBaseEnum(T...) + { + static foreach (Ti; T) + { + static if (!is(typeof(allAreBaseEnum) == bool) && // not yet defined + !is(Ti : E)) + { + enum allAreBaseEnum = false; + } + } + static if (!is(typeof(allAreBaseEnum) == bool)) // if not yet defined + { + enum allAreBaseEnum = true; + } + } + + static if (is(E U == enum)) { + alias Base = U; + } else static assert(0); + + Base mValue; + +public: + this(E flag) + { + this = flag; + } + + this(T...)(T flags) + if (allAreBaseEnum!(T)) + { + this = flags; + } + + bool opCast(B: bool)() const + { + return mValue != 0; + } + + Base opCast(B)() const + if (is(Base : B)) + { + return mValue; + } + + auto opUnary(string op)() const + if (op == "~") + { + return WitFlags(cast(E) cast(Base) ~mValue); + } + + auto ref opAssign(T...)(T flags) + if (allAreBaseEnum!(T)) + { + mValue = 0; + foreach (E flag; flags) + { + mValue |= flag; + } + return this; + } + + auto ref opAssign(E flag) + { + mValue = flag; + return this; + } + + auto ref opOpAssign(string op: "|")(WitFlags flags) + { + mValue |= flags.mValue; + return this; + } + + auto ref opOpAssign(string op: "&")(WitFlags flags) + { + mValue &= flags.mValue; + return this; + } + + auto ref opOpAssign(string op: "|")(E flag) + { + mValue |= flag; + return this; + } + + auto ref opOpAssign(string op: "&")(E flag) + { + mValue &= flag; + return this; + } + + auto opBinary(string op)(WitFlags flags) const + if (op == "|" || op == "&") + { + WitFlags result = this; + result.opOpAssign!op(flags); + return result; + } + + auto opBinary(string op)(E flag) const + if (op == "|" || op == "&") + { + WitFlags result = this; + result.opOpAssign!op(flag); + return result; + } + + auto opBinaryRight(string op)(E flag) const + if (op == "|" || op == "&") + { + return opBinary!op(flag); + } + + bool opDispatch(string name)() const + if (__traits(hasMember, E, name)) + { + enum e = __traits(getMember, E, name); + return (mValue & e) == e; + } + + void opDispatch(string name)(bool set) + if (__traits(hasMember, E, name)) + { + enum e = __traits(getMember, E, name); + if (set) + mValue |= e; + else + mValue &= ~e; + } +} + +/// Based on Rust's Option +struct Option(T) { +private: + bool present = false; + T value; + + @disable this(); + this(bool present, T value = T.init) @safe @nogc nothrow { + this.present = present; + this.value = value; + } +public: + static Option some(T value) @safe @nogc nothrow { + return Option(true, value); + } + + static Option none() @safe @nogc nothrow { + return Option(false); + } + + bool isSome() const @safe @nogc nothrow => present; + alias isSome this; // implicit conversion to bool + + bool isNone() const @safe @nogc nothrow => !present; + + ref inout(T) unwrap() inout @safe @nogc nothrow return + in (present) do { return value; } + + T unwrapOr(T fallback) @safe @nogc nothrow => present ? value : fallback; + + T unwrapOrElse(D)(scope D fallback) + if (is(D R == return) && is(R : T) && is(D == __parameters)) + { return present ? value : fallback(); } +} + +/// Based on Rust's Result +struct Result(T, E) { +private: + bool hasError; + union Storage { + ubyte __zeroinit = 0; + static if (!is(T == void)) { + T value; + } + static if (!is(E == void)) { + E error; + } + } + Storage storage; + + @disable this(); + this(bool hasError, Storage storage) @safe @nogc nothrow { + this.hasError = hasError; + this.storage = storage; + } + +public: + static if (is(T == void)) { + static Result ok() @safe @nogc nothrow => Result(false, Storage()); + } else { + static Result ok(T value) @safe @nogc nothrow { + Storage newStorage; + newStorage.value = value; + + return Result(false, newStorage); + } + } + + static if (is(E == void)) { + static Result err() @safe @nogc nothrow => Result(true, Storage()); + } else { + static Result err(E error) @safe @nogc nothrow { + Storage newStorage; + newStorage.error = error; + + return Result(true, newStorage); + } + } + + bool isOk() const @safe @nogc nothrow => !hasError; + + bool isErr() const @safe @nogc nothrow => hasError; + alias isErr this; // implicit conversion to bool + + static if (!is(T == void)) { + ref inout(T) unwrap() inout @safe @nogc nothrow return + in (isOk) do { return storage.value; } + + T unwrapOr(T fallback) @safe @nogc nothrow => isOk ? storage.value : fallback; + + T unwrapOrElse(D)(scope D fallback) + if (is(D R == return) && is(R : T) && is(D == __parameters)) + { return isOk ? storage.value : fallback(); } + } + + static if (!is(E == void)) { + ref inout(E) unwrapErr() inout @safe @nogc nothrow return + in (isErr) do { return storage.error; } + } +} From fd719690bac280531ca1b93a87f0ee785730a46e Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 12 Mar 2026 16:10:09 -0700 Subject: [PATCH 4/4] Refactor type generation [skip ci] --- crates/d/src/lib.rs | 1440 +++++++++++++++++++++++-------------- crates/d/src/wit_common.d | 140 +--- 2 files changed, 900 insertions(+), 680 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 4d6e8cead..e39a1efaa 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1,27 +1,38 @@ use anyhow::Result; use heck::*; use std::borrow::Cow; -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::mem::take; use std::path::PathBuf; -use wit_bindgen_core::{Files, Source, WorldGenerator, wit_parser::*}; +use wit_bindgen_core::{Direction, Types}; +use wit_bindgen_core::{Files, InterfaceGenerator, Source, WorldGenerator, wit_parser::*}; #[derive(Default)] struct D { - world_src: Source, + used_interfaces: HashSet<(WorldKey, InterfaceId)>, + + interface_imports: Vec, + interface_exports: Vec, + type_imports_src: Source, + function_imports_src: Source, + function_exports_src: Source, + opts: Opts, - cur_world_fqn: String, - interfaces: HashMap, + world_id: Option, + world_fqn: String, + interface_fqns: HashMap, cur_interface: Option, + + types: Types, } -#[derive(Default)] -struct InterfaceSource { - fqn: String, - src: Source, - imported: bool, - exported: bool, +#[derive(Default, Debug)] +struct InterfaceFQNSet { + import: Option, + export: Option, + common: Option, } #[derive(Default, Debug, Clone)] @@ -164,703 +175,1022 @@ fn escape_d_identifier(name: &str) -> &str { "while" => "while_", "with" => "with_", + // Symbols we define as part of the bindings we want to avoid creating conflicts with + "WitList" => "WitList_", + "WitString" => "WitString_", + "WitFlags" => "WitFlags_", + "Option" => "Option_", + "Result" => "Result_", + "bits" => "bits_", // part of WitFlags + s => s, } } fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { - let mut ns = String::new(); - let pkg = &resolve.packages[id]; - ns.push_str("wit."); - ns.push_str(escape_d_identifier(&pkg.name.namespace.to_snake_case())); - ns.push_str("."); - ns.push_str(escape_d_identifier(&pkg.name.name.to_snake_case())); - ns.push_str("."); let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { p.name.namespace == pkg.name.namespace && p.name.name == pkg.name.name && p.name.version != pkg.name.version }); - if pkg_has_multiple_versions { - if let Some(version) = &pkg.name.version { - let version = version - .to_string() - .replace('.', "_") - .replace('-', "_") - .replace('+', "_"); - ns.push_str(&version); - ns.push_str("."); + + format!( + "wit.{}.{}{}", + escape_d_identifier(&pkg.name.namespace.to_snake_case()), + escape_d_identifier(&pkg.name.name.to_snake_case()), + if pkg_has_multiple_versions { + if let Some(version) = &pkg.name.version { + let version = version + .to_string() + .replace('.', "_") + .replace('-', "_") + .replace('+', "_"); + format!(".{version}") + } else { + String::default() + } + } else { + String::default() } - } - ns + ) } fn get_interface_fqn( interface_id: &WorldKey, - cur_world_fqn: &String, + world_fqn: &str, resolve: &Resolve, - is_export: bool, + direction: Option, ) -> String { - let mut ns = String::new(); match interface_id { WorldKey::Name(name) => { - ns.push_str(cur_world_fqn); - if is_export { - ns.push_str(".exports") - } else { - ns.push_str(".imports") - } - ns.push_str("."); - ns.push_str(escape_d_identifier(&name.to_snake_case())) + format!( + "{}.{}.{}", + world_fqn, + match direction { + None => panic!( + "Inline interfaces can only generate `import` or `export` module variant" + ), + Some(Direction::Import) => "imports", + Some(Direction::Export) => "exports", + }, + escape_d_identifier(&name.to_snake_case()) + ) } WorldKey::Interface(id) => { let iface = &resolve.interfaces[*id]; - ns.push_str(&get_package_fqn(iface.package.unwrap(), resolve)); - ns.push_str(escape_d_identifier( - &iface.name.as_ref().unwrap().to_snake_case(), - )) + + format!( + "{}.{}.{}", + get_package_fqn(iface.package.unwrap(), resolve), + escape_d_identifier(&iface.name.as_ref().unwrap().to_snake_case()), + match direction { + None => "common", + Some(Direction::Import) => "imports", + Some(Direction::Export) => "exports", + }, + ) } } - ns } fn get_world_fqn(id: WorldId, resolve: &Resolve) -> String { - let mut ns = String::new(); - let world = &resolve.worlds[id]; - ns.push_str(&get_package_fqn(world.package.unwrap(), resolve)); - ns.push_str(escape_d_identifier(&world.name.to_snake_case())); - ns + format!( + "{}.{}", + get_package_fqn(world.package.unwrap(), resolve), + escape_d_identifier(&world.name.to_snake_case()) + ) } impl D { - fn get_type_fqn(&self, name: &str, owner: &TypeOwner) -> String { - match owner { - TypeOwner::None => String::from(name), - TypeOwner::Interface(id) => { - format!( - "{}.{}", - self.interfaces[id].fqn, - escape_d_identifier(&name.to_upper_camel_case()) - ) - } - TypeOwner::World(_) => format!( - "{}.{}", - self.cur_world_fqn, - escape_d_identifier(&name.to_upper_camel_case()) - ), + fn interface<'a>( + &'a mut self, + resolve: &'a Resolve, + direction: Option, + name: Option<&'a WorldKey>, + wasm_import_module: Option<&'a str>, + ) -> DInterfaceGenerator<'a> { + let mut sizes = SizeAlign::default(); + sizes.fill(resolve); + + DInterfaceGenerator { + src: Source::default(), + fqn: "", + r#gen: self, + resolve, + interface: None, + name: name, + sizes, + direction, + + wasm_import_module, } } - fn get_type_name(&self, name: &str, owner: &TypeOwner) -> String { - match &owner { - TypeOwner::Interface(id) => Some(id), - _ => None, + fn lookup_interface_fqn(&self, id: InterfaceId, direction: Option) -> Option<&str> { + let all_fqns = &self.interface_fqns[&id]; + match direction { + None => all_fqns.common.as_deref(), + Some(Direction::Import) => all_fqns.import.as_deref(), + Some(Direction::Export) => all_fqns.export.as_deref(), + } + } +} + +impl WorldGenerator for D { + fn uses_nominal_type_ids(&self) -> bool { + false + } + + fn preprocess(&mut self, resolve: &Resolve, world_id: WorldId) { + self.world_fqn = get_world_fqn(world_id, resolve); + self.world_id = Some(world_id); + self.types.analyze(resolve); + + let world = &resolve.worlds[world_id]; + + for (name, import) in world.imports.iter() { + match import { + WorldItem::Interface { id, .. } => { + let fqns = self.interface_fqns.entry(*id).or_insert_with(|| { + let mut result = InterfaceFQNSet::default(); + + match name { + WorldKey::Interface(_) => { + result.common = + Some(get_interface_fqn(&name, &self.world_fqn, resolve, None)); + } + WorldKey::Name(_) => { + // For anonymous/inline imports, the common types are in the same file as the imports + result.common = Some(get_interface_fqn( + &name, + &self.world_fqn, + resolve, + Some(Direction::Import), + )); + } + } + + result + }); + (*fqns).import = Some(get_interface_fqn( + &name, + &self.world_fqn, + resolve, + Some(Direction::Import), + )) + } + _ => {} + } + } + + for (name, export) in world.exports.iter() { + match export { + WorldItem::Interface { id, .. } => { + let fqns = self.interface_fqns.entry(*id).or_insert_with(|| { + let mut result = InterfaceFQNSet::default(); + + match name { + WorldKey::Interface(_) => { + result.common = + Some(get_interface_fqn(&name, &self.world_fqn, resolve, None)); + } + WorldKey::Name(_) => { + // For anonymous/inline exports, the common types are in the same file as the exports + result.common = Some(get_interface_fqn( + &name, + &self.world_fqn, + resolve, + Some(Direction::Export), + )); + } + } + + result + }); + (*fqns).export = Some(get_interface_fqn( + &name, + &self.world_fqn, + resolve, + Some(Direction::Export), + )) + } + _ => {} + } } - .zip(self.cur_interface.as_ref()) - .filter(|(id, cur_id)| id == cur_id) - .map_or_else( - || self.get_type_fqn(name, owner), - |_| escape_d_identifier(&name.to_upper_camel_case()).into(), - ) } - fn prepare_interface_bindings( - &self, + fn import_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, id: InterfaceId, - fqn: &String, - cur_world_fqn: &String, + files: &mut Files, + ) -> Result<()> { + self.used_interfaces.insert((name.clone(), id)); + + self.cur_interface = Some(id); + + let fqn = self.interface_fqns[&id].import.as_ref().unwrap().clone(); + + self.interface_imports.push(fqn.clone()); + + let wasm_import_module = resolve.name_world_key(name); + let mut r#gen = self.interface( + resolve, + Some(Direction::Import), + Some(name), + Some(&wasm_import_module), + ); + r#gen.fqn = &fqn; + r#gen.interface = Some(id); + r#gen.prologue(); + + if let WorldKey::Name(_) = name { + // We have an inline interface imported in a world. + // Emit the "common" types as well + + r#gen.direction = None; + r#gen.types(id); + r#gen.direction = Some(Direction::Import); + } + + r#gen.types(id); + + let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + interface_filepath.add_extension("d"); + + files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); + + //self.interface_imports.push(interface_src.fqn.clone()); + //interface_src.src.push_str("\n// Function imports\n"); + //interface_src.src.append_src(&tmp_src); + + self.cur_interface = None; + Ok(()) + } + + fn import_types( + &mut self, resolve: &Resolve, - ) -> Source { - let mut src = Source::default(); - let interface = &resolve.interfaces[id]; + _world: WorldId, + types: &[(&str, TypeId)], + _files: &mut Files, + ) { + let mut r#gen = self.interface(resolve, Some(Direction::Import), None, Some("$root")); + for (name, id) in types.iter() { + r#gen.define_type(name, *id); + } - src.push_str(&format!( - "/++\n{}\n+/\n", - interface.docs.contents.as_deref().unwrap_or_default() - )); + let src = take(&mut r#gen.src); + self.type_imports_src.append_src(&src); + } - src.push_str(&format!("module {};\n\n", fqn)); + fn import_funcs( + &mut self, + _resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) { + let _name = WorldKey::Name("$root".to_string()); + //let wasm_import_module = resolve.name_world_key(&name); - src.push_str("import wit.common;\n\n"); + for (name, _func) in funcs { + self.function_imports_src + .push_str(&format!("// Import function - {name}\n")); + } + } - let mut deps = BTreeSet::new(); + fn export_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + files: &mut Files, + ) -> Result<()> { + self.used_interfaces.insert((name.clone(), id)); - for dep_id in resolve.interface_direct_deps(id) { - deps.insert(dep_id); + self.cur_interface = Some(id); + + let fqn = self.interface_fqns[&id].export.as_ref().unwrap().clone(); + + self.interface_exports.push(fqn.clone()); + + let wasm_import_module = resolve.name_world_key(name); + let mut r#gen = self.interface( + resolve, + Some(Direction::Export), + Some(name), + Some(&wasm_import_module), + ); + r#gen.interface = Some(id); + r#gen.prologue(); + + if let WorldKey::Name(_) = name { + // We have an inline interface exported in a world. + // Emit the "common" types as well + + r#gen.direction = None; + r#gen.types(id); + r#gen.direction = Some(Direction::Export); } - for dep_id in deps { - let wrapped_dep_id = WorldKey::Interface(dep_id); - src.push_str(&format!( - "static import {};\n", - get_interface_fqn(&wrapped_dep_id, cur_world_fqn, resolve, false) - )); + r#gen.types(id); + + let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + interface_filepath.add_extension("d"); + + files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); + + self.cur_interface = None; + Ok(()) + } + + fn export_funcs( + &mut self, + _resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) -> Result<()> { + for (name, _func) in funcs { + self.function_exports_src + .push_str(&format!("// Export function: {name}\n")); } + Ok(()) + } + + fn finish(&mut self, resolve: &Resolve, world_id: WorldId, files: &mut Files) -> Result<()> { + for (name, id) in take(&mut self.used_interfaces) { + if let WorldKey::Interface(_) = name { + let fqn = self.interface_fqns[&id].common.as_ref().unwrap().clone(); - src.push_str("\n// Type defines\n"); + let wasm_import_module = resolve.name_world_key(&name); + let mut r#gen = + self.interface(resolve, None, Some(&name), Some(&wasm_import_module)); + r#gen.interface = Some(id); + r#gen.prologue(); + r#gen.types(id); - for (name, id) in &interface.types { - let type_src = self.generate_type_declaration(name, *id, resolve); - src.append_src(&type_src); + let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + interface_filepath.add_extension("d"); + + files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); + } } - src + let mut world_src = Source::default(); + + let world = &resolve.worlds[world_id]; + + world_src.push_str(&format!( + "/++\n{}\n+/\n", + world.docs.contents.as_deref().unwrap_or_default() + )); + + world_src.push_str(&format!("module {};\n\n", self.world_fqn)); + world_src.push_str("import wit.common;\n\n"); + world_src.push_str("// Interface imports\n"); + world_src.push_str( + &self + .interface_imports + .iter() + .map(|fqn| format!("public import {fqn};")) + .collect::>() + .join("\n"), + ); + + world_src.push_str("\n\n// Type imports\n"); + world_src.append_src(&self.type_imports_src); + + world_src.push_str("\n// Function imports\n"); + world_src.append_src(&self.function_imports_src); + + world_src.push_str("\n// Interface exports\n"); + world_src.push_str( + &self + .interface_exports + .iter() + .map(|fqn| format!("public import {fqn};")) + .collect::>() + .join("\n"), + ); + + world_src.push_str("\n\nprivate alias AliasSeq(T...) = T;\n"); + world_src.push_str("template Exports(Impl...) {\n"); + world_src.push_str("// Interface exports\n"); + + world_src.push_str("alias InterfaceExports = AliasSeq!(\n"); + world_src.indent(1); + world_src.push_str( + &self + .interface_exports + .iter() + .map(|fqn| format!("{fqn}.Exports!Impl")) + .collect::>() + .join(",\n"), + ); + world_src.deindent(1); + world_src.push_str("\n);\n"); + + world_src.push_str("\n// Function exports\n"); + world_src.append_src(&self.function_exports_src); + world_src.push_str("}\n"); + + let mut world_filepath = PathBuf::from_iter(get_world_fqn(world_id, resolve).split(".")); + world_filepath.push("package.d"); + + files.push(world_filepath.to_str().unwrap(), world_src.as_bytes()); + + files.push("wit/common.d", include_bytes!("wit_common.d")); + Ok(()) } +} + +struct DInterfaceGenerator<'a> { + src: Source, + direction: Option, + r#gen: &'a mut D, + resolve: &'a Resolve, + interface: Option, + name: Option<&'a WorldKey>, + wasm_import_module: Option<&'a str>, + fqn: &'a str, + + sizes: SizeAlign, +} - fn generate_type_use(&self, r#type: &Type, resolve: &Resolve) -> Cow<'static, str> { - match r#type { +impl<'a> DInterfaceGenerator<'a> { + fn scoped_type_name(&self, id: TypeId, from_module_fqn: &str) -> String { + let ty = &self.resolve.types[id]; + + let owner_fqn = self.type_owner_fqn(&ty.owner).unwrap(); + + let upper_name = ty.name.as_ref().unwrap().to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + if from_module_fqn == owner_fqn { + escaped_name.into() + } else { + format!("{owner_fqn}.{escaped_name}") + } + } + fn type_name(&self, ty: &Type, from_module_fqn: &str) -> Cow<'static, str> { + match ty { Type::Bool => Cow::Borrowed("bool"), + Type::Char => Cow::Borrowed("dchar"), Type::U8 => Cow::Borrowed("ubyte"), - Type::U16 => Cow::Borrowed("ushort"), - Type::U32 => Cow::Borrowed("uint"), - Type::U64 => Cow::Borrowed("ulong"), Type::S8 => Cow::Borrowed("byte"), + Type::U16 => Cow::Borrowed("ushort"), Type::S16 => Cow::Borrowed("short"), + Type::U32 => Cow::Borrowed("uint"), Type::S32 => Cow::Borrowed("int"), + Type::U64 => Cow::Borrowed("ulong"), Type::S64 => Cow::Borrowed("long"), Type::F32 => Cow::Borrowed("float"), Type::F64 => Cow::Borrowed("double"), - Type::Char => Cow::Borrowed("dchar"), - Type::String => Cow::Borrowed("String"), - Type::ErrorContext => { - todo!("use of `error_context`!"); - } + Type::String => Cow::Borrowed("WitString"), Type::Id(id) => { - let typedef = &resolve.types[*id]; - match &typedef.owner { + let typedef = &self.resolve.types[*id]; + + match typedef.owner { TypeOwner::None => match &typedef.kind { - TypeDefKind::Handle(handle) => todo!("use of `TypeDefKind::Handle`"), - TypeDefKind::Tuple(tuple) => Cow::Owned(format!( + TypeDefKind::Record(_) => { + Cow::Owned(self.scoped_type_name(*id, from_module_fqn)) + } + TypeDefKind::Resource => { + Cow::Owned(self.scoped_type_name(*id, from_module_fqn)) + } + TypeDefKind::Handle(Handle::Own(_id)) => { + Cow::Borrowed("/* todo - type_name of `own` */") + } + TypeDefKind::Handle(Handle::Borrow(_id)) => { + Cow::Borrowed("/* todo - type_name of `borrow` */") + } + TypeDefKind::Tuple(t) => Cow::Owned(format!( "Tuple!({})", - tuple - .types + t.types .iter() - .map(|ty| self.generate_type_use(ty, resolve).into_owned()) + .map(|ty| self.type_name(ty, from_module_fqn).into_owned()) .collect::>() .join(", ") )), - TypeDefKind::Option(opt_type) => Cow::Owned(format!( - "Option!({})", - self.generate_type_use(opt_type, resolve) - )), - TypeDefKind::Result(result) => Cow::Owned(format!( + TypeDefKind::Option(o) => { + Cow::Owned(format!("Option!({})", self.type_name(o, from_module_fqn))) + } + TypeDefKind::Result(r) => Cow::Owned(format!( "Result!({}, {})", - match result.ok { - Some(ok_type) => self.generate_type_use(&ok_type, resolve), + match r.ok { + Some(ok_type) => self.type_name(&ok_type, from_module_fqn), None => Cow::Borrowed("void"), }, - match result.err { - Some(err_type) => self.generate_type_use(&err_type, resolve), + match r.err { + Some(err_type) => self.type_name(&err_type, from_module_fqn), None => Cow::Borrowed("void"), } )), - TypeDefKind::List(list_type) => Cow::Owned(format!( - "List!({})", - self.generate_type_use(list_type, resolve) + TypeDefKind::List(ty) => Cow::Owned(format!( + "WitList!({})", + self.type_name(&ty, from_module_fqn) )), - TypeDefKind::Map(_, _) => todo!("use of `TypeDefKind::Map`"), - TypeDefKind::FixedLengthList(list_type, length) => Cow::Owned(format!( - "{}[{length}]", - self.generate_type_use(list_type, resolve) - )), - TypeDefKind::Future(future_type) => todo!("use of `TypeDefKind::Future`"), - TypeDefKind::Stream(stream_type) => todo!("use of `TypeDefKind::Stream`"), - TypeDefKind::Type(target_type) => { - self.generate_type_use(target_type, resolve) + TypeDefKind::Future(_) => { + Cow::Borrowed("/* todo - type_name of `future` */") + } + TypeDefKind::Stream(_) => { + Cow::Borrowed("/* todo - type_name of `stream` */") } - TypeDefKind::Unknown => { - panic!("Trying to emit type use for `TypeDefKind::Unknown`?"); + TypeDefKind::FixedLengthList(ty, size) => { + Cow::Owned(format!("{}[{size}]", self.type_name(ty, from_module_fqn))) } + TypeDefKind::Map(_, _) => todo!(), + TypeDefKind::Unknown => unimplemented!(), unhandled => { panic!( - "Encountered unexpected use of ownerless typedef: {unhandled:?}." + "Encountered unexpected `type_name` invocation of ownerless typedef: {unhandled:?}." ); } }, - _ => Cow::Owned( - self.get_type_name(typedef.name.as_ref().unwrap(), &typedef.owner), - ), + _ => Cow::Owned(self.scoped_type_name(*id, from_module_fqn)), } } + Type::ErrorContext => todo!(), } } - fn generate_type_declaration(&self, name: &str, id: TypeId, resolve: &Resolve) -> Source { - let mut src = Source::default(); + fn type_owner_fqn(&self, owner: &TypeOwner) -> Option<&str> { + match &owner { + TypeOwner::None => None, + TypeOwner::Interface(interface_id) => match self.direction { + Some(_) => self + .r#gen + .lookup_interface_fqn(*interface_id, self.direction) + .or_else(|| self.r#gen.lookup_interface_fqn(*interface_id, None)), + None => self.r#gen.lookup_interface_fqn(*interface_id, None), + }, + TypeOwner::World(world_id) => { + if *world_id != self.r#gen.world_id.unwrap() { + panic!("Dealing with type from different world?"); + } - let typedef = &resolve.types[id]; + Some(&self.r#gen.world_fqn) + } + } + } - let upper_name = name.to_upper_camel_case(); - let escaped_name = escape_d_identifier(&upper_name); + fn prologue(&mut self) { + let id = self.interface.unwrap(); - src.push_str(&format!( - "\n/++\n{}\n+/\n", - typedef.docs.contents.as_deref().unwrap_or_default() - )); - match &typedef.kind { - TypeDefKind::Record(record) => { - src.push_str(&format!("struct {escaped_name} {{\n")); - - let mut is_first = true; - for field in &record.fields { - if is_first { - is_first = false; - } else { - src.push_str("\n"); - } + let fqn = self.r#gen.lookup_interface_fqn(id, self.direction).unwrap(); - src.push_str(&format!( - "/++\n{}\n+/\n", - field.docs.contents.as_deref().unwrap_or_default() - )); - src.push_str(&format!( - "{} {};\n", - self.generate_type_use(&field.ty, resolve), - field.name.to_lower_camel_case() - )); - } + let interface = &self.resolve.interfaces[self.interface.unwrap()]; - src.push_str("}\n"); - } - TypeDefKind::Resource => { - //src.push_str(&format!("// TODO: def of resource - {name}")) - todo!("def of `TypeDefKind::Resource`"); - } - TypeDefKind::Handle(handle) => { - todo!("def of `TypeDefKind::Handle`"); - } - TypeDefKind::Flags(flags) => { - let storage_type = match flags.repr() { - FlagsRepr::U8 => "ubyte", - FlagsRepr::U16 => "ushort", - FlagsRepr::U32(1) => "uint", - FlagsRepr::U32(2) => "ulong", - repr => todo!("flags {repr:?}"), - }; + self.src.push_str(&format!( + "/++\n{}\n+/\n", + interface.docs.contents.as_deref().unwrap_or_default() + )); - src.push_str(&format!("enum {escaped_name}_ : {storage_type} {{\n")); - for (index, flag) in flags.flags.iter().enumerate() { - if index != 0 { - src.push_str("\n"); - } - src.push_str(&format!( - "/++\n{}\n+/\n", - flag.docs.contents.as_deref().unwrap_or_default() - )); - src.push_str(&format!( - "{} = 1 << {index},\n", - escape_d_identifier(&flag.name.to_lower_camel_case()) - )); - } - src.push_str(&format!( - "}}\n/// ditto\nalias {escaped_name} = Flags!{escaped_name}_;" - )); - } - TypeDefKind::Tuple(tuple) => src.push_str(&format!( - "alias {escaped_name} = Tuple!({});", - tuple - .types - .iter() - .map(|ty| self.generate_type_use(ty, resolve).into_owned()) - .collect::>() - .join(", ") - )), - TypeDefKind::Variant(variant) => { - let storage_type = match variant.tag() { - Int::U8 => "ubyte", - Int::U16 => "ushort", - Int::U32 => "uint", - Int::U64 => "ulong", - }; + self.src.push_str(&format!("module {};\n\n", fqn)); - src.push_str(&format!("struct {escaped_name} {{\n")); - src.deindent(1); - src.push_str(&format!("@safe @nogc nothrow:\n")); - src.indent(1); + self.src.push_str("import wit.common;\n\n"); + if self.direction.is_some() + && let Some(WorldKey::Interface(_)) = self.name + { + self.src.push_str("public import "); + self.src + .push_str(self.r#gen.lookup_interface_fqn(id, None).unwrap()); + self.src.push_str(";\n\n"); + } - src.push_str(&format!("enum Tag : {storage_type} {{\n")); + let mut deps = BTreeSet::new(); - let mut is_first = true; - for case in &variant.cases { - if is_first { - is_first = false; - } else { - src.push_str("\n"); - } - src.push_str(&format!( - "/++\n{}\n+/\n", - case.docs.contents.as_deref().unwrap_or_default() - )); - src.push_str(&format!( - "{},\n", - escape_d_identifier(&case.name.to_lower_camel_case()) - )); - } + for dep_id in self.resolve.interface_direct_deps(id) { + deps.insert(dep_id); + } - src.push_str("}\n"); - - src.deindent(1); - src.push_str(&format!("\nprivate:\n")); - src.indent(1); - - if variant.cases.iter().any(|case| case.ty.is_some()) { - src.push_str(&format!("union Storage {{\n")); - src.push_str("ubyte __zeroinit = 0;\n"); - for case in &variant.cases { - if let Some(ty) = &case.ty { - src.push_str(&format!( - "{} {};\n", - self.generate_type_use(ty, resolve), - escape_d_identifier(&case.name.to_lower_camel_case()) - )); - } + for dep_id in deps { + let common_fqn = self.r#gen.lookup_interface_fqn(dep_id, None).unwrap(); + let directional_fqn = self.r#gen.lookup_interface_fqn(dep_id, self.direction); + + if let Some(WorldKey::Interface(_)) = self.name { + self.src.push_str(&format!( + "static import {};\n", + match self.direction { + Some(_) => directional_fqn.unwrap_or(common_fqn), + None => common_fqn, } + )); + } else { + self.src + .push_str(&format!("static import {};\n", common_fqn)); - src.push_str("}\n\n"); - - src.push_str("Tag _tag;\n"); - src.push_str("Storage _storage;\n\n"); - } else { - src.push_str("Tag _tag;\n\n"); - } - - src.push_str("@disable this();\n"); - src.push_str("this(Tag tag, Storage storage = Storage.init) {\n"); - src.push_str("_tag = tag;\n"); - src.push_str("_storage = storage;\n"); - src.push_str("}\n"); + if let Some(fqn) = directional_fqn { + self.src.push_str(&format!("static import {};\n", fqn)); + }; + } + } + self.src.push_str("\n"); + } - src.deindent(1); - src.push_str(&format!("\npublic:\n")); - src.indent(1); + fn type_is_direction_sensitive(&self, id: TypeId) -> bool { + let type_info = &self.r#gen.types.get(id); - src.push_str("Tag tag() => _tag;\n"); + type_info.has_resource + } +} - for case in &variant.cases { - src.push_str(&format!( - "\n/++\n{}\n+/\n", - case.docs.contents.as_deref().unwrap_or_default() - )); - let upper_case_name = case.name.to_upper_camel_case(); - let escaped_upper_case_name = escape_d_identifier(&upper_case_name); - - let lower_case_name = case.name.to_lower_camel_case(); - let escaped_lower_case_name = escape_d_identifier(&lower_case_name); - - if let Some(ty) = &case.ty { - src.push_str(&format!( - "static {escaped_name} {escaped_lower_case_name}({} val) {{\n", - self.generate_type_use(ty, resolve) - )); - src.push_str("Storage storage;\n"); - src.push_str(&format!("storage.{escaped_lower_case_name} = val;\n")); - src.push_str(&format!( - "return {escaped_name}(Tag.{escaped_lower_case_name}, storage);\n" - )); - src.push_str("}\n"); - - src.push_str(&format!( - "/// ditto\n ref inout({}) get{escaped_upper_case_name}() inout return\n", - self.generate_type_use(ty, resolve) - )); - - src.push_str(&format!("in (is{escaped_upper_case_name}) ")); - src.push_str(&format!( - "do {{ return _storage.{escaped_lower_case_name}; }}\n" - )); - } else { - src.push_str(&format!( - "static {escaped_name} {escaped_lower_case_name}() => {escaped_name}(Tag.{escaped_lower_case_name});\n", - )); - } - src.push_str(&format!( - "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", - )); - } +impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { + fn resolve(&self) -> &'a Resolve { + self.resolve + } - src.push_str("}\n"); + // Override `types` to filter by `self.direction` + fn types(&mut self, iface: InterfaceId) { + let iface = &self.resolve().interfaces[iface]; + for (name, id) in iface.types.iter() { + if self.direction.is_some() == self.type_is_direction_sensitive(*id) { + self.define_type(name, *id); } - TypeDefKind::Enum(r#enum) => { - let storage_type = match r#enum.tag() { - Int::U8 => "ubyte", - Int::U16 => "ushort", - Int::U32 => "uint", - Int::U64 => "ulong", - }; + } + } - src.push_str(&format!("enum {escaped_name} : {storage_type} {{\n")); + fn type_record(&mut self, id: TypeId, name: &str, record: &Record, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - let mut is_first = true; - for case in &r#enum.cases { - if is_first { - is_first = false; - } else { - src.push_str("\n"); - } - src.push_str(&format!( - "/++\n{}\n+/\n", - case.docs.contents.as_deref().unwrap_or_default() - )); - src.push_str(&format!( - "{},\n", - escape_d_identifier(&case.name.to_lower_camel_case()) - )); - } + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner) + .unwrap() + .to_string(); - src.push_str(&format!("}}")); - } - TypeDefKind::Option(opt_type) => src.push_str(&format!( - "alias {escaped_name} = Option!({});", - self.generate_type_use(opt_type, resolve) - )), - TypeDefKind::Result(result) => src.push_str(&format!( - "alias {escaped_name} = Result!({}, {});", - match result.ok { - Some(ok_type) => self.generate_type_use(&ok_type, resolve), - None => Cow::Borrowed("void"), - }, - match result.err { - Some(err_type) => self.generate_type_use(&err_type, resolve), - None => Cow::Borrowed("void"), - } - )), - TypeDefKind::List(list_type) => src.push_str(&format!( - "alias {escaped_name} = List!({});", - self.generate_type_use(list_type, resolve) - )), - TypeDefKind::Map(_, _) => { - todo!("def of `TypeDefKind::Map`"); - } - TypeDefKind::FixedLengthList(list_type, length) => { - src.push_str(&format!( - "alias {escaped_name} = {}[{length}];", - self.generate_type_use(&list_type, resolve), - )); - } - TypeDefKind::Future(future_type) => { - todo!("def of `TypeDefKind::Future`"); - } - TypeDefKind::Stream(stream_type) => { - todo!("def of `TypeDefKind::Stream`"); - } - TypeDefKind::Type(target_type) => { - src.push_str(&format!( - "alias {escaped_name} = {};", - self.generate_type_use(&target_type, resolve), - )); - } - TypeDefKind::Unknown => { - panic!("Trying to emit type declaration for `TypeDefKind::Unknown`?"); + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!("struct {escaped_name} {{\n")); + + let mut is_first = true; + for field in &record.fields { + if is_first { + is_first = false; + } else { + self.src.push_str("\n"); } + + self.src.push_str(&format!( + "/++\n{}\n+/\n", + field.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "{} {};\n", + self.type_name(&field.ty, &owner_fqn), + field.name.to_lower_camel_case() + )); } - src.push_str("\n"); - src + + self.src.push_str("}\n"); } -} -impl WorldGenerator for D { - fn uses_nominal_type_ids(&self) -> bool { - false + fn type_resource(&mut self, _id: TypeId, name: &str, _docs: &Docs) { + self.src + .push_str(&format!("// TODO: def of `resource` - {name}\n")) + //todo!("def of `resource`") } - fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { - self.cur_world_fqn = get_world_fqn(world, resolve); + fn type_tuple(&mut self, id: TypeId, name: &str, tuple: &Tuple, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - let world = &resolve.worlds[world]; + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); - self.world_src.push_str(&format!( - "/++\n{}\n+/\n", - world.docs.contents.as_deref().unwrap_or_default() + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = Tuple!({});", + tuple + .types + .iter() + .map(|ty| self.type_name(ty, owner_fqn).into_owned()) + .collect::>() + .join(", ") )); + } - self.world_src - .push_str(&format!("module {};\n\n", self.cur_world_fqn)); + fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - self.world_src.push_str("import wit.common;\n\n"); + let storage_type = match flags.repr() { + FlagsRepr::U8 => "ubyte", + FlagsRepr::U16 => "ushort", + FlagsRepr::U32(1) => "uint", + FlagsRepr::U32(2) => "ulong", + repr => todo!("flags {repr:?}"), + }; - self.world_src.push_str("// Interface imports\n"); - } + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!("struct {escaped_name} {{\n")); - fn import_interface( - &mut self, - resolve: &Resolve, - name: &WorldKey, - id: InterfaceId, - _files: &mut Files, - ) -> Result<()> { - self.cur_interface = Some(id); - let interface_src = match self.interfaces.get_mut(&id) { - Some(src) => src, - None => { - eprintln!("Import {id:?}"); - let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, false); - - let mut result_init = InterfaceSource::default(); - result_init.fqn = new_fqn; - self.interfaces.insert(id, result_init); - - let new_src = self.prepare_interface_bindings( - id, - &self.interfaces.get(&id).unwrap().fqn, - &self.cur_world_fqn, - resolve, - ); - - let result = self.interfaces.get_mut(&id).unwrap(); - result.src = new_src; - result + self.src + .push_str(&format!("mixin WitFlags!{storage_type};\n\n")); + + for (index, flag) in flags.flags.iter().enumerate() { + if index != 0 { + self.src.push_str("\n"); } + self.src.push_str(&format!( + "/++\n{}\n+/\n", + flag.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "enum {} = {escaped_name}[{index}];\n", + escape_d_identifier(&flag.name.to_lower_camel_case()) + )); + } + self.src.push_str(&format!("}}\n")); + } + + fn type_variant(&mut self, id: TypeId, name: &str, variant: &Variant, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + let storage_type = match variant.tag() { + Int::U8 => "ubyte", + Int::U16 => "ushort", + Int::U32 => "uint", + Int::U64 => "ulong", }; - if interface_src.imported { - return Ok(()); + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner) + .unwrap() + .to_string(); + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!("struct {escaped_name} {{\n")); + self.src.deindent(1); + self.src.push_str("@safe @nogc nothrow:\n"); + self.src.indent(1); + + self.src + .push_str(&format!("enum Tag : {storage_type} {{\n")); + + let mut is_first = true; + for case in &variant.cases { + if is_first { + is_first = false; + } else { + self.src.push_str("\n"); + } + self.src.push_str(&format!( + "/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "{},\n", + escape_d_identifier(&case.name.to_lower_camel_case()) + )); } - interface_src.imported = true; - self.world_src - .push_str(&format!("public import {};\n", &self.interfaces[&id].fqn)); + self.src.push_str("}\n"); - self.cur_interface = None; - Ok(()) - } + self.src.deindent(1); + self.src.push_str("\nprivate:\n"); + self.src.indent(1); - fn import_types( - &mut self, - resolve: &Resolve, - world: WorldId, - types: &[(&str, TypeId)], - _files: &mut Files, - ) { - self.world_src.push_str(&format!("\n// Type imports\n")); - for (name, id) in types { - let type_src = self.generate_type_declaration(name, *id, resolve); - self.world_src.append_src(&type_src); + if variant.cases.iter().any(|case| case.ty.is_some()) { + self.src.push_str("union Storage {\n"); + self.src.push_str("ubyte __zeroinit = 0;\n"); + for case in &variant.cases { + if let Some(ty) = &case.ty { + self.src.push_str(&format!( + "{} {};\n", + self.type_name(ty, &owner_fqn), + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + } + + self.src.push_str("}\n\n"); + + self.src.push_str("Tag _tag;\n"); + self.src.push_str("Storage _storage;\n\n"); + } else { + self.src.push_str("Tag _tag;\n\n"); } - } - fn import_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) { - self.world_src.push_str(&format!("\n// Function imports\n")); - for (name, func) in funcs { - self.world_src - .push_str(&format!("// Import function: {name}\n")); + self.src.push_str("@disable this();\n"); + self.src + .push_str("this(Tag tag, Storage storage = Storage.init) {\n"); + self.src.push_str("_tag = tag;\n"); + self.src.push_str("_storage = storage;\n"); + self.src.push_str("}\n"); + + self.src.deindent(1); + self.src.push_str("\npublic:\n"); + self.src.indent(1); + + self.src.push_str("Tag tag() => _tag;\n"); + + for case in &variant.cases { + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + let upper_case_name = case.name.to_upper_camel_case(); + let escaped_upper_case_name = escape_d_identifier(&upper_case_name); + + let lower_case_name = case.name.to_lower_camel_case(); + let escaped_lower_case_name = escape_d_identifier(&lower_case_name); + + if let Some(ty) = &case.ty { + self.src.push_str(&format!( + "static {escaped_name} {escaped_lower_case_name}({} val) {{\n", + self.type_name(ty, &owner_fqn) + )); + self.src.push_str("Storage storage;\n"); + self.src + .push_str(&format!("storage.{escaped_lower_case_name} = val;\n")); + self.src.push_str(&format!( + "return {escaped_name}(Tag.{escaped_lower_case_name}, storage);\n" + )); + self.src.push_str("}\n"); + + self.src.push_str(&format!( + "/// ditto\n ref inout({}) get{escaped_upper_case_name}() inout return\n", + self.type_name(ty, &owner_fqn) + )); + + self.src + .push_str(&format!("in (is{escaped_upper_case_name}) ")); + self.src.push_str(&format!( + "do {{ return _storage.{escaped_lower_case_name}; }}\n" + )); + } else { + self.src.push_str(&format!( + "static {escaped_name} {escaped_lower_case_name}() => {escaped_name}(Tag.{escaped_lower_case_name});\n", + )); + } + self.src.push_str(&format!( + "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", + )); } + + self.src.push_str("}\n"); } - fn pre_export_interface(&mut self, resolve: &Resolve, files: &mut Files) -> Result<()> { - self.world_src.push_str("\n// Interface exports\n"); - self.world_src - .push_str("mixin template Exports(alias Impl) {\n"); + fn type_option(&mut self, id: TypeId, name: &str, payload: &Type, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - Ok(()) + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = Option!({});", + self.type_name(payload, owner_fqn) + )); } - fn export_interface( - &mut self, - resolve: &Resolve, - name: &WorldKey, - id: InterfaceId, - _files: &mut Files, - ) -> Result<()> { - self.cur_interface = Some(id); - let interface = &resolve.interfaces[id]; - let interface_src = match self.interfaces.get_mut(&id) { - Some(src) => src, - None => { - eprintln!("Export {id:?}"); - let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, true); - - let mut result_init = InterfaceSource::default(); - result_init.fqn = new_fqn; - self.interfaces.insert(id, result_init); - - let new_src = self.prepare_interface_bindings( - id, - &self.interfaces.get(&id).unwrap().fqn, - &self.cur_world_fqn, - resolve, - ); - - let result = self.interfaces.get_mut(&id).unwrap(); - result.src = new_src; - result + fn type_result(&mut self, id: TypeId, name: &str, result: &Result_, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = Result!({}, {});", + match result.ok { + Some(ok_type) => self.type_name(&ok_type, owner_fqn), + None => Cow::Borrowed("void"), + }, + match result.err { + Some(err_type) => self.type_name(&err_type, owner_fqn), + None => Cow::Borrowed("void"), } + )); + } + + fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + let storage_type = match enum_.tag() { + Int::U8 => "ubyte", + Int::U16 => "ushort", + Int::U32 => "uint", + Int::U64 => "ulong", }; - if interface_src.exported { - return Ok(()); + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src + .push_str(&format!("enum {escaped_name} : {storage_type} {{\n")); + + let mut is_first = true; + for case in &enum_.cases { + if is_first { + is_first = false; + } else { + self.src.push_str("\n"); + } + self.src.push_str(&format!( + "/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "{},\n", + escape_d_identifier(&case.name.to_lower_camel_case()) + )); } - interface_src.exported = true; - self.world_src.push_str(&format!( - "mixin imported!\"{}\".Exports!Impl;\n", - interface_src.fqn - )); + self.src.push_str("}"); + } - interface_src - .src - .push_str("\nmixin template Exports(alias Impl) {\n"); + fn type_alias(&mut self, id: TypeId, name: &str, alias_ty: &Type, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - interface_src - .src - .push_str(&format!("// Function exports\n")); - for (name, func) in &interface.functions { - interface_src - .src - .push_str(&format!("// Export function: {name}\n")); - } + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); - interface_src.src.push_str("}\n"); + let typename = self.type_name( + alias_ty, + self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(), + ); - self.cur_interface = None; - Ok(()) + self.src + .push_str(&format!("alias {escaped_name} = {typename};\n")); } - fn export_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) -> Result<()> { - self.world_src.push_str(&format!("\n// Function exports\n")); - for (name, func) in funcs { - self.world_src - .push_str(&format!("// Export function: {name}\n")); - } - Ok(()) + fn type_list(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = WitList!({});", + self.type_name(ty, owner_fqn) + )); } - fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { - // Close out interface exports - self.world_src.push_str("}\n"); + fn type_fixed_length_list( + &mut self, + id: TypeId, + name: &str, + ty: &Type, + size: u32, + docs: &Docs, + ) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - let mut world_filepath = PathBuf::from_iter(get_world_fqn(id, resolve).split(".")); - world_filepath.push("package.d"); + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); - files.push( - world_filepath.to_str().unwrap(), - self.world_src.as_str().as_bytes(), - ); + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = {}[{size}];", + self.type_name(ty, owner_fqn) + )); + } - files.push("wit/common.d", include_bytes!("wit_common.d")); + fn type_future(&mut self, _id: TypeId, name: &str, _ty: &Option, _docs: &Docs) { + todo!("def of `future` - {name}"); + } - for (_, interface_src) in &self.interfaces { - let mut interface_filepath = PathBuf::from_iter(interface_src.fqn.split(".")); - interface_filepath.add_extension("d"); + fn type_stream(&mut self, _id: TypeId, name: &str, _ty: &Option, _docs: &Docs) { + todo!("def of `stream` - {name}"); + } - files.push( - interface_filepath.to_str().unwrap(), - interface_src.src.as_bytes(), - ); - } - Ok(()) + fn type_builtin(&mut self, _id: TypeId, name: &str, _ty: &Type, _docs: &Docs) { + todo!("def of `builtin` - {name}"); } } diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index f5d3f67f5..755caeca3 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -1,7 +1,7 @@ module wit.common; /// Thin CABI compliant wrapper over `T[]` -struct List(T) { +struct WitList(T) { @safe @nogc pure nothrow: T* ptr; size_t length; @@ -25,7 +25,7 @@ struct List(T) { // except list in WIT is actually List!(dchar) // // We assume UTF-8 data (as D native strings are UTF-8) -alias String = List!(immutable char); +alias WitString = List!(char); // TODO: split this file up and give Tuple a full port of the Phobos version? /// adapted from Phobos std.typecons.Tuple @@ -35,142 +35,32 @@ struct Tuple(Types...) if (is(Types)) { alias expand this; } -/// adapted from Phobos std.bitmanip.BitFlags -struct Flags(Enum) if (is(Enum == enum)) { -@safe @nogc pure nothrow: - public alias E = Enum; - -private: - template allAreBaseEnum(T...) - { - static foreach (Ti; T) - { - static if (!is(typeof(allAreBaseEnum) == bool) && // not yet defined - !is(Ti : E)) - { - enum allAreBaseEnum = false; - } - } - static if (!is(typeof(allAreBaseEnum) == bool)) // if not yet defined - { - enum allAreBaseEnum = true; - } - } - - static if (is(E U == enum)) { - alias Base = U; - } else static assert(0); - - Base mValue; - -public: - this(E flag) - { - this = flag; - } - - this(T...)(T flags) - if (allAreBaseEnum!(T)) - { - this = flags; - } +mixin template WitFlags(T) if (__traits(isUnsigned, T)) { + private alias F = typeof(this); - bool opCast(B: bool)() const - { - return mValue != 0; - } + T bits; - Base opCast(B)() const - if (is(Base : B)) - { - return mValue; - } + @safe nothrow @nogc pure: - auto opUnary(string op)() const - if (op == "~") - { - return WitFlags(cast(E) cast(Base) ~mValue); - } + static typeof(this) opIndex(size_t i) + in(i < T.sizeof*8) => F(cast(T)(1 << i)); - auto ref opAssign(T...)(T flags) - if (allAreBaseEnum!(T)) - { - mValue = 0; - foreach (E flag; flags) - { - mValue |= flag; - } - return this; - } + auto opUnary(string op : "~")() const => F(~bits); - auto ref opAssign(E flag) + auto ref opOpAssign(string op)(F rhs) + if (op == "|" || op == "&" || op == "^") { - mValue = flag; + mixin("bits "~op~"= rhs.bits;"); return this; } - auto ref opOpAssign(string op: "|")(WitFlags flags) + auto opBinary(string op)(F flags) const + if (op == "|" || op == "&" || op == "^") { - mValue |= flags.mValue; - return this; - } - - auto ref opOpAssign(string op: "&")(WitFlags flags) - { - mValue &= flags.mValue; - return this; - } - - auto ref opOpAssign(string op: "|")(E flag) - { - mValue |= flag; - return this; - } - - auto ref opOpAssign(string op: "&")(E flag) - { - mValue &= flag; - return this; - } - - auto opBinary(string op)(WitFlags flags) const - if (op == "|" || op == "&") - { - WitFlags result = this; + F result = this; result.opOpAssign!op(flags); return result; } - - auto opBinary(string op)(E flag) const - if (op == "|" || op == "&") - { - WitFlags result = this; - result.opOpAssign!op(flag); - return result; - } - - auto opBinaryRight(string op)(E flag) const - if (op == "|" || op == "&") - { - return opBinary!op(flag); - } - - bool opDispatch(string name)() const - if (__traits(hasMember, E, name)) - { - enum e = __traits(getMember, E, name); - return (mValue & e) == e; - } - - void opDispatch(string name)(bool set) - if (__traits(hasMember, E, name)) - { - enum e = __traits(getMember, E, name); - if (set) - mValue |= e; - else - mValue &= ~e; - } } /// Based on Rust's Option