wasm_bindgen_backend/
util.rs

1//! Common utility function for manipulating syn types and
2//! handling parsed values
3
4use std::collections::hash_map::DefaultHasher;
5use std::env;
6use std::fmt;
7use std::hash::{Hash, Hasher};
8use std::iter::FromIterator;
9use std::sync::atomic::AtomicBool;
10use std::sync::atomic::AtomicUsize;
11use std::sync::atomic::Ordering::SeqCst;
12
13use crate::ast;
14use proc_macro2::{self, Ident};
15
16/// Check whether a given `&str` is a Rust keyword
17#[rustfmt::skip]
18fn is_rust_keyword(name: &str) -> bool {
19    matches!(name,
20        "abstract" | "alignof" | "as" | "become" | "box" | "break" | "const" | "continue"
21        | "crate" | "do" | "else" | "enum" | "extern" | "false" | "final" | "fn" | "for" | "if"
22        | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" | "mut"
23        | "offsetof" | "override" | "priv" | "proc" | "pub" | "pure" | "ref" | "return"
24        | "Self" | "self" | "sizeof" | "static" | "struct" | "super" | "trait" | "true"
25        | "type" | "typeof" | "unsafe" | "unsized" | "use" | "virtual" | "where" | "while"
26        | "yield" | "bool" | "_"
27    )
28}
29
30/// Create an `Ident`, possibly mangling it if it conflicts with a Rust keyword.
31pub fn rust_ident(name: &str) -> Ident {
32    if name.is_empty() {
33        panic!("tried to create empty Ident (from \"\")");
34    } else if is_rust_keyword(name) {
35        Ident::new(&format!("{}_", name), proc_macro2::Span::call_site())
36
37    // we didn't historically have `async` in the `is_rust_keyword` list above,
38    // so for backwards compatibility reasons we need to generate an `async`
39    // identifier as well, but we'll be sure to use a raw identifier to ease
40    // compatibility with the 2018 edition.
41    //
42    // Note, though, that `proc-macro` doesn't support a normal way to create a
43    // raw identifier. To get around that we do some wonky parsing to
44    // roundaboutly create one.
45    } else if name == "async" {
46        let ident = "r#async"
47            .parse::<proc_macro2::TokenStream>()
48            .unwrap()
49            .into_iter()
50            .next()
51            .unwrap();
52        match ident {
53            proc_macro2::TokenTree::Ident(i) => i,
54            _ => unreachable!(),
55        }
56    } else if name.chars().next().unwrap().is_ascii_digit() {
57        Ident::new(&format!("N{}", name), proc_macro2::Span::call_site())
58    } else {
59        raw_ident(name)
60    }
61}
62
63/// Create an `Ident` without checking to see if it conflicts with a Rust
64/// keyword.
65pub fn raw_ident(name: &str) -> Ident {
66    Ident::new(name, proc_macro2::Span::call_site())
67}
68
69/// Create a path type from the given segments. For example an iterator yielding
70/// the idents `[foo, bar, baz]` will result in the path type `foo::bar::baz`.
71pub fn simple_path_ty<I>(segments: I) -> syn::Type
72where
73    I: IntoIterator<Item = Ident>,
74{
75    path_ty(false, segments)
76}
77
78/// Create a global path type from the given segments. For example an iterator
79/// yielding the idents `[foo, bar, baz]` will result in the path type
80/// `::foo::bar::baz`.
81pub fn leading_colon_path_ty<I>(segments: I) -> syn::Type
82where
83    I: IntoIterator<Item = Ident>,
84{
85    path_ty(true, segments)
86}
87
88fn path_ty<I>(leading_colon: bool, segments: I) -> syn::Type
89where
90    I: IntoIterator<Item = Ident>,
91{
92    let segments: Vec<_> = segments
93        .into_iter()
94        .map(|i| syn::PathSegment {
95            ident: i,
96            arguments: syn::PathArguments::None,
97        })
98        .collect();
99
100    syn::TypePath {
101        qself: None,
102        path: syn::Path {
103            leading_colon: if leading_colon {
104                Some(Default::default())
105            } else {
106                None
107            },
108            segments: syn::punctuated::Punctuated::from_iter(segments),
109        },
110    }
111    .into()
112}
113
114/// Create a path type with a single segment from a given Identifier
115pub fn ident_ty(ident: Ident) -> syn::Type {
116    simple_path_ty(Some(ident))
117}
118
119/// Convert an ImportFunction into the more generic Import type, wrapping the provided function
120pub fn wrap_import_function(function: ast::ImportFunction) -> ast::Import {
121    ast::Import {
122        module: None,
123        js_namespace: None,
124        kind: ast::ImportKind::Function(function),
125    }
126}
127
128/// Small utility used when generating symbol names.
129///
130/// Hashes the public field here along with a few cargo-set env vars to
131/// distinguish between runs of the procedural macro.
132#[derive(Debug)]
133pub struct ShortHash<T>(pub T);
134
135impl<T: Hash> fmt::Display for ShortHash<T> {
136    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137        static HASHED: AtomicBool = AtomicBool::new(false);
138        static HASH: AtomicUsize = AtomicUsize::new(0);
139
140        // Try to amortize the cost of loading env vars a lot as we're gonna be
141        // hashing for a lot of symbols.
142        if !HASHED.load(SeqCst) {
143            let mut h = DefaultHasher::new();
144            env::var("CARGO_PKG_NAME")
145                .expect("should have CARGO_PKG_NAME env var")
146                .hash(&mut h);
147            env::var("CARGO_PKG_VERSION")
148                .expect("should have CARGO_PKG_VERSION env var")
149                .hash(&mut h);
150            // This may chop off 32 bits on 32-bit platforms, but that's ok, we
151            // just want something to mix in below anyway.
152            HASH.store(h.finish() as usize, SeqCst);
153            HASHED.store(true, SeqCst);
154        }
155
156        let mut h = DefaultHasher::new();
157        HASH.load(SeqCst).hash(&mut h);
158        self.0.hash(&mut h);
159        write!(f, "{:016x}", h.finish())
160    }
161}