Skip to content

Commit bca5742

Browse files
committed
rust: targeted attribute injection via additional_type_attributes/additional_member_attributes
1 parent 93a5f4e commit bca5742

5 files changed

Lines changed: 496 additions & 21 deletions

File tree

crates/guest-rust/macro/src/lib.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ impl Parse for Config {
127127
opts.additional_derive_ignore =
128128
list.into_iter().map(|i| i.value()).collect()
129129
}
130+
Opt::AdditionalTypeAttributes(list) => opts.additional_type_attributes = list,
131+
Opt::AdditionalMemberAttributes(list) => {
132+
opts.additional_member_attributes = list
133+
}
130134
Opt::With(with) => opts.with.extend(with),
131135
Opt::GenerateAll => {
132136
opts.generate_all = true;
@@ -312,6 +316,8 @@ mod kw {
312316
syn::custom_keyword!(export_prefix);
313317
syn::custom_keyword!(additional_derives);
314318
syn::custom_keyword!(additional_derives_ignore);
319+
syn::custom_keyword!(additional_type_attributes);
320+
syn::custom_keyword!(additional_member_attributes);
315321
syn::custom_keyword!(with);
316322
syn::custom_keyword!(generate_all);
317323
syn::custom_keyword!(type_section_suffix);
@@ -394,6 +400,8 @@ enum Opt {
394400
// Parse as paths so we can take the concrete types/macro names rather than raw strings
395401
AdditionalDerives(Vec<syn::Path>),
396402
AdditionalDerivesIgnore(Vec<syn::LitStr>),
403+
AdditionalTypeAttributes(Vec<(String, String)>),
404+
AdditionalMemberAttributes(Vec<(String, String)>),
397405
With(HashMap<String, WithOption>),
398406
GenerateAll,
399407
TypeSectionSuffix(syn::LitStr),
@@ -522,6 +530,26 @@ impl Parse for Opt {
522530
syn::bracketed!(contents in input);
523531
let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
524532
Ok(Opt::AdditionalDerivesIgnore(list.iter().cloned().collect()))
533+
} else if l.peek(kw::additional_type_attributes) {
534+
input.parse::<kw::additional_type_attributes>()?;
535+
input.parse::<Token![:]>()?;
536+
let contents;
537+
braced!(contents in input);
538+
let fields: Punctuated<_, Token![,]> =
539+
contents.parse_terminated(attr_map_field_parse, Token![,])?;
540+
Ok(Opt::AdditionalTypeAttributes(
541+
fields.into_iter().flatten().collect(),
542+
))
543+
} else if l.peek(kw::additional_member_attributes) {
544+
input.parse::<kw::additional_member_attributes>()?;
545+
input.parse::<Token![:]>()?;
546+
let contents;
547+
braced!(contents in input);
548+
let fields: Punctuated<_, Token![,]> =
549+
contents.parse_terminated(attr_map_field_parse, Token![,])?;
550+
Ok(Opt::AdditionalMemberAttributes(
551+
fields.into_iter().flatten().collect(),
552+
))
525553
} else if l.peek(kw::with) {
526554
input.parse::<kw::with>()?;
527555
input.parse::<Token![:]>()?;
@@ -601,6 +629,29 @@ impl Parse for Opt {
601629
}
602630
}
603631

632+
// Parse one `"selector": ["#[attr]", ...]` entry into a (selector, attribute) pair
633+
// per attribute.
634+
fn attr_map_field_parse(input: ParseStream<'_>) -> Result<Vec<(String, String)>> {
635+
let selector = input.parse::<syn::LitStr>()?;
636+
input.parse::<Token![:]>()?;
637+
let contents;
638+
let bracket = syn::bracketed!(contents in input);
639+
let attrs = Punctuated::<syn::LitStr, Token![,]>::parse_terminated(&contents)?;
640+
// An empty list would otherwise flatten away silently and escape the
641+
// unused-selector check in the generator.
642+
if attrs.is_empty() {
643+
return Err(Error::new(
644+
bracket.span.join(),
645+
"attribute list must not be empty",
646+
));
647+
}
648+
let selector = selector.value();
649+
Ok(attrs
650+
.into_iter()
651+
.map(|a| (selector.clone(), a.value()))
652+
.collect())
653+
}
654+
604655
fn with_field_parse(input: ParseStream<'_>) -> Result<(String, WithOption)> {
605656
let interface = input.parse::<syn::LitStr>()?.value();
606657
input.parse::<Token![:]>()?;

crates/guest-rust/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,31 @@ extern crate std;
682682
/// // By default this set is empty.
683683
/// additional_derives: [PartialEq, Eq, Hash, Clone],
684684
///
685+
/// // Extra attributes to emit on specific generated types (records, variants,
686+
/// // and enums), rather than on all types like `additional_derives`. A type is
687+
/// // selected by its bare kebab name, its package (`my:pkg`), its owning
688+
/// // interface (`my:pkg/types`), or its fully-qualified name; the qualified
689+
/// // forms written as in `with`, carrying the `@version` when versioned. An
690+
/// // injected `#[derive(...)]` folds into the generated derive (deduped); other
691+
/// // attributes are emitted verbatim, on every form including the borrowed one
692+
/// // under `Borrowing` (so owned-only derives fail there). See the CLI docs for
693+
/// // the full grammar.
694+
/// //
695+
/// // By default this map is empty.
696+
/// additional_type_attributes: {
697+
/// "my-record": [r#"#[derive(serde::Serialize)]"#], // one type, by bare name
698+
/// "my:pkg/types": [r#"#[derive(Clone)]"#], // every type in an interface
699+
/// },
700+
///
701+
/// // Like `additional_type_attributes`, but for generated record fields and
702+
/// // enum/variant cases, selected by `<type-selector>.member-name` (any type
703+
/// // selector above) or a bare `member-name`.
704+
/// //
705+
/// // By default this map is empty.
706+
/// additional_member_attributes: {
707+
/// "my-record.my-field": [r#"#[serde(rename = "mf")]"#],
708+
/// },
709+
///
685710
/// // When generating bindings for interfaces that are not defined in the
686711
/// // same package as `world`, this option can be used to either generate
687712
/// // those bindings or point to already generated bindings.

crates/rust/src/interface.rs

Lines changed: 150 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::{
66
};
77
use anyhow::Result;
88
use heck::*;
9+
use indexmap::IndexSet;
910
use std::collections::{BTreeMap, BTreeSet};
1011
use std::fmt::Write as _;
1112
use std::mem;
@@ -2086,6 +2087,119 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
20862087
result
20872088
}
20882089

2090+
/// The owning interface and its package, when `id` is owned by an interface.
2091+
fn type_owner(&self, id: TypeId) -> Option<(InterfaceId, &PackageName)> {
2092+
let TypeOwner::Interface(iface_id) = self.resolve.types[id].owner else {
2093+
return None;
2094+
};
2095+
let pkg_id = self.resolve.interfaces[iface_id].package?;
2096+
Some((iface_id, &self.resolve.packages[pkg_id].name))
2097+
}
2098+
2099+
/// Selector keys naming the type itself: its bare wit name and its
2100+
/// fully-qualified `ns:pkg/iface/type` name. Qualified keys are written exactly
2101+
/// as in `with`, so they carry the package `@version` when versioned.
2102+
fn type_identity_keys(&self, id: TypeId) -> Vec<String> {
2103+
let id = dealias(self.resolve, id);
2104+
match self.resolve.types[id].name.as_deref() {
2105+
Some(name) => vec![name.to_string(), full_wit_type_name(self.resolve, id)],
2106+
None => Vec::new(),
2107+
}
2108+
}
2109+
2110+
/// Every selector key that matches type `id`: its identity keys plus its
2111+
/// owning interface and package.
2112+
fn type_selector_keys(&self, id: TypeId) -> Vec<String> {
2113+
let id = dealias(self.resolve, id);
2114+
let mut keys = self.type_identity_keys(id);
2115+
if let Some((iface_id, pkg)) = self.type_owner(id) {
2116+
keys.push(pkg.to_string());
2117+
if let Some(iface) = self.resolve.id_of(iface_id) {
2118+
keys.push(iface);
2119+
}
2120+
}
2121+
keys
2122+
}
2123+
2124+
/// The attributes from `entries` whose selector satisfies `matches`, deduped in
2125+
/// configured order, paired with the distinct selectors that matched. The
2126+
/// matched selectors are recorded so `finish` can report selectors that matched
2127+
/// nothing, exactly as the `with` option reports unused remappings.
2128+
fn matching_attrs(
2129+
entries: &[(String, String)],
2130+
matches: impl Fn(&str) -> bool,
2131+
) -> (Vec<String>, Vec<String>) {
2132+
let mut attrs = IndexSet::new();
2133+
let mut used = IndexSet::new();
2134+
for (sel, attr) in entries.iter().filter(|(sel, _)| matches(sel)) {
2135+
attrs.insert(attr.clone());
2136+
used.insert(sel.clone());
2137+
}
2138+
(attrs.into_iter().collect(), used.into_iter().collect())
2139+
}
2140+
2141+
/// Attributes configured via `additional_type_attributes` for type `id`.
2142+
fn additional_type_attrs(&mut self, id: TypeId) -> Vec<String> {
2143+
let keys = self.type_selector_keys(id);
2144+
let (attrs, used) =
2145+
Self::matching_attrs(&self.r#gen.opts.additional_type_attributes, |sel| {
2146+
keys.iter().any(|k| k == sel)
2147+
});
2148+
self.r#gen.used_type_attr_selectors.extend(used);
2149+
attrs
2150+
}
2151+
2152+
/// Attributes configured via `additional_member_attributes` for `member` (a
2153+
/// record field or enum/variant case) of type `id`, matched by `<type>.<member>`
2154+
/// for any type selector (bare, qualified, interface, or package) or a bare
2155+
/// `<member>`.
2156+
fn additional_member_attrs(&mut self, id: TypeId, member: &str) -> Vec<String> {
2157+
let type_keys = self.type_selector_keys(id);
2158+
let (attrs, used) =
2159+
Self::matching_attrs(&self.r#gen.opts.additional_member_attributes, |sel| {
2160+
// member names are dot-free, so the last `.` splits `<type>.<member>`
2161+
sel == member
2162+
|| sel
2163+
.rsplit_once('.')
2164+
.is_some_and(|(ty, m)| m == member && type_keys.iter().any(|tk| tk == ty))
2165+
});
2166+
self.r#gen.used_member_attr_selectors.extend(used);
2167+
attrs
2168+
}
2169+
2170+
/// Split injected attributes into derive paths and everything else. Derive
2171+
/// paths are merged into the generated `#[derive(...)]` so they dedup against
2172+
/// the built-in and `additional_derives` derives instead of colliding (E0119);
2173+
/// other attributes are emitted verbatim.
2174+
fn split_injected_derives(attrs: &[String]) -> (Vec<String>, Vec<String>) {
2175+
let mut derives = Vec::new();
2176+
let mut others = Vec::new();
2177+
for attr in attrs {
2178+
match attr
2179+
.trim()
2180+
.strip_prefix("#[derive(")
2181+
.and_then(|s| s.strip_suffix(")]"))
2182+
{
2183+
Some(inner) => derives.extend(
2184+
inner
2185+
.split(',')
2186+
.map(str::trim)
2187+
.filter(|p| !p.is_empty())
2188+
.map(String::from),
2189+
),
2190+
None => others.push(attr.clone()),
2191+
}
2192+
}
2193+
(derives, others)
2194+
}
2195+
2196+
/// Emit each attribute on its own line, verbatim.
2197+
fn push_attrs(&mut self, attrs: &[String]) {
2198+
for attr in attrs {
2199+
uwriteln!(self.src, "{attr}");
2200+
}
2201+
}
2202+
20892203
fn print_typedef_record(&mut self, id: TypeId, record: &Record, docs: &Docs) {
20902204
let info = self.info(id);
20912205
// We use a BTree set to make sure we don't have any duplicates and we have a stable order
@@ -2096,6 +2210,14 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
20962210
.iter()
20972211
.cloned()
20982212
.collect();
2213+
// Computed once, then emitted on every ownership mode below.
2214+
let (injected_derives, injected_attrs) =
2215+
Self::split_injected_derives(&self.additional_type_attrs(id));
2216+
let field_attrs: Vec<Vec<String>> = record
2217+
.fields
2218+
.iter()
2219+
.map(|f| self.additional_member_attrs(id, &f.name))
2220+
.collect();
20992221
for (name, mode) in self.modes_of(id) {
21002222
self.rustdoc(docs);
21012223
let mut derives = BTreeSet::new();
@@ -2113,16 +2235,19 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
21132235
} else if info.is_clone() {
21142236
derives.insert("Clone".to_string());
21152237
}
2238+
derives.extend(injected_derives.iter().cloned());
21162239
if !derives.is_empty() {
21172240
self.push_str("#[derive(");
21182241
self.push_str(&derives.into_iter().collect::<Vec<_>>().join(", "));
21192242
self.push_str(")]\n")
21202243
}
2244+
self.push_attrs(&injected_attrs);
21212245
self.push_str(&format!("pub struct {name}"));
21222246
self.print_generics(mode.lifetime);
21232247
self.push_str(" {\n");
2124-
for field in record.fields.iter() {
2248+
for (field, attrs) in record.fields.iter().zip(&field_attrs) {
21252249
self.rustdoc(&field.docs);
2250+
self.push_attrs(attrs);
21262251
self.push_str("pub ");
21272252
self.push_str(&to_rust_ident(&field.name));
21282253
self.push_str(": ");
@@ -2182,10 +2307,12 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
21822307
{
21832308
self.print_rust_enum(
21842309
id,
2310+
// Raw wit case names; `print_rust_enum` upper-camel-cases them at the
2311+
// emit site so member selectors still match the wit (kebab) name.
21852312
variant
21862313
.cases
21872314
.iter()
2188-
.map(|c| (c.name.to_upper_camel_case(), &c.docs, c.ty.as_ref())),
2315+
.map(|c| (c.name.clone(), &c.docs, c.ty.as_ref())),
21892316
docs,
21902317
);
21912318
}
@@ -2207,6 +2334,13 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
22072334
.iter()
22082335
.cloned()
22092336
.collect();
2337+
let (injected_derives, injected_attrs) =
2338+
Self::split_injected_derives(&self.additional_type_attrs(id));
2339+
let case_attrs: Vec<Vec<String>> = cases
2340+
.clone()
2341+
.into_iter()
2342+
.map(|(case_name, _, _)| self.additional_member_attrs(id, &case_name))
2343+
.collect();
22102344
for (name, mode) in self.modes_of(id) {
22112345
self.rustdoc(docs);
22122346
let mut derives = BTreeSet::new();
@@ -2223,17 +2357,20 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
22232357
} else if info.is_clone() {
22242358
derives.insert("Clone".to_string());
22252359
}
2360+
derives.extend(injected_derives.iter().cloned());
22262361
if !derives.is_empty() {
22272362
self.push_str("#[derive(");
22282363
self.push_str(&derives.into_iter().collect::<Vec<_>>().join(", "));
22292364
self.push_str(")]\n")
22302365
}
2366+
self.push_attrs(&injected_attrs);
22312367
self.push_str(&format!("pub enum {name}"));
22322368
self.print_generics(mode.lifetime);
22332369
self.push_str(" {\n");
2234-
for (case_name, docs, payload) in cases.clone() {
2370+
for ((case_name, docs, payload), attrs) in cases.clone().into_iter().zip(&case_attrs) {
22352371
self.rustdoc(docs);
2236-
self.push_str(&case_name);
2372+
self.push_attrs(attrs);
2373+
self.push_str(&case_name.to_upper_camel_case());
22372374
if let Some(ty) = payload {
22382375
self.push_str("(");
22392376
let mode = self.filter_mode(ty, mode);
@@ -2250,7 +2387,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
22502387
cases
22512388
.clone()
22522389
.into_iter()
2253-
.map(|(name, _docs, ty)| (name, ty)),
2390+
.map(|(name, _docs, ty)| (name.to_upper_camel_case(), ty)),
22542391
);
22552392

22562393
if info.error {
@@ -2343,24 +2480,14 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
23432480
}
23442481
}
23452482

2346-
fn print_typedef_enum(
2347-
&mut self,
2348-
id: TypeId,
2349-
name: &str,
2350-
enum_: &Enum,
2351-
docs: &Docs,
2352-
attrs: &[String],
2353-
case_attr: Box<dyn Fn(&EnumCase) -> String>,
2354-
) where
2355-
Self: Sized,
2356-
{
2483+
fn print_typedef_enum(&mut self, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) {
23572484
let info = self.info(id);
23582485

23592486
let name = to_upper_camel_case(name);
23602487
self.rustdoc(docs);
2361-
for attr in attrs {
2362-
self.push_str(&format!("{attr}\n"));
2363-
}
2488+
let (injected_derives, injected_attrs) =
2489+
Self::split_injected_derives(&self.additional_type_attrs(id));
2490+
self.push_attrs(&injected_attrs);
23642491
self.push_str("#[repr(");
23652492
self.int_repr(enum_.tag());
23662493
self.push_str(")]\n");
@@ -2379,13 +2506,15 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
23792506
.into_iter()
23802507
.map(|s| s.to_string()),
23812508
);
2509+
derives.extend(injected_derives);
23822510
self.push_str("#[derive(");
23832511
self.push_str(&derives.into_iter().collect::<Vec<_>>().join(", "));
23842512
self.push_str(")]\n");
23852513
self.push_str(&format!("pub enum {name} {{\n"));
23862514
for case in enum_.cases.iter() {
23872515
self.rustdoc(&case.docs);
2388-
self.push_str(&case_attr(case));
2516+
let case_attrs = self.additional_member_attrs(id, &case.name);
2517+
self.push_attrs(&case_attrs);
23892518
self.push_str(&case.name.to_upper_camel_case());
23902519
self.push_str(",\n");
23912520
}
@@ -2967,7 +3096,7 @@ impl<'a> {camel}Borrow<'a>{{
29673096
}
29683097

29693098
fn type_enum(&mut self, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) {
2970-
self.print_typedef_enum(id, name, enum_, docs, &[], Box::new(|_| String::new()));
3099+
self.print_typedef_enum(id, name, enum_, docs);
29713100

29723101
let name = to_upper_camel_case(name);
29733102
let mut cases = String::new();

0 commit comments

Comments
 (0)