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(string: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(leading_colon: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(leading_colon: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<_> = segmentsimpl Iterator
93 .into_iter()
94 .map(|i: Ident| 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(segments: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}
162