rustc_macros/
symbols.rs

1//! Proc macro which builds the Symbol table
2//!
3//! # Debugging
4//!
5//! Since this proc-macro does some non-trivial work, debugging it is important.
6//! This proc-macro can be invoked as an ordinary unit test, like so:
7//!
8//! ```bash
9//! cd compiler/rustc_macros
10//! cargo test symbols::test_symbols -- --nocapture
11//! ```
12//!
13//! This unit test finds the `symbols!` invocation in `compiler/rustc_span/src/symbol.rs`
14//! and runs it. It verifies that the output token stream can be parsed as valid module
15//! items and that no errors were produced.
16//!
17//! You can also view the generated code by using `cargo expand`:
18//!
19//! ```bash
20//! cargo install cargo-expand          # this is necessary only once
21//! cd compiler/rustc_span
22//! # The specific version number in CFG_RELEASE doesn't matter.
23//! # The output is large.
24//! CFG_RELEASE="0.0.0" cargo +nightly expand > /tmp/rustc_span.rs
25//! ```
26
27use std::collections::HashMap;
28
29use proc_macro2::{Span, TokenStream};
30use quote::quote;
31use syn::parse::{Parse, ParseStream, Result};
32use syn::punctuated::Punctuated;
33use syn::{Expr, Ident, Lit, LitStr, Macro, Token, braced};
34
35#[cfg(test)]
36mod tests;
37
38mod kw {
39    syn::custom_keyword!(Keywords);
40    syn::custom_keyword!(Symbols);
41}
42
43struct Keyword {
44    name: Ident,
45    value: LitStr,
46}
47
48impl Parse for Keyword {
49    fn parse(input: ParseStream<'_>) -> Result<Self> {
50        let name = input.parse()?;
51        input.parse::<Token![:]>()?;
52        let value = input.parse()?;
53
54        Ok(Keyword { name, value })
55    }
56}
57
58struct Symbol {
59    name: Ident,
60    value: Value,
61}
62
63enum Value {
64    SameAsName,
65    String(LitStr),
66    Env(LitStr, Macro),
67    Unsupported(Expr),
68}
69
70impl Parse for Symbol {
71    fn parse(input: ParseStream<'_>) -> Result<Self> {
72        let name = input.parse()?;
73        let colon_token: Option<Token![:]> = input.parse()?;
74        let value = if colon_token.is_some() { input.parse()? } else { Value::SameAsName };
75
76        Ok(Symbol { name, value })
77    }
78}
79
80impl Parse for Value {
81    fn parse(input: ParseStream<'_>) -> Result<Self> {
82        let expr: Expr = input.parse()?;
83        match &expr {
84            Expr::Lit(expr) => {
85                if let Lit::Str(lit) = &expr.lit {
86                    return Ok(Value::String(lit.clone()));
87                }
88            }
89            Expr::Macro(expr) => {
90                if expr.mac.path.is_ident("env")
91                    && let Ok(lit) = expr.mac.parse_body()
92                {
93                    return Ok(Value::Env(lit, expr.mac.clone()));
94                }
95            }
96            _ => {}
97        }
98        Ok(Value::Unsupported(expr))
99    }
100}
101
102struct Input {
103    keywords: Punctuated<Keyword, Token![,]>,
104    symbols: Punctuated<Symbol, Token![,]>,
105}
106
107impl Parse for Input {
108    fn parse(input: ParseStream<'_>) -> Result<Self> {
109        input.parse::<kw::Keywords>()?;
110        let content;
111        braced!(content in input);
112        let keywords = Punctuated::parse_terminated(&content)?;
113
114        input.parse::<kw::Symbols>()?;
115        let content;
116        braced!(content in input);
117        let symbols = Punctuated::parse_terminated(&content)?;
118
119        Ok(Input { keywords, symbols })
120    }
121}
122
123#[derive(Default)]
124struct Errors {
125    list: Vec<syn::Error>,
126}
127
128impl Errors {
129    fn error(&mut self, span: Span, message: String) {
130        self.list.push(syn::Error::new(span, message));
131    }
132}
133
134pub(super) fn symbols(input: TokenStream) -> TokenStream {
135    let (mut output, errors) = symbols_with_errors(input);
136
137    // If we generated any errors, then report them as compiler_error!() macro calls.
138    // This lets the errors point back to the most relevant span. It also allows us
139    // to report as many errors as we can during a single run.
140    output.extend(errors.into_iter().map(|e| e.to_compile_error()));
141
142    output
143}
144
145struct Preinterned {
146    idx: u32,
147    span_of_name: Span,
148}
149
150struct Entries {
151    map: HashMap<String, Preinterned>,
152}
153
154impl Entries {
155    fn with_capacity(capacity: usize) -> Self {
156        Entries { map: HashMap::with_capacity(capacity) }
157    }
158
159    fn insert(&mut self, span: Span, s: &str, errors: &mut Errors) -> u32 {
160        if let Some(prev) = self.map.get(s) {
161            errors.error(span, format!("Symbol `{s}` is duplicated"));
162            errors.error(prev.span_of_name, "location of previous definition".to_string());
163            prev.idx
164        } else {
165            let idx = self.len();
166            self.map.insert(s.to_string(), Preinterned { idx, span_of_name: span });
167            idx
168        }
169    }
170
171    fn len(&self) -> u32 {
172        u32::try_from(self.map.len()).expect("way too many symbols")
173    }
174}
175
176fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
177    let mut errors = Errors::default();
178
179    let input: Input = match syn::parse2(input) {
180        Ok(input) => input,
181        Err(e) => {
182            // This allows us to display errors at the proper span, while minimizing
183            // unrelated errors caused by bailing out (and not generating code).
184            errors.list.push(e);
185            Input { keywords: Default::default(), symbols: Default::default() }
186        }
187    };
188
189    let mut keyword_stream = quote! {};
190    let mut symbols_stream = quote! {};
191    let mut prefill_stream = quote! {};
192    let mut entries = Entries::with_capacity(input.keywords.len() + input.symbols.len() + 10);
193    let mut prev_key: Option<(Span, String)> = None;
194
195    let mut check_order = |span: Span, s: &str, errors: &mut Errors| {
196        if let Some((prev_span, ref prev_str)) = prev_key {
197            if s < prev_str {
198                errors.error(span, format!("Symbol `{s}` must precede `{prev_str}`"));
199                errors.error(prev_span, format!("location of previous symbol `{prev_str}`"));
200            }
201        }
202        prev_key = Some((span, s.to_string()));
203    };
204
205    // Generate the listed keywords.
206    for keyword in input.keywords.iter() {
207        let name = &keyword.name;
208        let value = &keyword.value;
209        let value_string = value.value();
210        let idx = entries.insert(keyword.name.span(), &value_string, &mut errors);
211        prefill_stream.extend(quote! {
212            #value,
213        });
214        keyword_stream.extend(quote! {
215            pub const #name: Symbol = Symbol::new(#idx);
216        });
217    }
218
219    // Generate the listed symbols.
220    for symbol in input.symbols.iter() {
221        let name = &symbol.name;
222        check_order(symbol.name.span(), &name.to_string(), &mut errors);
223
224        let value = match &symbol.value {
225            Value::SameAsName => name.to_string(),
226            Value::String(lit) => lit.value(),
227            Value::Env(..) => continue, // in another loop below
228            Value::Unsupported(expr) => {
229                errors.list.push(syn::Error::new_spanned(
230                    expr,
231                    concat!(
232                        "unsupported expression for symbol value; implement support for this in ",
233                        file!(),
234                    ),
235                ));
236                continue;
237            }
238        };
239        let idx = entries.insert(symbol.name.span(), &value, &mut errors);
240
241        prefill_stream.extend(quote! {
242            #value,
243        });
244        symbols_stream.extend(quote! {
245            pub const #name: Symbol = Symbol::new(#idx);
246        });
247    }
248
249    // Generate symbols for the strings "0", "1", ..., "9".
250    for n in 0..10 {
251        let n = n.to_string();
252        entries.insert(Span::call_site(), &n, &mut errors);
253        prefill_stream.extend(quote! {
254            #n,
255        });
256    }
257
258    // Symbols whose value comes from an environment variable. It's allowed for
259    // these to have the same value as another symbol.
260    for symbol in &input.symbols {
261        let (env_var, expr) = match &symbol.value {
262            Value::Env(lit, expr) => (lit, expr),
263            Value::SameAsName | Value::String(_) | Value::Unsupported(_) => continue,
264        };
265
266        if !proc_macro::is_available() {
267            errors.error(
268                Span::call_site(),
269                "proc_macro::tracked_env is not available in unit test".to_owned(),
270            );
271            break;
272        }
273
274        let value = match proc_macro::tracked_env::var(env_var.value()) {
275            Ok(value) => value,
276            Err(err) => {
277                errors.list.push(syn::Error::new_spanned(expr, err));
278                continue;
279            }
280        };
281
282        let idx = if let Some(prev) = entries.map.get(&value) {
283            prev.idx
284        } else {
285            prefill_stream.extend(quote! {
286                #value,
287            });
288            entries.insert(symbol.name.span(), &value, &mut errors)
289        };
290
291        let name = &symbol.name;
292        symbols_stream.extend(quote! {
293            pub const #name: Symbol = Symbol::new(#idx);
294        });
295    }
296
297    let symbol_digits_base = entries.map["0"].idx;
298    let preinterned_symbols_count = entries.len();
299    let output = quote! {
300        const SYMBOL_DIGITS_BASE: u32 = #symbol_digits_base;
301        const PREINTERNED_SYMBOLS_COUNT: u32 = #preinterned_symbols_count;
302
303        #[doc(hidden)]
304        #[allow(non_upper_case_globals)]
305        mod kw_generated {
306            use super::Symbol;
307            #keyword_stream
308        }
309
310        #[allow(non_upper_case_globals)]
311        #[doc(hidden)]
312        pub mod sym_generated {
313            use super::Symbol;
314            #symbols_stream
315        }
316
317        impl Interner {
318            pub(crate) fn fresh() -> Self {
319                Interner::prefill(&[
320                    #prefill_stream
321                ])
322            }
323        }
324    };
325
326    (output, errors.list)
327}