Path: blob/main/crates/wit-bindgen/src/rust.rs
3078 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!(),135TypeDefKind::Map(..) => todo!(),136}137}138}139140match &ty.kind {141TypeDefKind::List(t) => self.list(t, mode),142143TypeDefKind::Option(t) => {144format!("Option<{}>", self.ty(t, mode))145}146147TypeDefKind::Result(r) => {148let ok = self.optional_ty(r.ok.as_ref(), mode);149let err = self.optional_ty(r.err.as_ref(), mode);150format!("Result<{ok},{err}>")151}152153TypeDefKind::Variant(_) => panic!("unsupported anonymous variant"),154155// Tuple-like records are mapped directly to Rust tuples of156// types. Note the trailing comma after each member to157// appropriately handle 1-tuples.158TypeDefKind::Tuple(t) => {159let mut out = "(".to_string();160for ty in t.types.iter() {161out.push_str(&self.ty(ty, mode));162out.push_str(",");163}164out.push_str(")");165out166}167TypeDefKind::Record(_) => {168panic!("unsupported anonymous type reference: record")169}170TypeDefKind::Flags(_) => {171panic!("unsupported anonymous type reference: flags")172}173TypeDefKind::Enum(_) => {174panic!("unsupported anonymous type reference: enum")175}176TypeDefKind::Future(ty) => {177let wt = self.wasmtime_path();178let t = self.optional_ty(ty.as_ref(), TypeMode::Owned);179format!("{wt}::component::FutureReader<{t}>")180}181TypeDefKind::Stream(ty) => {182let wt = self.wasmtime_path();183let t = self.optional_ty(ty.as_ref(), TypeMode::Owned);184format!("{wt}::component::StreamReader<{t}>")185}186TypeDefKind::Handle(handle) => self.handle(handle),187TypeDefKind::Resource => unreachable!(),188189TypeDefKind::Type(t) => self.ty(t, mode),190TypeDefKind::Unknown => unreachable!(),191TypeDefKind::FixedSizeList(..) => todo!(),192TypeDefKind::Map(..) => todo!(),193}194}195196fn type_name_in_interface(&self, owner: TypeOwner, name: &str) -> String {197let mut out = String::new();198if let TypeOwner::Interface(id) = owner {199if let Some(path) = self.path_to_interface(id) {200out.push_str(&path);201out.push_str("::");202}203}204out.push_str(name);205out206}207208fn print_list(&mut self, ty: &Type, mode: TypeMode) {209self.push_str(&self.list(ty, mode))210}211fn list(&self, ty: &Type, mode: TypeMode) -> String {212let next_mode = if matches!(self.ownership(), Ownership::Owning) {213TypeMode::Owned214} else {215mode216};217let ty = self.ty(ty, next_mode);218match mode {219TypeMode::AllBorrowed(lt) => {220if lt != "'_" {221format!("&{lt} [{ty}]")222} else {223format!("&[{ty}]")224}225}226TypeMode::Owned => {227let wt = self.wasmtime_path();228format!("{wt}::component::__internal::Vec<{ty}>")229}230}231}232233fn print_stream(&mut self, ty: Option<&Type>) {234self.push_str(&self.stream(ty))235}236fn stream(&self, ty: Option<&Type>) -> String {237let wt = self.wasmtime_path();238let mut out = format!("{wt}::component::HostStream<");239out.push_str(&self.optional_ty(ty, TypeMode::Owned));240out.push_str(">");241out242}243244fn print_future(&mut self, ty: Option<&Type>) {245self.push_str(&self.future(ty))246}247fn future(&self, ty: Option<&Type>) -> String {248let wt = self.wasmtime_path();249let mut out = format!("{wt}::component::HostFuture<");250out.push_str(&self.optional_ty(ty, TypeMode::Owned));251out.push_str(">");252out253}254255fn print_handle(&mut self, handle: &Handle) {256self.push_str(&self.handle(handle))257}258fn handle(&self, handle: &Handle) -> String {259// Handles are either printed as `ResourceAny` for any guest-defined260// resource or `Resource<T>` for all host-defined resources. This means261// that this function needs to determine if `handle` points to a host262// or a guest resource which is determined by:263//264// * For world-owned resources, they're always imported.265// * For interface-owned resources, it depends on the how bindings were266// last generated for this interface.267//268// Additionally type aliases via `use` are "peeled" here to find the269// original definition of the resource since that's the one that we270// care about for determining whether it's imported or not.271let resource = match handle {272Handle::Own(t) | Handle::Borrow(t) => *t,273};274let ty = &self.resolve().types[resource];275let def_id = super::resolve_type_definition_id(self.resolve(), resource);276let ty_def = &self.resolve().types[def_id];277let is_host_defined = match ty_def.owner {278TypeOwner::Interface(i) => self.is_imported_interface(i),279_ => true,280};281let wt = self.wasmtime_path();282if is_host_defined {283let mut out = format!("{wt}::component::Resource<");284out.push_str(&self.type_name_in_interface(285ty.owner,286&ty.name.as_ref().unwrap().to_upper_camel_case(),287));288out.push_str(">");289out290} else {291format!("{wt}::component::ResourceAny")292}293}294295fn print_generics(&mut self, lifetime: Option<&str>) {296self.push_str(&self.generics(lifetime))297}298fn generics(&self, lifetime: Option<&str>) -> String {299if let Some(lt) = lifetime {300format!("<{lt},>")301} else {302String::new()303}304}305306fn modes_of(&self, ty: TypeId) -> Vec<(String, TypeMode)> {307let info = self.info(ty);308// Info only populated for types that are passed to and from functions. For309// types which are not, default to the ownership setting.310if !info.owned && !info.borrowed {311return vec![(312self.param_name(ty),313match self.ownership() {314Ownership::Owning => TypeMode::Owned,315Ownership::Borrowing { .. } => TypeMode::AllBorrowed("'a"),316},317)];318}319let mut result = Vec::new();320let first_mode =321if info.owned || !info.borrowed || matches!(self.ownership(), Ownership::Owning) {322TypeMode::Owned323} else {324assert!(!self.uses_two_names(&info));325TypeMode::AllBorrowed("'a")326};327result.push((self.result_name(ty), first_mode));328if self.uses_two_names(&info) {329result.push((self.param_name(ty), TypeMode::AllBorrowed("'a")));330}331result332}333334fn param_name(&self, ty: TypeId) -> String {335let info = self.info(ty);336let name = self.resolve().types[ty]337.name338.as_ref()339.unwrap()340.to_upper_camel_case();341if self.uses_two_names(&info) {342format!("{name}Param")343} else {344name345}346}347348fn result_name(&self, ty: TypeId) -> String {349let info = self.info(ty);350let name = self.resolve().types[ty]351.name352.as_ref()353.unwrap()354.to_upper_camel_case();355if self.uses_two_names(&info) {356format!("{name}Result")357} else {358name359}360}361362fn uses_two_names(&self, info: &TypeInfo) -> bool {363info.has_list364&& info.borrowed365&& info.owned366&& matches!(367self.ownership(),368Ownership::Borrowing {369duplicate_if_necessary: true370}371)372}373374fn lifetime_for(&self, info: &TypeInfo, mode: TypeMode) -> Option<&'static str> {375if matches!(self.ownership(), Ownership::Owning) {376return None;377}378let lt = match mode {379TypeMode::AllBorrowed(s) => s,380_ => return None,381};382// No lifetimes needed unless this has a list.383if !info.has_list {384return None;385}386// If two names are used then this type will have an owned and a387// borrowed copy and the borrowed copy is being used, so it needs a388// lifetime. Otherwise if it's only borrowed and not owned then this can389// also use a lifetime since it's not needed in two contexts and only390// the borrowed version of the structure was generated.391if self.uses_two_names(info) || (info.borrowed && !info.owned) {392Some(lt)393} else {394None395}396}397398fn typedfunc_sig(&self, func: &Function, param_mode: TypeMode) -> String {399let mut out = "(".to_string();400for (_, ty) in func.params.iter() {401out.push_str(&self.ty(ty, param_mode));402out.push_str(", ");403}404out.push_str("), (");405if let Some(ty) = func.result {406out.push_str(&self.ty(&ty, TypeMode::Owned));407out.push_str(", ");408}409out.push_str(")");410out411}412}413414/// Translate `name` to a Rust `snake_case` identifier.415pub fn to_rust_ident(name: &str) -> String {416match name {417// Escape Rust keywords.418// Source: https://doc.rust-lang.org/reference/keywords.html419"as" => "as_".into(),420"break" => "break_".into(),421"const" => "const_".into(),422"continue" => "continue_".into(),423"crate" => "crate_".into(),424"else" => "else_".into(),425"enum" => "enum_".into(),426"extern" => "extern_".into(),427"false" => "false_".into(),428"fn" => "fn_".into(),429"for" => "for_".into(),430"if" => "if_".into(),431"impl" => "impl_".into(),432"in" => "in_".into(),433"let" => "let_".into(),434"loop" => "loop_".into(),435"match" => "match_".into(),436"mod" => "mod_".into(),437"move" => "move_".into(),438"mut" => "mut_".into(),439"pub" => "pub_".into(),440"ref" => "ref_".into(),441"return" => "return_".into(),442"self" => "self_".into(),443"static" => "static_".into(),444"struct" => "struct_".into(),445"super" => "super_".into(),446"trait" => "trait_".into(),447"true" => "true_".into(),448"type" => "type_".into(),449"unsafe" => "unsafe_".into(),450"use" => "use_".into(),451"where" => "where_".into(),452"while" => "while_".into(),453"async" => "async_".into(),454"await" => "await_".into(),455"dyn" => "dyn_".into(),456"abstract" => "abstract_".into(),457"become" => "become_".into(),458"box" => "box_".into(),459"do" => "do_".into(),460"final" => "final_".into(),461"macro" => "macro_".into(),462"override" => "override_".into(),463"priv" => "priv_".into(),464"typeof" => "typeof_".into(),465"unsized" => "unsized_".into(),466"virtual" => "virtual_".into(),467"yield" => "yield_".into(),468"try" => "try_".into(),469"gen" => "gen_".into(),470s => s.to_snake_case(),471}472}473474/// Translate `name` to a Rust `UpperCamelCase` identifier.475pub fn to_rust_upper_camel_case(name: &str) -> String {476match name {477// We use `Host` as the name of the trait for host implementations478// to fill in, so rename it if "Host" is used as a regular identifier.479"host" => "Host_".into(),480s => s.to_upper_camel_case(),481}482}483484485