Skip to content

Commit 81ae4c0

Browse files
greenhatpchickey
andauthored
feature: with individual type remapping support for Rust (#1173)
* feature: `with` individual type remapping support for Rust Add individual type remapping in the with parameter of the wit_bindgen::generate macro The type remapping happens in the `InterfaceGenerator::type_path` only for the types provided via with macros parameter. Effectively, the remapped type is used in place of the original type everywhere in the generated bindings for any imports and exports, including lifting and lowering functions. The original type definition is not generated. This approach puts a requirement on the remapped type to have the same internal structure and identical to what would wit-bindgen generate (including alignment, etc.), since lifting/lowering uses its fields directly. See the `codegen::with_type` test for the example of the remapping in action for various types. * comment rewording Co-authored-by: Pat Hickey <pat@moreproductive.org> * comment rewording Co-authored-by: Pat Hickey <pat@moreproductive.org> * comment rewording Co-authored-by: Pat Hickey <pat@moreproductive.org> * comment rewording Co-authored-by: Pat Hickey <pat@moreproductive.org> * comment rewording Co-authored-by: Pat Hickey <pat@moreproductive.org> * comment rewording Co-authored-by: Pat Hickey <pat@moreproductive.org> * refactor: introduce `TypeGeneration::generated()` --------- Co-authored-by: Pat Hickey <pat@moreproductive.org>
1 parent 1830a6d commit 81ae4c0

File tree

4 files changed

+263
-42
lines changed

4 files changed

+263
-42
lines changed

crates/guest-rust/src/lib.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -681,23 +681,30 @@
681681
/// // already has generated bindings for all WASI types and structures. In this
682682
/// // situation the key `with` here can be used to use those types
683683
/// // elsewhere rather than regenerating types.
684+
/// // If for example your world refers to some type and you want to use
685+
/// // your own custom implementation of that type then you can specify
686+
/// // that here as well. There is a requirement on the remapped (custom)
687+
/// // type to have the same internal structure and identical to what would
688+
/// // wit-bindgen generate (including alignment, etc.), since
689+
/// // lifting/lowering uses its fields directly.
684690
/// //
685691
/// // If, however, your world refers to interfaces for which you don't have
686692
/// // already generated bindings then you can use the special `generate` value
687693
/// // to have those bindings generated.
688694
/// //
689-
/// // The `with` key only supports replacing types at the interface level
690-
/// // at this time.
695+
/// // The `with` key here works for interfaces and individual types.
691696
/// //
692-
/// // When an interface is specified no bindings will be generated at
693-
/// // all. It's assumed bindings are fully generated somewhere else. This is an
694-
/// // indicator that any further references to types defined in these
695-
/// // interfaces should use the upstream paths specified here instead.
697+
/// // When an interface or type is specified here no bindings will be
698+
/// // generated at all. It's assumed bindings are fully generated
699+
/// // somewhere else. This is an indicator that any further references to types
700+
/// // defined in these interfaces should use the upstream paths specified
701+
/// // here instead.
696702
/// //
697703
/// // Any unused keys in this map are considered an error.
698704
/// with: {
699705
/// "wasi:io/poll": wasi::io::poll,
700706
/// "some:package/my-interface": generate,
707+
/// "some:package/my-interface/my-type": my_crate::types::MyType,
701708
/// },
702709
///
703710
/// // Indicates that all interfaces not present in `with` should be assumed

crates/rust/src/interface.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::bindgen::FunctionBindgen;
22
use crate::{
3-
int_repr, to_rust_ident, to_upper_camel_case, wasm_type, AsyncConfig, FnSig, Identifier,
4-
InterfaceName, Ownership, RuntimeItem, RustFlagsRepr, RustWasm,
3+
full_wit_type_name, int_repr, to_rust_ident, to_upper_camel_case, wasm_type, AsyncConfig,
4+
FnSig, Identifier, InterfaceName, Ownership, RuntimeItem, RustFlagsRepr, RustWasm,
5+
TypeGeneration,
56
};
67
use anyhow::Result;
78
use heck::*;
@@ -1814,14 +1815,19 @@ pub mod vtable{ordinal} {{
18141815
}
18151816

18161817
pub fn type_path(&self, id: TypeId, owned: bool) -> String {
1817-
self.type_path_with_name(
1818-
id,
1819-
if owned {
1820-
self.result_name(id)
1821-
} else {
1822-
self.param_name(id)
1823-
},
1824-
)
1818+
let full_wit_type_name = full_wit_type_name(self.resolve, id);
1819+
if let Some(TypeGeneration::Remap(remapped_path)) = self.gen.with.get(&full_wit_type_name) {
1820+
remapped_path.clone()
1821+
} else {
1822+
self.type_path_with_name(
1823+
id,
1824+
if owned {
1825+
self.result_name(id)
1826+
} else {
1827+
self.param_name(id)
1828+
},
1829+
)
1830+
}
18251831
}
18261832

18271833
fn type_path_with_name(&self, id: TypeId, name: String) -> String {

crates/rust/src/lib.rs

Lines changed: 100 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::interface::InterfaceGenerator;
22
use anyhow::{bail, Result};
3+
use core::panic;
34
use heck::*;
45
use indexmap::{IndexMap, IndexSet};
56
use std::collections::{BTreeMap, HashMap, HashSet};
@@ -8,8 +9,8 @@ use std::mem;
89
use std::str::FromStr;
910
use wit_bindgen_core::abi::{Bitcast, WasmType};
1011
use wit_bindgen_core::{
11-
name_package_module, uwrite, uwriteln, wit_parser::*, Files, InterfaceGenerator as _, Source,
12-
Types, WorldGenerator,
12+
dealias, name_package_module, uwrite, uwriteln, wit_parser::*, Files, InterfaceGenerator as _,
13+
Source, Types, WorldGenerator,
1314
};
1415

1516
mod bindgen;
@@ -38,15 +39,15 @@ struct RustWasm {
3839
interface_last_seen_as_import: HashMap<InterfaceId, bool>,
3940
import_funcs_called: bool,
4041
with_name_counter: usize,
41-
// Track which interfaces were generated. Remapped interfaces provided via `with`
42+
// Track which interfaces and types are generated. Remapped interfaces and types provided via `with`
4243
// are required to be used.
43-
generated_interfaces: HashSet<String>,
44+
generated_types: HashSet<String>,
4445
world: Option<WorldId>,
4546

4647
rt_module: IndexSet<RuntimeItem>,
4748
export_macros: Vec<(String, String)>,
4849

49-
/// Interface names to how they should be generated
50+
/// Maps wit interface and type names to their Rust identifiers
5051
with: GenerationConfiguration,
5152

5253
future_payloads: IndexMap<String, String>,
@@ -55,35 +56,45 @@ struct RustWasm {
5556

5657
#[derive(Default)]
5758
struct GenerationConfiguration {
58-
map: HashMap<String, InterfaceGeneration>,
59+
map: HashMap<String, TypeGeneration>,
5960
generate_by_default: bool,
6061
}
6162

6263
impl GenerationConfiguration {
63-
fn get(&self, key: &str) -> Option<&InterfaceGeneration> {
64+
fn get(&self, key: &str) -> Option<&TypeGeneration> {
6465
self.map.get(key).or_else(|| {
6566
self.generate_by_default
66-
.then_some(&InterfaceGeneration::Generate)
67+
.then_some(&TypeGeneration::Generate)
6768
})
6869
}
6970

70-
fn insert(&mut self, name: String, generate: InterfaceGeneration) {
71+
fn insert(&mut self, name: String, generate: TypeGeneration) {
7172
self.map.insert(name, generate);
7273
}
7374

74-
fn iter(&self) -> impl Iterator<Item = (&String, &InterfaceGeneration)> {
75+
fn iter(&self) -> impl Iterator<Item = (&String, &TypeGeneration)> {
7576
self.map.iter()
7677
}
7778
}
7879

79-
/// How an interface should be generated.
80-
enum InterfaceGeneration {
81-
/// Remapped to some other type
80+
/// How a wit interface or type should be rendered in Rust
81+
enum TypeGeneration {
82+
/// Uses a Rust identifier defined elsewhere
8283
Remap(String),
83-
/// Generate the interface
84+
/// Define the interface or type with this bindgen invocation
8485
Generate,
8586
}
8687

88+
impl TypeGeneration {
89+
/// Returns true if the interface or type should be defined with this bindgen invocation
90+
fn generated(&self) -> bool {
91+
match self {
92+
TypeGeneration::Generate => true,
93+
TypeGeneration::Remap(_) => false,
94+
}
95+
}
96+
}
97+
8798
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
8899
enum RuntimeItem {
89100
AllocCrate,
@@ -237,7 +248,7 @@ pub struct Opts {
237248
#[cfg_attr(feature = "clap", arg(long = "additional_derive_attribute", short = 'd', default_values_t = Vec::<String>::new()))]
238249
pub additional_derive_attributes: Vec<String>,
239250

240-
/// Remapping of interface names to rust module names.
251+
/// Remapping of wit interface and type names to Rust module names and types.
241252
///
242253
/// Argument must be of the form `k=v` and this option can be passed
243254
/// multiple times or one option can be comma separated, for example
@@ -410,9 +421,9 @@ impl RustWasm {
410421
let Some(remapping) = self.with.get(&with_name) else {
411422
bail!(MissingWith(with_name));
412423
};
413-
self.generated_interfaces.insert(with_name);
424+
self.generated_types.insert(with_name);
414425
let entry = match remapping {
415-
InterfaceGeneration::Remap(remapped_path) => {
426+
TypeGeneration::Remap(remapped_path) => {
416427
let name = format!("__with_name{}", self.with_name_counter);
417428
self.with_name_counter += 1;
418429
uwriteln!(self.src, "use {remapped_path} as {name};");
@@ -421,7 +432,7 @@ impl RustWasm {
421432
path: name,
422433
}
423434
}
424-
InterfaceGeneration::Generate => {
435+
TypeGeneration::Generate => {
425436
let path = compute_module_path(name, resolve, is_export).join("::");
426437

427438
InterfaceName {
@@ -1086,7 +1097,7 @@ impl WorldGenerator for RustWasm {
10861097
if resolve.interfaces[*id].package == world.package {
10871098
let name = resolve.name_world_key(key);
10881099
if self.with.get(&name).is_none() {
1089-
self.with.insert(name, InterfaceGeneration::Generate);
1100+
self.with.insert(name, TypeGeneration::Generate);
10901101
}
10911102
}
10921103
}
@@ -1105,6 +1116,20 @@ impl WorldGenerator for RustWasm {
11051116
id: InterfaceId,
11061117
_files: &mut Files,
11071118
) -> Result<()> {
1119+
let mut to_define = Vec::new();
1120+
for (name, ty_id) in resolve.interfaces[id].types.iter() {
1121+
let full_name = full_wit_type_name(resolve, *ty_id);
1122+
if let Some(type_gen) = self.with.get(&full_name) {
1123+
// skip type definition generation for remapped types
1124+
if type_gen.generated() {
1125+
to_define.push((name, ty_id));
1126+
}
1127+
} else {
1128+
to_define.push((name, ty_id));
1129+
}
1130+
self.generated_types.insert(full_name);
1131+
}
1132+
11081133
self.interface_last_seen_as_import.insert(id, true);
11091134
let wasm_import_module = resolve.name_world_key(name);
11101135
let mut gen = self.interface(
@@ -1117,7 +1142,10 @@ impl WorldGenerator for RustWasm {
11171142
if gen.gen.name_interface(resolve, id, name, false)? {
11181143
return Ok(());
11191144
}
1120-
gen.types(id);
1145+
1146+
for (name, ty_id) in to_define {
1147+
gen.define_type(&name, *ty_id);
1148+
}
11211149

11221150
gen.generate_imports(resolve.interfaces[id].functions.values(), Some(name));
11231151

@@ -1152,6 +1180,20 @@ impl WorldGenerator for RustWasm {
11521180
id: InterfaceId,
11531181
_files: &mut Files,
11541182
) -> Result<()> {
1183+
let mut to_define = Vec::new();
1184+
for (name, ty_id) in resolve.interfaces[id].types.iter() {
1185+
let full_name = full_wit_type_name(resolve, *ty_id);
1186+
if let Some(type_gen) = self.with.get(&full_name) {
1187+
// skip type definition generation for remapped types
1188+
if type_gen.generated() {
1189+
to_define.push((name, ty_id));
1190+
}
1191+
} else {
1192+
to_define.push((name, ty_id));
1193+
}
1194+
self.generated_types.insert(full_name);
1195+
}
1196+
11551197
self.interface_last_seen_as_import.insert(id, false);
11561198
let wasm_import_module = format!("[export]{}", resolve.name_world_key(name));
11571199
let mut gen = self.interface(
@@ -1164,7 +1206,11 @@ impl WorldGenerator for RustWasm {
11641206
if gen.gen.name_interface(resolve, id, name, true)? {
11651207
return Ok(());
11661208
}
1167-
gen.types(id);
1209+
1210+
for (name, ty_id) in to_define {
1211+
gen.define_type(&name, *ty_id);
1212+
}
1213+
11681214
let macro_name =
11691215
gen.generate_exports(Some((id, name)), resolve.interfaces[id].functions.values())?;
11701216

@@ -1218,8 +1264,21 @@ impl WorldGenerator for RustWasm {
12181264
types: &[(&str, TypeId)],
12191265
_files: &mut Files,
12201266
) {
1267+
let mut to_define = Vec::new();
1268+
for (name, ty_id) in types {
1269+
let full_name = full_wit_type_name(resolve, *ty_id);
1270+
if let Some(type_gen) = self.with.get(&full_name) {
1271+
// skip type definition generation for remapped types
1272+
if type_gen.generated() {
1273+
to_define.push((name, ty_id));
1274+
}
1275+
} else {
1276+
to_define.push((name, ty_id));
1277+
}
1278+
self.generated_types.insert(full_name);
1279+
}
12211280
let mut gen = self.interface(Identifier::World(world), "$root", resolve, true);
1222-
for (name, ty) in types {
1281+
for (name, ty) in to_define {
12231282
gen.define_type(name, *ty);
12241283
}
12251284
let src = gen.finish();
@@ -1333,7 +1392,7 @@ impl WorldGenerator for RustWasm {
13331392
.collect::<HashSet<String>>();
13341393

13351394
let mut unused_keys = remapped_keys
1336-
.difference(&self.generated_interfaces)
1395+
.difference(&self.generated_types)
13371396
.collect::<Vec<&String>>();
13381397

13391398
unused_keys.sort();
@@ -1457,11 +1516,11 @@ impl std::fmt::Display for WithOption {
14571516
}
14581517
}
14591518

1460-
impl From<WithOption> for InterfaceGeneration {
1519+
impl From<WithOption> for TypeGeneration {
14611520
fn from(opt: WithOption) -> Self {
14621521
match opt {
1463-
WithOption::Path(p) => InterfaceGeneration::Remap(p),
1464-
WithOption::Generate => InterfaceGeneration::Generate,
1522+
WithOption::Path(p) => TypeGeneration::Remap(p),
1523+
WithOption::Generate => TypeGeneration::Generate,
14651524
}
14661525
}
14671526
}
@@ -1682,3 +1741,18 @@ impl std::error::Error for MissingWith {}
16821741
// ```
16831742
// with: {{\n\t{with_name:?}: generate\n}}
16841743
// ```")
1744+
1745+
/// Returns the full WIT type name with fully qualified interface name
1746+
fn full_wit_type_name(resolve: &Resolve, id: TypeId) -> String {
1747+
let id = dealias(resolve, id);
1748+
let type_def = &resolve.types[id];
1749+
let interface_name = match type_def.owner {
1750+
TypeOwner::World(w) => Some(resolve.worlds[w].name.clone()),
1751+
TypeOwner::Interface(id) => resolve.id_of(id),
1752+
TypeOwner::None => None,
1753+
};
1754+
match interface_name {
1755+
Some(interface_name) => format!("{}/{}", interface_name, type_def.name.clone().unwrap()),
1756+
None => type_def.name.clone().unwrap(),
1757+
}
1758+
}

0 commit comments

Comments
 (0)