Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 10 additions & 6 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,13 @@ impl Types {
live.add_type(resolve, ty);
}
for id in live.iter() {
let info = self.type_info.get_mut(&id).unwrap();
if import {
info.borrowed = true;
} else {
info.owned = true;
if resolve.types[id].name.is_some() {
let info = self.type_info.get_mut(&id).unwrap();
if import {
info.borrowed = true;
} else {
info.owned = true;
}
}
}
let mut live = LiveTypes::default();
Expand All @@ -93,7 +95,9 @@ impl Types {
live.add_type(resolve, ty);
}
for id in live.iter() {
self.type_info.get_mut(&id).unwrap().owned = true;
if resolve.types[id].name.is_some() {
self.type_info.get_mut(&id).unwrap().owned = true;
}
}

for ty in func.results.iter_types() {
Expand Down
102 changes: 79 additions & 23 deletions crates/rust-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use heck::*;
use std::collections::HashMap;
use std::fmt::{self, Write};
use std::iter::zip;
use std::str::FromStr;
use wit_bindgen_core::wit_parser::abi::{Bitcast, LiftLower, WasmType};
use wit_bindgen_core::{wit_parser::*, TypeInfo, Types};

Expand All @@ -12,19 +13,51 @@ pub enum TypeMode {
LeafBorrowed(&'static str),
}

#[derive(Default, Debug, Clone, Copy)]
pub enum Ownership {
/// Generated types will be composed entirely of owning fields, regardless
/// of whether they are used as parameters to imports or not.
#[default]
Owning,

/// Generated types used as parameters to imports will be "deeply
/// borrowing", i.e. contain references rather than owned values when
/// applicable.
Borrowing {
/// 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, or if it's used both as a parameter to an import and
/// a return value from an import.
duplicate_if_necessary: bool,
},
}

impl FromStr for Ownership {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"owning" => Ok(Self::Owning),
"borrowing" => Ok(Self::Borrowing {
duplicate_if_necessary: false,
}),
"borrowing-duplicate-if-necessary" => Ok(Self::Borrowing {
duplicate_if_necessary: true,
}),
_ => Err(format!(
"unrecognized ownership: `{s}`; \
expected `owning`, `borrowing`, or `borrowing-duplicate-if-necessary`"
)),
}
}
}

pub trait RustGenerator<'a> {
fn resolve(&self) -> &'a Resolve;
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;
/// Whether to generate owning or borrowing type definitions.
fn ownership(&self) -> Ownership;

/// Return true iff the generator should qualify uses of `std` features with
/// `#[cfg(feature = "std")]` in its output.
Expand All @@ -46,7 +79,13 @@ pub trait RustGenerator<'a> {
fn push_str(&mut self, s: &str);
fn info(&self, ty: TypeId) -> TypeInfo;
fn types_mut(&mut self) -> &mut Types;
fn print_borrowed_slice(&mut self, mutbl: bool, ty: &Type, lifetime: &'static str);
fn print_borrowed_slice(
&mut self,
mutbl: bool,
ty: &Type,
lifetime: &'static str,
mode: TypeMode,
);
fn print_borrowed_str(&mut self, lifetime: &'static str);

fn rustdoc(&mut self, docs: &Docs) {
Expand Down Expand Up @@ -346,30 +385,35 @@ pub trait RustGenerator<'a> {
}

fn print_list(&mut self, ty: &Type, mode: TypeMode) {
let next_mode = if matches!(self.ownership(), Ownership::Owning) {
TypeMode::Owned
} else {
mode
};
match mode {
TypeMode::AllBorrowed(lt) => {
self.print_borrowed_slice(false, ty, lt);
self.print_borrowed_slice(false, ty, lt, next_mode);
}
TypeMode::LeafBorrowed(lt) => {
if self.resolve().all_bits_valid(ty) {
self.print_borrowed_slice(false, ty, lt);
self.print_borrowed_slice(false, ty, lt, next_mode);
} else {
self.push_str(self.vec_name());
self.push_str("::<");
self.print_ty(ty, mode);
self.print_ty(ty, next_mode);
self.push_str(">");
}
}
TypeMode::Owned => {
self.push_str(self.vec_name());
self.push_str("::<");
self.print_ty(ty, mode);
self.print_ty(ty, next_mode);
self.push_str(">");
}
}
}

fn print_rust_slice(&mut self, mutbl: bool, ty: &Type, lifetime: &'static str) {
fn print_rust_slice(&mut self, mutbl: bool, ty: &Type, lifetime: &'static str, mode: TypeMode) {
self.push_str("&");
if lifetime != "'_" {
self.push_str(lifetime);
Expand All @@ -379,7 +423,7 @@ pub trait RustGenerator<'a> {
self.push_str(" mut ");
}
self.push_str("[");
self.print_ty(ty, TypeMode::AllBorrowed(lifetime));
self.print_ty(ty, mode);
self.push_str("]");
}

Expand Down Expand Up @@ -409,12 +453,13 @@ pub trait RustGenerator<'a> {
return Vec::new();
}
let mut result = Vec::new();
let first_mode = if info.owned || !info.borrowed {
TypeMode::Owned
} else {
assert!(!self.uses_two_names(&info));
TypeMode::AllBorrowed("'a")
};
let first_mode =
if info.owned || !info.borrowed || matches!(self.ownership(), Ownership::Owning) {
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")));
Expand Down Expand Up @@ -1009,10 +1054,21 @@ pub trait RustGenerator<'a> {
}

fn uses_two_names(&self, info: &TypeInfo) -> bool {
info.has_list && info.borrowed && info.owned && self.duplicate_if_necessary()
info.has_list
&& info.borrowed
&& info.owned
&& matches!(
self.ownership(),
Ownership::Borrowing {
duplicate_if_necessary: true
}
)
}

fn lifetime_for(&self, info: &TypeInfo, mode: TypeMode) -> Option<&'static str> {
if matches!(self.ownership(), Ownership::Owning) {
return None;
}
let lt = match mode {
TypeMode::AllBorrowed(s) | TypeMode::LeafBorrowed(s) => s,
_ => return None,
Expand Down
1 change: 1 addition & 0 deletions crates/rust-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ proc-macro2 = "1.0"
syn = "2.0"
wit-bindgen-core = { workspace = true }
wit-bindgen-rust = { workspace = true }
wit-bindgen-rust-lib = { workspace = true }
wit-component = { workspace = true }
anyhow = { workspace = true }
50 changes: 43 additions & 7 deletions crates/rust-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use proc_macro2::{Span, TokenStream};
use std::path::{Path, PathBuf};
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::{token, Token};
use syn::{braced, token, Token};
use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};
use wit_bindgen_rust::Opts;
use wit_bindgen_rust_lib::Ownership;

#[proc_macro]
pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
Expand Down Expand Up @@ -60,7 +61,7 @@ impl Parse for Config {
Opt::UseStdFeature => opts.std_feature = true,
Opt::RawStrings => opts.raw_strings = true,
Opt::MacroExport => opts.macro_export = true,
Opt::DuplicateIfNecessary => opts.duplicate_if_necessary = true,
Opt::Ownership(ownership) => opts.ownership = ownership,
Opt::MacroCallPrefix(prefix) => opts.macro_call_prefix = Some(prefix.value()),
Opt::ExportMacroName(name) => opts.export_macro_name = Some(name.value()),
Opt::Skip(list) => opts.skip.extend(list.iter().map(|i| i.value())),
Expand Down Expand Up @@ -146,7 +147,7 @@ mod kw {
syn::custom_keyword!(world);
syn::custom_keyword!(path);
syn::custom_keyword!(inline);
syn::custom_keyword!(duplicate_if_necessary);
syn::custom_keyword!(ownership);
}

enum Opt {
Expand All @@ -159,7 +160,7 @@ enum Opt {
MacroCallPrefix(syn::LitStr),
ExportMacroName(syn::LitStr),
Skip(Vec<syn::LitStr>),
DuplicateIfNecessary,
Ownership(Ownership),
}

impl Parse for Opt {
Expand All @@ -186,9 +187,44 @@ impl Parse for Opt {
} else if l.peek(kw::macro_export) {
input.parse::<kw::macro_export>()?;
Ok(Opt::MacroExport)
} else if l.peek(kw::duplicate_if_necessary) {
input.parse::<kw::duplicate_if_necessary>()?;
Ok(Opt::DuplicateIfNecessary)
} else if l.peek(kw::ownership) {
input.parse::<kw::ownership>()?;
input.parse::<Token![:]>()?;
let ownership = input.parse::<syn::Ident>()?;
Ok(Opt::Ownership(match ownership.to_string().as_str() {
"Owning" => Ownership::Owning,
"Borrowing" => Ownership::Borrowing {
duplicate_if_necessary: {
let contents;
braced!(contents in input);
let field = contents.parse::<syn::Ident>()?;
match field.to_string().as_str() {
"duplicate_if_necessary" => {
contents.parse::<Token![:]>()?;
contents.parse::<syn::LitBool>()?.value
}
name => {
return Err(Error::new(
field.span(),
format!(
"unrecognized `Ownership::Borrowing` field: `{name}`; \
expected `duplicate_if_necessary`"
),
));
}
}
},
},
name => {
return Err(Error::new(
ownership.span(),
format!(
"unrecognized ownership: `{name}`; \
expected `Owning` or `Borrowing`"
),
));
}
}))
} else if l.peek(kw::macro_call_prefix) {
input.parse::<kw::macro_call_prefix>()?;
input.parse::<Token![:]>()?;
Expand Down
33 changes: 23 additions & 10 deletions crates/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use wit_bindgen_core::{
WorldGenerator,
};
use wit_bindgen_rust_lib::{
int_repr, to_rust_ident, wasm_type, FnSig, RustFlagsRepr, RustFunctionGenerator, RustGenerator,
TypeMode,
int_repr, to_rust_ident, wasm_type, FnSig, Ownership, RustFlagsRepr, RustFunctionGenerator,
RustGenerator, TypeMode,
};

#[derive(Default)]
Expand Down Expand Up @@ -69,11 +69,18 @@ pub struct Opts {
#[cfg_attr(feature = "clap", arg(long))]
pub skip: Vec<String>,

/// 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.
/// Whether to generate owning or borrowing type definitions.
///
/// Valid values include:
/// - `owning`: Generated types will be composed entirely of owning fields,
/// regardless of whether they are used as parameters to imports or not.
/// - `borrowing`: Generated types used as parameters to imports will be
/// "deeply borrowing", i.e. contain references rather than owned values
/// when applicable.
/// - `borrowing-duplicate-if-necessary`: As above, but generating distinct
/// types for borrowing and owning, if necessary.
#[cfg_attr(feature = "clap", arg(long))]
pub duplicate_if_necessary: bool,
pub ownership: Ownership,
}

impl Opts {
Expand Down Expand Up @@ -724,8 +731,8 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
self.resolve
}

fn duplicate_if_necessary(&self) -> bool {
self.gen.opts.duplicate_if_necessary
fn ownership(&self) -> Ownership {
self.gen.opts.ownership
}

fn path_to_interface(&self, interface: InterfaceId) -> Option<String> {
Expand Down Expand Up @@ -779,8 +786,14 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
&mut self.gen.types
}

fn print_borrowed_slice(&mut self, mutbl: bool, ty: &Type, lifetime: &'static str) {
self.print_rust_slice(mutbl, ty, lifetime);
fn print_borrowed_slice(
&mut self,
mutbl: bool,
ty: &Type,
lifetime: &'static str,
mode: TypeMode,
) {
self.print_rust_slice(mutbl, ty, lifetime, mode);
}

fn print_borrowed_str(&mut self, lifetime: &'static str) {
Expand Down
4 changes: 3 additions & 1 deletion crates/rust/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ mod codegen_tests {
mod duplicate {
wit_bindgen::generate!({
path: $test,
duplicate_if_necessary,
ownership: Borrowing {
duplicate_if_necessary: true
}
});

#[test]
Expand Down
12 changes: 12 additions & 0 deletions crates/test-rust-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,15 @@ test = false
[[bin]]
name = "results"
test = false

[[bin]]
name = "owning"
test = false

[[bin]]
name = "borrowing"
test = false

[[bin]]
name = "borrowing-duplicate-if-necessary"
test = false
Loading