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}