Path: blob/main/crates/wit-bindgen/src/rust.rs
1693 views
use crate::{Ownership, types::TypeInfo};1use heck::*;2use wit_parser::*;34#[derive(Debug, Copy, Clone, PartialEq)]5pub enum TypeMode {6Owned,7AllBorrowed(&'static str),8}910pub trait RustGenerator<'a> {11fn resolve(&self) -> &'a Resolve;1213fn push_str(&mut self, s: &str);14fn info(&self, ty: TypeId) -> TypeInfo;15fn path_to_interface(&self, interface: InterfaceId) -> Option<String>;16fn is_imported_interface(&self, interface: InterfaceId) -> bool;17fn wasmtime_path(&self) -> String;1819/// This determines whether we generate owning types or (where appropriate)20/// borrowing types.21///22/// For example, when generating a type which is only used as a parameter to23/// a guest-exported function, there is no need for it to own its fields.24/// However, constructing deeply-nested borrows (e.g. `&[&[&[&str]]]]` for25/// `list<list<list<string>>>`) can be very awkward, so by default we26/// generate owning types and use only shallow borrowing at the top level27/// inside function signatures.28fn ownership(&self) -> Ownership;2930fn print_ty(&mut self, ty: &Type, mode: TypeMode) {31self.push_str(&self.ty(ty, mode))32}33fn ty(&self, ty: &Type, mode: TypeMode) -> String {34match ty {35Type::Id(t) => self.tyid(*t, mode),36Type::Bool => "bool".to_string(),37Type::U8 => "u8".to_string(),38Type::U16 => "u16".to_string(),39Type::U32 => "u32".to_string(),40Type::U64 => "u64".to_string(),41Type::S8 => "i8".to_string(),42Type::S16 => "i16".to_string(),43Type::S32 => "i32".to_string(),44Type::S64 => "i64".to_string(),45Type::F32 => "f32".to_string(),46Type::F64 => "f64".to_string(),47Type::Char => "char".to_string(),48Type::String => match mode {49TypeMode::AllBorrowed(lt) => {50if lt != "'_" {51format!("&{lt} str")52} else {53format!("&str")54}55}56TypeMode::Owned => {57let wt = self.wasmtime_path();58format!("{wt}::component::__internal::String")59}60},61Type::ErrorContext => {62let wt = self.wasmtime_path();63format!("{wt}::component::ErrorContext")64}65}66}6768fn print_optional_ty(&mut self, ty: Option<&Type>, mode: TypeMode) {69self.push_str(&self.optional_ty(ty, mode))70}71fn optional_ty(&self, ty: Option<&Type>, mode: TypeMode) -> String {72match ty {73Some(ty) => self.ty(ty, mode),74None => "()".to_string(),75}76}7778fn tyid(&self, id: TypeId, mode: TypeMode) -> String {79let info = self.info(id);80let lt = self.lifetime_for(&info, mode);81let ty = &self.resolve().types[id];82if ty.name.is_some() {83// If this type has a list internally, no lifetime is being printed,84// but we're in a borrowed mode, then that means we're in a borrowed85// context and don't want ownership of the type but we're using an86// owned type definition. Inject a `&` in front to indicate that, at87// the API level, ownership isn't required.88let mut out = String::new();89if info.has_list && lt.is_none() {90if let TypeMode::AllBorrowed(lt) = mode {91if lt != "'_" {92out.push_str(&format!("&{lt} "))93} else {94out.push_str("&")95}96}97}98let name = if lt.is_some() {99self.param_name(id)100} else {101self.result_name(id)102};103out.push_str(&self.type_name_in_interface(ty.owner, &name));104105// If the type recursively owns data and it's a106// variant/record/list, then we need to place the107// lifetime parameter on the type as well.108if info.has_list && needs_generics(self.resolve(), &ty.kind) {109out.push_str(&self.generics(lt));110}111112return out;113114fn needs_generics(resolve: &Resolve, ty: &TypeDefKind) -> bool {115match ty {116TypeDefKind::Variant(_)117| TypeDefKind::Record(_)118| TypeDefKind::Option(_)119| TypeDefKind::Result(_)120| TypeDefKind::Future(_)121| TypeDefKind::Stream(_)122| TypeDefKind::List(_)123| TypeDefKind::Flags(_)124| TypeDefKind::Enum(_)125| TypeDefKind::Tuple(_)126| TypeDefKind::Handle(_)127| TypeDefKind::Resource => true,128TypeDefKind::Type(Type::Id(t)) => {129needs_generics(resolve, &resolve.types[*t].kind)130}131TypeDefKind::Type(Type::String) => true,132TypeDefKind::Type(_) => false,133TypeDefKind::Unknown => unreachable!(),134TypeDefKind::FixedSizeList(..) => todo!(),135}136}137}138139match &ty.kind {140TypeDefKind::List(t) => self.list(t, mode),141142TypeDefKind::Option(t) => {143format!("Option<{}>", self.ty(t, mode))144}145146TypeDefKind::Result(r) => {147let ok = self.optional_ty(r.ok.as_ref(), mode);148let err = self.optional_ty(r.err.as_ref(), mode);149format!("Result<{ok},{err}>")150}151152TypeDefKind::Variant(_) => panic!("unsupported anonymous variant"),153154// Tuple-like records are mapped directly to Rust tuples of155// types. Note the trailing comma after each member to156// appropriately handle 1-tuples.157TypeDefKind::Tuple(t) => {158let mut out = "(".to_string();159for ty in t.types.iter() {160out.push_str(&self.ty(ty, mode));161out.push_str(",");162}163out.push_str(")");164out165}166TypeDefKind::Record(_) => {167panic!("unsupported anonymous type reference: record")168}169TypeDefKind::Flags(_) => {170panic!("unsupported anonymous type reference: flags")171}172TypeDefKind::Enum(_) => {173panic!("unsupported anonymous type reference: enum")174}175TypeDefKind::Future(ty) => {176let wt = self.wasmtime_path();177let t = self.optional_ty(ty.as_ref(), TypeMode::Owned);178format!("{wt}::component::FutureReader<{t}>")179}180TypeDefKind::Stream(ty) => {181let wt = self.wasmtime_path();182let t = self.optional_ty(ty.as_ref(), TypeMode::Owned);183format!("{wt}::component::StreamReader<{t}>")184}185TypeDefKind::Handle(handle) => self.handle(handle),186TypeDefKind::Resource => unreachable!(),187188TypeDefKind::Type(t) => self.ty(t, mode),189TypeDefKind::Unknown => unreachable!(),190TypeDefKind::FixedSizeList(..) => todo!(),191}192}193194fn type_name_in_interface(&self, owner: TypeOwner, name: &str) -> String {195let mut out = String::new();196if let TypeOwner::Interface(id) = owner {197if let Some(path) = self.path_to_interface(id) {198out.push_str(&path);199out.push_str("::");200}201}202out.push_str(name);203out204}205206fn print_list(&mut self, ty: &Type, mode: TypeMode) {207self.push_str(&self.list(ty, mode))208}209fn list(&self, ty: &Type, mode: TypeMode) -> String {210let next_mode = if matches!(self.ownership(), Ownership::Owning) {211TypeMode::Owned212} else {213mode214};215let ty = self.ty(ty, next_mode);216match mode {217TypeMode::AllBorrowed(lt) => {218if lt != "'_" {219format!("&{lt} [{ty}]")220} else {221format!("&[{ty}]")222}223}224TypeMode::Owned => {225let wt = self.wasmtime_path();226format!("{wt}::component::__internal::Vec<{ty}>")227}228}229}230231fn print_stream(&mut self, ty: Option<&Type>) {232self.push_str(&self.stream(ty))233}234fn stream(&self, ty: Option<&Type>) -> String {235let wt = self.wasmtime_path();236let mut out = format!("{wt}::component::HostStream<");237out.push_str(&self.optional_ty(ty, TypeMode::Owned));238out.push_str(">");239out240}241242fn print_future(&mut self, ty: Option<&Type>) {243self.push_str(&self.future(ty))244}245fn future(&self, ty: Option<&Type>) -> String {246let wt = self.wasmtime_path();247let mut out = format!("{wt}::component::HostFuture<");248out.push_str(&self.optional_ty(ty, TypeMode::Owned));249out.push_str(">");250out251}252253fn print_handle(&mut self, handle: &Handle) {254self.push_str(&self.handle(handle))255}256fn handle(&self, handle: &Handle) -> String {257// Handles are either printed as `ResourceAny` for any guest-defined258// resource or `Resource<T>` for all host-defined resources. This means259// that this function needs to determine if `handle` points to a host260// or a guest resource which is determined by:261//262// * For world-owned resources, they're always imported.263// * For interface-owned resources, it depends on the how bindings were264// last generated for this interface.265//266// Additionally type aliases via `use` are "peeled" here to find the267// original definition of the resource since that's the one that we268// care about for determining whether it's imported or not.269let resource = match handle {270Handle::Own(t) | Handle::Borrow(t) => *t,271};272let ty = &self.resolve().types[resource];273let def_id = super::resolve_type_definition_id(self.resolve(), resource);274let ty_def = &self.resolve().types[def_id];275let is_host_defined = match ty_def.owner {276TypeOwner::Interface(i) => self.is_imported_interface(i),277_ => true,278};279let wt = self.wasmtime_path();280if is_host_defined {281let mut out = format!("{wt}::component::Resource<");282out.push_str(&self.type_name_in_interface(283ty.owner,284&ty.name.as_ref().unwrap().to_upper_camel_case(),285));286out.push_str(">");287out288} else {289format!("{wt}::component::ResourceAny")290}291}292293fn print_generics(&mut self, lifetime: Option<&str>) {294self.push_str(&self.generics(lifetime))295}296fn generics(&self, lifetime: Option<&str>) -> String {297if let Some(lt) = lifetime {298format!("<{lt},>")299} else {300String::new()301}302}303304fn modes_of(&self, ty: TypeId) -> Vec<(String, TypeMode)> {305let info = self.info(ty);306// Info only populated for types that are passed to and from functions. For307// types which are not, default to the ownership setting.308if !info.owned && !info.borrowed {309return vec![(310self.param_name(ty),311match self.ownership() {312Ownership::Owning => TypeMode::Owned,313Ownership::Borrowing { .. } => TypeMode::AllBorrowed("'a"),314},315)];316}317let mut result = Vec::new();318let first_mode =319if info.owned || !info.borrowed || matches!(self.ownership(), Ownership::Owning) {320TypeMode::Owned321} else {322assert!(!self.uses_two_names(&info));323TypeMode::AllBorrowed("'a")324};325result.push((self.result_name(ty), first_mode));326if self.uses_two_names(&info) {327result.push((self.param_name(ty), TypeMode::AllBorrowed("'a")));328}329result330}331332fn param_name(&self, ty: TypeId) -> String {333let info = self.info(ty);334let name = self.resolve().types[ty]335.name336.as_ref()337.unwrap()338.to_upper_camel_case();339if self.uses_two_names(&info) {340format!("{name}Param")341} else {342name343}344}345346fn result_name(&self, ty: TypeId) -> String {347let info = self.info(ty);348let name = self.resolve().types[ty]349.name350.as_ref()351.unwrap()352.to_upper_camel_case();353if self.uses_two_names(&info) {354format!("{name}Result")355} else {356name357}358}359360fn uses_two_names(&self, info: &TypeInfo) -> bool {361info.has_list362&& info.borrowed363&& info.owned364&& matches!(365self.ownership(),366Ownership::Borrowing {367duplicate_if_necessary: true368}369)370}371372fn lifetime_for(&self, info: &TypeInfo, mode: TypeMode) -> Option<&'static str> {373if matches!(self.ownership(), Ownership::Owning) {374return None;375}376let lt = match mode {377TypeMode::AllBorrowed(s) => s,378_ => return None,379};380// No lifetimes needed unless this has a list.381if !info.has_list {382return None;383}384// If two names are used then this type will have an owned and a385// borrowed copy and the borrowed copy is being used, so it needs a386// lifetime. Otherwise if it's only borrowed and not owned then this can387// also use a lifetime since it's not needed in two contexts and only388// the borrowed version of the structure was generated.389if self.uses_two_names(info) || (info.borrowed && !info.owned) {390Some(lt)391} else {392None393}394}395396fn typedfunc_sig(&self, func: &Function, param_mode: TypeMode) -> String {397let mut out = "(".to_string();398for (_, ty) in func.params.iter() {399out.push_str(&self.ty(ty, param_mode));400out.push_str(", ");401}402out.push_str("), (");403if let Some(ty) = func.result {404out.push_str(&self.ty(&ty, TypeMode::Owned));405out.push_str(", ");406}407out.push_str(")");408out409}410}411412/// Translate `name` to a Rust `snake_case` identifier.413pub fn to_rust_ident(name: &str) -> String {414match name {415// Escape Rust keywords.416// Source: https://doc.rust-lang.org/reference/keywords.html417"as" => "as_".into(),418"break" => "break_".into(),419"const" => "const_".into(),420"continue" => "continue_".into(),421"crate" => "crate_".into(),422"else" => "else_".into(),423"enum" => "enum_".into(),424"extern" => "extern_".into(),425"false" => "false_".into(),426"fn" => "fn_".into(),427"for" => "for_".into(),428"if" => "if_".into(),429"impl" => "impl_".into(),430"in" => "in_".into(),431"let" => "let_".into(),432"loop" => "loop_".into(),433"match" => "match_".into(),434"mod" => "mod_".into(),435"move" => "move_".into(),436"mut" => "mut_".into(),437"pub" => "pub_".into(),438"ref" => "ref_".into(),439"return" => "return_".into(),440"self" => "self_".into(),441"static" => "static_".into(),442"struct" => "struct_".into(),443"super" => "super_".into(),444"trait" => "trait_".into(),445"true" => "true_".into(),446"type" => "type_".into(),447"unsafe" => "unsafe_".into(),448"use" => "use_".into(),449"where" => "where_".into(),450"while" => "while_".into(),451"async" => "async_".into(),452"await" => "await_".into(),453"dyn" => "dyn_".into(),454"abstract" => "abstract_".into(),455"become" => "become_".into(),456"box" => "box_".into(),457"do" => "do_".into(),458"final" => "final_".into(),459"macro" => "macro_".into(),460"override" => "override_".into(),461"priv" => "priv_".into(),462"typeof" => "typeof_".into(),463"unsized" => "unsized_".into(),464"virtual" => "virtual_".into(),465"yield" => "yield_".into(),466"try" => "try_".into(),467"gen" => "gen_".into(),468s => s.to_snake_case(),469}470}471472/// Translate `name` to a Rust `UpperCamelCase` identifier.473pub fn to_rust_upper_camel_case(name: &str) -> String {474match name {475// We use `Host` as the name of the trait for host implementations476// to fill in, so rename it if "Host" is used as a regular identifier.477"host" => "Host_".into(),478s => s.to_upper_camel_case(),479}480}481482483