Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
20 changes: 20 additions & 0 deletions crates/guest-rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,26 @@ 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 bare name, its package, its interface, or its fully
/// // qualified name, the qualified forms written as in `with`.
/// //
/// // By default this map is empty.
/// additional_type_attributes: {
/// "my-record": [#[derive(serde::Serialize)]], // one type, by bare name
/// "my:pkg/types": [#[derive(Hash)]], // every type in an interface
/// },
///
/// // Like `additional_type_attributes`, but for generated record fields and
/// // enum/variant cases, selected by `<type-selector>.member-name` (any type
/// // selector above) or a bare `member-name`.
/// //
/// // By default this map is empty.
/// additional_member_attributes: {
/// "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
100 changes: 78 additions & 22 deletions crates/rust/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use crate::bindgen::{FunctionBindgen, POINTER_SIZE_EXPRESSION};
use crate::{
ConstructorReturnType, FnSig, Identifier, InterfaceName, Ownership, RuntimeItem, RustFlagsRepr,
RustWasm, TypeGeneration, classify_constructor_return_type, full_wit_type_name, int_repr,
to_rust_ident, to_upper_camel_case, wasm_type,
to_rust_ident, to_upper_camel_case, wasm_type, wit_type_selectors,
};
use anyhow::Result;
use heck::*;
use indexmap::IndexSet;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write as _;
use std::mem;
Expand Down Expand Up @@ -2086,6 +2087,51 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
result
}

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

fn additional_type_attrs(&mut self, selectors: &[String]) -> Vec<String> {
let (attrs, used) =
Self::matching_attrs(&self.r#gen.opts.additional_type_attributes, |sel| {
selectors.iter().any(|name| name == sel)
});
self.r#gen.used_type_attr_selectors.extend(used);
attrs
}

/// `member` is a record field or an enum/variant case, selected by a bare
/// name or `<type-selector>.<member>`. Member names contain no `.`, so the
/// last one separates the two halves.
fn additional_member_attrs(&mut self, selectors: &[String], member: &str) -> Vec<String> {
let (attrs, used) =
Self::matching_attrs(&self.r#gen.opts.additional_member_attributes, |sel| {
sel == member
|| sel.rsplit_once('.').is_some_and(|(ty, m)| {
m == member && selectors.iter().any(|name| name == ty)
})
});
self.r#gen.used_member_attr_selectors.extend(used);
Comment thread
wilfreddenton marked this conversation as resolved.
Outdated
attrs
}

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 +2142,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 selectors = wit_type_selectors(self.resolve, id);
let injected_attrs = self.additional_type_attrs(&selectors);
let field_attrs: Vec<Vec<String>> = record
.fields
.iter()
.map(|f| self.additional_member_attrs(&selectors, &f.name))
.collect();
for (name, mode) in self.modes_of(id) {
self.rustdoc(docs);
let mut derives = BTreeSet::new();
Expand All @@ -2118,11 +2172,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 +2241,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 +2263,13 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8)
.iter()
.cloned()
.collect();
let selectors = wit_type_selectors(self.resolve, id);
let injected_attrs = self.additional_type_attrs(&selectors);
let case_attrs: Vec<Vec<String>> = cases
.clone()
.into_iter()
.map(|(case_name, _, _)| self.additional_member_attrs(&selectors, &case_name))
.collect();
for (name, mode) in self.modes_of(id) {
self.rustdoc(docs);
let mut derives = BTreeSet::new();
Expand All @@ -2228,12 +2291,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 +2315,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 +2408,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 selectors = wit_type_selectors(self.resolve, id);
let injected_attrs = self.additional_type_attrs(&selectors);
self.push_str("#[repr(");
self.int_repr(enum_.tag());
self.push_str(")]\n");
Expand All @@ -2382,10 +2436,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(&selectors, &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 +3023,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