wiggle_generate/
names.rs

1use escaping::{escape_id, handle_2big_enum_variant, NamingConvention};
2use heck::{ToShoutySnakeCase, ToSnakeCase};
3use proc_macro2::{Ident, TokenStream};
4use quote::{format_ident, quote};
5use witx::{BuiltinType, Id, Type, TypeRef, WasmType};
6
7use crate::UserErrorType;
8
9pub fn type_(id: &Id) -> Ident {
10    escape_id(id, NamingConvention::CamelCase)
11}
12
13pub fn builtin_type(b: BuiltinType) -> TokenStream {
14    match b {
15        BuiltinType::U8 { .. } => quote!(u8),
16        BuiltinType::U16 => quote!(u16),
17        BuiltinType::U32 { .. } => quote!(u32),
18        BuiltinType::U64 => quote!(u64),
19        BuiltinType::S8 => quote!(i8),
20        BuiltinType::S16 => quote!(i16),
21        BuiltinType::S32 => quote!(i32),
22        BuiltinType::S64 => quote!(i64),
23        BuiltinType::F32 => quote!(f32),
24        BuiltinType::F64 => quote!(f64),
25        BuiltinType::Char => quote!(char),
26    }
27}
28
29pub fn wasm_type(ty: WasmType) -> TokenStream {
30    match ty {
31        WasmType::I32 => quote!(i32),
32        WasmType::I64 => quote!(i64),
33        WasmType::F32 => quote!(f32),
34        WasmType::F64 => quote!(f64),
35    }
36}
37
38pub fn type_ref(tref: &TypeRef, lifetime: TokenStream) -> TokenStream {
39    match tref {
40        TypeRef::Name(nt) => {
41            let ident = type_(&nt.name);
42            quote!(#ident)
43        }
44        TypeRef::Value(ty) => match &**ty {
45            Type::Builtin(builtin) => builtin_type(*builtin),
46            Type::Pointer(pointee) | Type::ConstPointer(pointee) => {
47                let pointee_type = type_ref(&pointee, lifetime.clone());
48                quote!(wiggle::GuestPtr<#pointee_type>)
49            }
50            Type::List(pointee) => match &**pointee.type_() {
51                Type::Builtin(BuiltinType::Char) => {
52                    quote!(wiggle::GuestPtr<str>)
53                }
54                _ => {
55                    let pointee_type = type_ref(&pointee, lifetime.clone());
56                    quote!(wiggle::GuestPtr<[#pointee_type]>)
57                }
58            },
59            Type::Variant(v) => match v.as_expected() {
60                Some((ok, err)) => {
61                    let ok = match ok {
62                        Some(ty) => type_ref(ty, lifetime.clone()),
63                        None => quote!(()),
64                    };
65                    let err = match err {
66                        Some(ty) => type_ref(ty, lifetime.clone()),
67                        None => quote!(()),
68                    };
69                    quote!(Result<#ok, #err>)
70                }
71                None => unimplemented!("anonymous variant ref {:?}", tref),
72            },
73            Type::Record(r) if r.is_tuple() => {
74                let types = r
75                    .members
76                    .iter()
77                    .map(|m| type_ref(&m.tref, lifetime.clone()))
78                    .collect::<Vec<_>>();
79                quote!((#(#types,)*))
80            }
81            _ => unimplemented!("anonymous type ref {:?}", tref),
82        },
83    }
84}
85
86/// Convert an enum variant from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
87///
88/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
89/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
90pub fn enum_variant(id: &Id) -> Ident {
91    handle_2big_enum_variant(id).unwrap_or_else(|| escape_id(id, NamingConvention::CamelCase))
92}
93
94pub fn flag_member(id: &Id) -> Ident {
95    format_ident!("{}", id.as_str().to_shouty_snake_case())
96}
97
98pub fn int_member(id: &Id) -> Ident {
99    format_ident!("{}", id.as_str().to_shouty_snake_case())
100}
101
102/// Convert a struct member from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
103///
104/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
105/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
106pub fn struct_member(id: &Id) -> Ident {
107    escape_id(id, NamingConvention::SnakeCase)
108}
109
110/// Convert a module name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
111///
112/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
113/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
114pub fn module(id: &Id) -> Ident {
115    escape_id(id, NamingConvention::SnakeCase)
116}
117
118/// Convert a trait name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
119///
120/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
121/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
122pub fn trait_name(id: &Id) -> Ident {
123    escape_id(id, NamingConvention::CamelCase)
124}
125
126/// Convert a function name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
127///
128/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
129/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
130pub fn func(id: &Id) -> Ident {
131    escape_id(id, NamingConvention::SnakeCase)
132}
133
134/// Convert a parameter name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
135///
136/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
137/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
138pub fn func_param(id: &Id) -> Ident {
139    escape_id(id, NamingConvention::SnakeCase)
140}
141
142/// For when you need a {name}_ptr binding for passing a value by reference:
143pub fn func_ptr_binding(id: &Id) -> Ident {
144    format_ident!("{}_ptr", id.as_str().to_snake_case())
145}
146
147/// For when you need a {name}_len binding for passing an array:
148pub fn func_len_binding(id: &Id) -> Ident {
149    format_ident!("{}_len", id.as_str().to_snake_case())
150}
151
152fn builtin_name(b: &BuiltinType) -> &'static str {
153    match b {
154        BuiltinType::U8 { .. } => "u8",
155        BuiltinType::U16 => "u16",
156        BuiltinType::U32 { .. } => "u32",
157        BuiltinType::U64 => "u64",
158        BuiltinType::S8 => "i8",
159        BuiltinType::S16 => "i16",
160        BuiltinType::S32 => "i32",
161        BuiltinType::S64 => "i64",
162        BuiltinType::F32 => "f32",
163        BuiltinType::F64 => "f64",
164        BuiltinType::Char => "char",
165    }
166}
167
168fn snake_typename(tref: &TypeRef) -> String {
169    match tref {
170        TypeRef::Name(nt) => nt.name.as_str().to_snake_case(),
171        TypeRef::Value(ty) => match &**ty {
172            Type::Builtin(b) => builtin_name(&b).to_owned(),
173            _ => panic!("unexpected anonymous type: {ty:?}"),
174        },
175    }
176}
177
178pub fn user_error_conversion_method(user_type: &UserErrorType) -> Ident {
179    let abi_type = snake_typename(&user_type.abi_type());
180    format_ident!(
181        "{}_from_{}",
182        abi_type,
183        user_type.method_fragment().to_snake_case()
184    )
185}
186
187/// Identifier escaping utilities.
188///
189/// This module most importantly exports an `escape_id` function that can be used to properly
190/// escape tokens that conflict with strict and reserved keywords, as of Rust's 2018 edition.
191///
192/// Weak keywords are not included as their semantic rules do not have the same implications as
193/// those of strict and reserved keywords. `union` for example, is permitted as the name of a
194/// variable. `dyn` was promoted to a strict keyword beginning in the 2018 edition.
195mod escaping {
196    use {
197        heck::{ToSnakeCase, ToUpperCamelCase},
198        proc_macro2::Ident,
199        quote::format_ident,
200        witx::Id,
201    };
202
203    /// Identifier naming convention.
204    ///
205    /// Because shouty snake case values (identifiers that look `LIKE_THIS`) cannot potentially
206    /// conflict with any Rust keywords, this enum only include snake and camel case variants.
207    pub enum NamingConvention {
208        /// Snake case. Used to denote values `LikeThis`.
209        CamelCase,
210        /// Snake case. Used to denote values `like_this`.
211        SnakeCase,
212    }
213
214    /// Given a witx [`Id`][witx] and a [`NamingConvention`][naming], return a [`Ident`] word of
215    /// Rust syntax that accounts for escaping both strict and reserved keywords. If an identifier
216    /// would have conflicted with a keyword, a trailing underscode will be appended.
217    ///
218    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
219    /// [naming]: enum.NamingConvention.html
220    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
221    pub fn escape_id(id: &Id, conv: NamingConvention) -> Ident {
222        use NamingConvention::{CamelCase, SnakeCase};
223        match (conv, id.as_str()) {
224            // For camel-cased identifiers, `Self` is the only potential keyword conflict.
225            (CamelCase, "self") => format_ident!("Self_"),
226            (CamelCase, s) => format_ident!("{}", s.to_upper_camel_case()),
227            // Snake-cased identifiers are where the bulk of conflicts can occur.
228            (SnakeCase, s) => {
229                let s = s.to_snake_case();
230                if STRICT.iter().chain(RESERVED).any(|k| *k == s) {
231                    // If the camel-cased string matched any strict or reserved keywords, then
232                    // append a trailing underscore to the identifier we generate.
233                    format_ident!("{}_", s)
234                } else {
235                    format_ident!("{}", s) // Otherwise, use the string as is.
236                }
237            }
238        }
239    }
240
241    /// Strict keywords.
242    ///
243    /// >  Strict keywords cannot be used as the names of:
244    /// >    * Items
245    /// >    * Variables and function parameters
246    /// >    * Fields and variants
247    /// >    * Type parameters
248    /// >    * Lifetime parameters or loop labels
249    /// >    * Macros or attributes
250    /// >    * Macro placeholders
251    /// >    * Crates
252    /// >
253    /// > - <cite>[The Rust Reference][ref]</cite>
254    ///
255    /// This list also includes keywords that were introduced in the 2018 edition of Rust.
256    ///
257    /// [ref]: https://doc.rust-lang.org/reference/keywords.html#strict-keywords
258    const STRICT: &[&str] = &[
259        "as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum",
260        "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move",
261        "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super", "trait",
262        "true", "type", "unsafe", "use", "where", "while",
263    ];
264
265    /// Reserved keywords.
266    ///
267    /// > These keywords aren't used yet, but they are reserved for future use. They have the same
268    /// > restrictions as strict keywords. The reasoning behind this is to make current programs
269    /// > forward compatible with future versions of Rust by forbidding them to use these keywords.
270    /// >
271    /// > - <cite>[The Rust Reference][ref]</cite>
272    ///
273    /// This list also includes keywords that were introduced in the 2018 edition of Rust.
274    ///
275    /// [ref]: https://doc.rust-lang.org/reference/keywords.html#reserved-keywords
276    const RESERVED: &[&str] = &[
277        "abstract", "become", "box", "do", "final", "macro", "override", "priv", "try", "typeof",
278        "unsized", "virtual", "yield",
279    ];
280
281    /// Handle WASI's [`errno::2big`][err] variant.
282    ///
283    /// This is an unfortunate edge case that must account for when generating `enum` variants.
284    /// This will only return `Some(_)` if the given witx identifier *is* `2big`, otherwise this
285    /// function will return `None`.
286    ///
287    /// This functionality is a short-term fix that keeps WASI working. Instead of expanding these sort of special cases,
288    /// we should replace this function by having the user provide a mapping of witx identifiers to Rust identifiers in the
289    /// arguments to the macro.
290    ///
291    /// [err]: https://github.com/WebAssembly/WASI/blob/master/phases/snapshot/docs.md#-errno-enumu16
292    pub fn handle_2big_enum_variant(id: &Id) -> Option<Ident> {
293        if id.as_str() == "2big" {
294            Some(format_ident!("TooBig"))
295        } else {
296            None
297        }
298    }
299}