Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions crates/guest-rust/macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ impl Parse for Config {
opts.additional_derive_ignore =
list.into_iter().map(|i| i.value()).collect()
}
Opt::AdditionalTypeAttributes(list) => opts.additional_type_attributes = list,
Opt::AdditionalMemberAttributes(list) => {
opts.additional_member_attributes = list
}
Opt::With(with) => opts.with.extend(with),
Opt::GenerateAll => {
opts.generate_all = true;
Expand Down Expand Up @@ -312,6 +316,8 @@ mod kw {
syn::custom_keyword!(export_prefix);
syn::custom_keyword!(additional_derives);
syn::custom_keyword!(additional_derives_ignore);
syn::custom_keyword!(additional_type_attributes);
syn::custom_keyword!(additional_member_attributes);
syn::custom_keyword!(with);
syn::custom_keyword!(generate_all);
syn::custom_keyword!(type_section_suffix);
Expand Down Expand Up @@ -394,6 +400,8 @@ enum Opt {
// Parse as paths so we can take the concrete types/macro names rather than raw strings
AdditionalDerives(Vec<syn::Path>),
AdditionalDerivesIgnore(Vec<syn::LitStr>),
AdditionalTypeAttributes(Vec<(String, String)>),
AdditionalMemberAttributes(Vec<(String, String)>),
With(HashMap<String, WithOption>),
GenerateAll,
TypeSectionSuffix(syn::LitStr),
Expand Down Expand Up @@ -522,6 +530,26 @@ impl Parse for Opt {
syn::bracketed!(contents in input);
let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
Ok(Opt::AdditionalDerivesIgnore(list.iter().cloned().collect()))
} else if l.peek(kw::additional_type_attributes) {
input.parse::<kw::additional_type_attributes>()?;
input.parse::<Token![:]>()?;
let contents;
braced!(contents in input);
let fields: Punctuated<_, Token![,]> =
contents.parse_terminated(attr_map_field_parse, Token![,])?;
Ok(Opt::AdditionalTypeAttributes(
fields.into_iter().flatten().collect(),
))
} else if l.peek(kw::additional_member_attributes) {
input.parse::<kw::additional_member_attributes>()?;
input.parse::<Token![:]>()?;
let contents;
braced!(contents in input);
let fields: Punctuated<_, Token![,]> =
contents.parse_terminated(attr_map_field_parse, Token![,])?;
Ok(Opt::AdditionalMemberAttributes(
fields.into_iter().flatten().collect(),
))
} else if l.peek(kw::with) {
input.parse::<kw::with>()?;
input.parse::<Token![:]>()?;
Expand Down Expand Up @@ -601,6 +629,30 @@ impl Parse for Opt {
}
}

// Parse one `"selector": [#[attr] ...]` entry into a pair per attribute.
fn attr_map_field_parse(input: ParseStream<'_>) -> Result<Vec<(String, String)>> {
let selector = input.parse::<syn::LitStr>()?;
input.parse::<Token![:]>()?;
let contents;
let bracket = syn::bracketed!(contents in input);
let attrs = contents.call(syn::Attribute::parse_outer)?;
if !contents.is_empty() {
return Err(contents.error("expected an outer attribute, `#[...]`"));
}
// An empty list would flatten away and escape the unused-selector check.
if attrs.is_empty() {
return Err(Error::new(
bracket.span.join(),
"attribute list must not be empty",
));
}
let selector = selector.value();
Ok(attrs
.into_iter()
.map(|a| (selector.clone(), a.to_token_stream().to_string()))
.collect())
}

fn with_field_parse(input: ParseStream<'_>) -> Result<(String, WithOption)> {
let interface = input.parse::<syn::LitStr>()?.value();
input.parse::<Token![:]>()?;
Expand Down
17 changes: 17 additions & 0 deletions crates/guest-rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,23 @@ extern crate std;
/// // By default this set is empty.
/// additional_derives: [PartialEq, Eq, Hash, Clone],
///
/// // Extra attributes to emit on specific generated types (records, variants,
/// // and enums), rather than on all types like `additional_derives`. A type
/// // is selected by its fully qualified name, written as in `with`.
/// //
/// // By default this map is empty.
/// additional_type_attributes: {
/// "my:pkg/types/my-record": [#[derive(serde::Serialize)]],
/// },
///
/// // Like `additional_type_attributes`, but for generated record fields and
/// // enum/variant cases, selected by `<type-name>.member-name`.
/// //
/// // By default this map is empty.
/// additional_member_attributes: {
/// "my:pkg/types/my-record.my-field": [#[serde(rename = "mf")]],
/// },
///
/// // When generating bindings for interfaces that are not defined in the
/// // same package as `world`, this option can be used to either generate
/// // those bindings or point to already generated bindings.
Expand Down
95 changes: 73 additions & 22 deletions crates/rust/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use crate::{
};
use anyhow::Result;
use heck::*;
use std::collections::{BTreeMap, BTreeSet};
use indexmap::IndexSet;
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::fmt::Write as _;
use std::mem;
use wit_bindgen_core::abi::{self, AbiVariant, LiftLower};
Expand Down Expand Up @@ -2086,6 +2087,46 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
result
}

/// Matching attributes, deduped in configured order. Selectors that matched
/// are recorded in `used` so `finish` can report the ones that did not.
fn matching_attrs(
entries: &[(String, String)],
used: &mut HashSet<String>,
matches: impl Fn(&str) -> bool,
) -> Vec<String> {
let mut attrs = IndexSet::new();
for (sel, attr) in entries.iter().filter(|(sel, _)| matches(sel)) {
attrs.insert(attr.clone());
used.insert(sel.clone());
}
attrs.into_iter().collect()
}

fn additional_type_attrs(&mut self, type_name: &str) -> Vec<String> {
Self::matching_attrs(
&self.r#gen.opts.additional_type_attributes,
&mut self.r#gen.used_type_attr_selectors,
|sel| sel == type_name,
)
}

/// `member` is a record field or an enum/variant case, selected by
/// `<type-name>.<member>`. Member names contain no `.`, so the last one
/// separates the two halves.
fn additional_member_attrs(&mut self, type_name: &str, member: &str) -> Vec<String> {
Self::matching_attrs(
&self.r#gen.opts.additional_member_attributes,
&mut self.r#gen.used_member_attr_selectors,
|sel| sel.rsplit_once('.') == Some((type_name, member)),
)
}

fn push_attrs(&mut self, attrs: &[String]) {
for attr in attrs {
uwriteln!(self.src, "{attr}");
}
}

fn print_typedef_record(&mut self, id: TypeId, record: &Record, docs: &Docs) {
let info = self.info(id);
// We use a BTree set to make sure we don't have any duplicates and we have a stable order
Expand All @@ -2096,6 +2137,14 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
.iter()
.cloned()
.collect();
// Computed once, then emitted on every ownership mode below.
let type_name = full_wit_type_name(self.resolve, id);
let injected_attrs = self.additional_type_attrs(&type_name);
let field_attrs: Vec<Vec<String>> = record
.fields
.iter()
.map(|f| self.additional_member_attrs(&type_name, &f.name))
.collect();
for (name, mode) in self.modes_of(id) {
self.rustdoc(docs);
let mut derives = BTreeSet::new();
Expand All @@ -2118,11 +2167,13 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
self.push_str(&derives.into_iter().collect::<Vec<_>>().join(", "));
self.push_str(")]\n")
}
self.push_attrs(&injected_attrs);
self.push_str(&format!("pub struct {name}"));
self.print_generics(mode.lifetime);
self.push_str(" {\n");
for field in record.fields.iter() {
for (field, attrs) in record.fields.iter().zip(&field_attrs) {
self.rustdoc(&field.docs);
self.push_attrs(attrs);
self.push_str("pub ");
self.push_str(&to_rust_ident(&field.name));
self.push_str(": ");
Expand Down Expand Up @@ -2185,7 +2236,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
variant
.cases
.iter()
.map(|c| (c.name.to_upper_camel_case(), &c.docs, c.ty.as_ref())),
.map(|c| (c.name.clone(), &c.docs, c.ty.as_ref())),
docs,
);
}
Expand All @@ -2207,6 +2258,13 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
.iter()
.cloned()
.collect();
let type_name = full_wit_type_name(self.resolve, id);
let injected_attrs = self.additional_type_attrs(&type_name);
let case_attrs: Vec<Vec<String>> = cases
.clone()
.into_iter()
.map(|(case_name, _, _)| self.additional_member_attrs(&type_name, &case_name))
.collect();
for (name, mode) in self.modes_of(id) {
self.rustdoc(docs);
let mut derives = BTreeSet::new();
Expand All @@ -2228,12 +2286,14 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
self.push_str(&derives.into_iter().collect::<Vec<_>>().join(", "));
self.push_str(")]\n")
}
self.push_attrs(&injected_attrs);
self.push_str(&format!("pub enum {name}"));
self.print_generics(mode.lifetime);
self.push_str(" {\n");
for (case_name, docs, payload) in cases.clone() {
for ((case_name, docs, payload), attrs) in cases.clone().into_iter().zip(&case_attrs) {
self.rustdoc(docs);
self.push_str(&case_name);
self.push_attrs(attrs);
self.push_str(&case_name.to_upper_camel_case());
if let Some(ty) = payload {
self.push_str("(");
let mode = self.filter_mode(ty, mode);
Expand All @@ -2250,7 +2310,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
cases
.clone()
.into_iter()
.map(|(name, _docs, ty)| (name, ty)),
.map(|(name, _docs, ty)| (name.to_upper_camel_case(), ty)),
);

if info.error {
Expand Down Expand Up @@ -2343,24 +2403,13 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
}
}

fn print_typedef_enum(
&mut self,
id: TypeId,
name: &str,
enum_: &Enum,
docs: &Docs,
attrs: &[String],
case_attr: Box<dyn Fn(&EnumCase) -> String>,
) where
Self: Sized,
{
fn print_typedef_enum(&mut self, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) {
let info = self.info(id);

let name = to_upper_camel_case(name);
self.rustdoc(docs);
for attr in attrs {
self.push_str(&format!("{attr}\n"));
}
let type_name = full_wit_type_name(self.resolve, id);
let injected_attrs = self.additional_type_attrs(&type_name);
self.push_str("#[repr(");
self.int_repr(enum_.tag());
self.push_str(")]\n");
Expand All @@ -2382,10 +2431,12 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
self.push_str("#[derive(");
self.push_str(&derives.into_iter().collect::<Vec<_>>().join(", "));
self.push_str(")]\n");
self.push_attrs(&injected_attrs);
self.push_str(&format!("pub enum {name} {{\n"));
for case in enum_.cases.iter() {
self.rustdoc(&case.docs);
self.push_str(&case_attr(case));
let case_attrs = self.additional_member_attrs(&type_name, &case.name);
self.push_attrs(&case_attrs);
self.push_str(&case.name.to_upper_camel_case());
self.push_str(",\n");
}
Expand Down Expand Up @@ -2967,7 +3018,7 @@ impl<'a> {camel}Borrow<'a>{{
}

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

let name = to_upper_camel_case(name);
let mut cases = String::new();
Expand Down
Loading
Loading