Mirror default-owned change for guests in wit-bindgen (#6189)

This commit is a mirror of bytecodealliance/wit-bindgen#547 into the
`bindgen!` macro for Wasmtime. The new default is to generate only one
Rust type per WIT type input, regardless of if the representation can be
slightly more optimal in niche cases with more borrows. This should make
the macro easier to work with in the limit ideally.

Closes #6124
This commit is contained in:
Alex Crichton
2023-04-10 11:27:13 -05:00
committed by GitHub
parent 2d1dbb17af
commit 83a8ca77cd
4 changed files with 73 additions and 9 deletions

View File

@@ -76,6 +76,7 @@ impl Parse for Config {
Opt::Tracing(val) => opts.tracing = val, Opt::Tracing(val) => opts.tracing = val,
Opt::Async(val) => opts.async_ = val, Opt::Async(val) => opts.async_ = val,
Opt::TrappableErrorType(val) => opts.trappable_error_type = val, Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
Opt::DuplicateIfNecessary(val) => opts.duplicate_if_necessary = val,
} }
} }
} else { } else {
@@ -131,6 +132,7 @@ mod kw {
syn::custom_keyword!(tracing); syn::custom_keyword!(tracing);
syn::custom_keyword!(trappable_error_type); syn::custom_keyword!(trappable_error_type);
syn::custom_keyword!(world); syn::custom_keyword!(world);
syn::custom_keyword!(duplicate_if_necessary);
} }
enum Opt { enum Opt {
@@ -140,6 +142,7 @@ enum Opt {
Tracing(bool), Tracing(bool),
Async(bool), Async(bool),
TrappableErrorType(Vec<TrappableError>), TrappableErrorType(Vec<TrappableError>),
DuplicateIfNecessary(bool),
} }
impl Parse for Opt { impl Parse for Opt {
@@ -165,6 +168,12 @@ impl Parse for Opt {
input.parse::<Token![async]>()?; input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?; input.parse::<Token![:]>()?;
Ok(Opt::Async(input.parse::<syn::LitBool>()?.value)) Ok(Opt::Async(input.parse::<syn::LitBool>()?.value))
} else if l.peek(kw::duplicate_if_necessary) {
input.parse::<kw::duplicate_if_necessary>()?;
input.parse::<Token![:]>()?;
Ok(Opt::DuplicateIfNecessary(
input.parse::<syn::LitBool>()?.value,
))
} else if l.peek(kw::trappable_error_type) { } else if l.peek(kw::trappable_error_type) {
input.parse::<kw::trappable_error_type>()?; input.parse::<kw::trappable_error_type>()?;
input.parse::<Token![:]>()?; input.parse::<Token![:]>()?;

View File

@@ -18,6 +18,7 @@ macro_rules! gentest {
path: $path, path: $path,
world: $name, world: $name,
tracing: true, tracing: true,
duplicate_if_necessary: true,
}); });
} }
} }

View File

@@ -61,6 +61,11 @@ pub struct Opts {
/// A list of "trappable errors" which are used to replace the `E` in /// A list of "trappable errors" which are used to replace the `E` in
/// `result<T, E>` found in WIT. /// `result<T, E>` found in WIT.
pub trappable_error_type: Vec<TrappableError>, pub trappable_error_type: Vec<TrappableError>,
/// Whether or not to generate "duplicate" type definitions for a single
/// WIT type if necessary, for example if it's used as both an import and an
/// export.
pub duplicate_if_necessary: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -1385,6 +1390,10 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
self.resolve self.resolve
} }
fn duplicate_if_necessary(&self) -> bool {
self.gen.opts.duplicate_if_necessary
}
fn path_to_interface(&self, interface: InterfaceId) -> Option<String> { fn path_to_interface(&self, interface: InterfaceId) -> Option<String> {
match self.current_interface { match self.current_interface {
Some(id) if id == interface => None, Some(id) if id == interface => None,

View File

@@ -17,6 +17,16 @@ pub trait RustGenerator<'a> {
fn info(&self, ty: TypeId) -> TypeInfo; fn info(&self, ty: TypeId) -> TypeInfo;
fn path_to_interface(&self, interface: InterfaceId) -> Option<String>; fn path_to_interface(&self, interface: InterfaceId) -> Option<String>;
/// This, if enabled, will possibly cause types to get duplicate copies to
/// get generated of each other. For example a record containing a string
/// used both in the import and export context would get one variant
/// generated for both.
///
/// If this is disabled then the import context would require the same type
/// used for the export context, which has an owned string that might not
/// otherwise be necessary.
fn duplicate_if_necessary(&self) -> bool;
fn print_ty(&mut self, ty: &Type, mode: TypeMode) { fn print_ty(&mut self, ty: &Type, mode: TypeMode) {
match ty { match ty {
Type::Id(t) => self.print_tyid(*t, mode), Type::Id(t) => self.print_tyid(*t, mode),
@@ -58,6 +68,20 @@ pub trait RustGenerator<'a> {
let lt = self.lifetime_for(&info, mode); let lt = self.lifetime_for(&info, mode);
let ty = &self.resolve().types[id]; let ty = &self.resolve().types[id];
if ty.name.is_some() { if ty.name.is_some() {
// If this type has a list internally, no lifetime is being printed,
// but we're in a borrowed mode, then that means we're in a borrowed
// context and don't want ownership of the type but we're using an
// owned type definition. Inject a `&` in front to indicate that, at
// the API level, ownership isn't required.
if info.has_list && lt.is_none() {
if let TypeMode::AllBorrowed(lt) = mode {
self.push_str("&");
if lt != "'_" {
self.push_str(lt);
self.push_str(" ");
}
}
}
let name = if lt.is_some() { let name = if lt.is_some() {
self.param_name(id) self.param_name(id)
} else { } else {
@@ -197,12 +221,19 @@ pub trait RustGenerator<'a> {
fn modes_of(&self, ty: TypeId) -> Vec<(String, TypeMode)> { fn modes_of(&self, ty: TypeId) -> Vec<(String, TypeMode)> {
let info = self.info(ty); let info = self.info(ty);
let mut result = Vec::new(); if !info.owned && !info.borrowed {
if info.borrowed { return Vec::new();
result.push((self.param_name(ty), TypeMode::AllBorrowed("'a")));
} }
if info.owned && (!info.borrowed || self.uses_two_names(&info)) { let mut result = Vec::new();
result.push((self.result_name(ty), TypeMode::Owned)); let first_mode = if info.owned || !info.borrowed {
TypeMode::Owned
} else {
assert!(!self.uses_two_names(&info));
TypeMode::AllBorrowed("'a")
};
result.push((self.result_name(ty), first_mode));
if self.uses_two_names(&info) {
result.push((self.param_name(ty), TypeMode::AllBorrowed("'a")));
} }
return result; return result;
} }
@@ -347,13 +378,27 @@ pub trait RustGenerator<'a> {
} }
fn uses_two_names(&self, info: &TypeInfo) -> bool { fn uses_two_names(&self, info: &TypeInfo) -> bool {
info.has_list && info.borrowed && info.owned info.has_list && info.borrowed && info.owned && self.duplicate_if_necessary()
} }
fn lifetime_for(&self, info: &TypeInfo, mode: TypeMode) -> Option<&'static str> { fn lifetime_for(&self, info: &TypeInfo, mode: TypeMode) -> Option<&'static str> {
match mode { let lt = match mode {
TypeMode::AllBorrowed(s) if info.has_list => Some(s), TypeMode::AllBorrowed(s) => s,
_ => None, _ => return None,
};
// No lifetimes needed unless this has a list.
if !info.has_list {
return None;
}
// If two names are used then this type will have an owned and a
// borrowed copy and the borrowed copy is being used, so it needs a
// lifetime. Otherwise if it's only borrowed and not owned then this can
// also use a lifetime since it's not needed in two contexts and only
// the borrowed version of the structure was generated.
if self.uses_two_names(info) || (info.borrowed && !info.owned) {
Some(lt)
} else {
None
} }
} }
} }