1 use escaping::{NamingConvention, escape_id, handle_2big_enum_variant};
2 use heck::{ToShoutySnakeCase, ToSnakeCase};
3 use proc_macro2::{Ident, TokenStream};
4 use quote::{format_ident, quote};
5 use witx::{BuiltinType, Id, Type, TypeRef, WasmType};
6 
7 use crate::UserErrorType;
8 
type_(id: &Id) -> Ident9 pub fn type_(id: &Id) -> Ident {
10     escape_id(id, NamingConvention::CamelCase)
11 }
12 
builtin_type(b: BuiltinType) -> TokenStream13 pub 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 
wasm_type(ty: WasmType) -> TokenStream29 pub 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 
type_ref(tref: &TypeRef, lifetime: TokenStream) -> TokenStream38 pub 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
enum_variant(id: &Id) -> Ident90 pub fn enum_variant(id: &Id) -> Ident {
91     handle_2big_enum_variant(id).unwrap_or_else(|| escape_id(id, NamingConvention::CamelCase))
92 }
93 
flag_member(id: &Id) -> Ident94 pub fn flag_member(id: &Id) -> Ident {
95     format_ident!("{}", id.as_str().to_shouty_snake_case())
96 }
97 
int_member(id: &Id) -> Ident98 pub 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
struct_member(id: &Id) -> Ident106 pub 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
module(id: &Id) -> Ident114 pub 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
trait_name(id: &Id) -> Ident122 pub 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
func(id: &Id) -> Ident130 pub 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
func_param(id: &Id) -> Ident138 pub 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:
func_ptr_binding(id: &Id) -> Ident143 pub 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:
func_len_binding(id: &Id) -> Ident148 pub fn func_len_binding(id: &Id) -> Ident {
149     format_ident!("{}_len", id.as_str().to_snake_case())
150 }
151 
builtin_name(b: &BuiltinType) -> &'static str152 fn 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 
snake_typename(tref: &TypeRef) -> String168 fn 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 
user_error_conversion_method(user_type: &UserErrorType) -> Ident178 pub 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.
195 mod 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
escape_id(id: &Id, conv: NamingConvention) -> Ident221     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
handle_2big_enum_variant(id: &Id) -> Option<Ident>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 }
300