]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_macros/src/symbols.rs
Auto merge of #106503 - cjgillot:remap-nofilter, r=oli-obk
[rust.git] / compiler / rustc_macros / src / 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 //! cargo expand > /tmp/rustc_span.rs   # it's a big file
23 //! ```
24
25 use proc_macro2::{Span, TokenStream};
26 use quote::quote;
27 use std::collections::HashMap;
28 use syn::parse::{Parse, ParseStream, Result};
29 use syn::{braced, punctuated::Punctuated, Ident, LitStr, Token};
30
31 #[cfg(test)]
32 mod tests;
33
34 mod kw {
35     syn::custom_keyword!(Keywords);
36     syn::custom_keyword!(Symbols);
37 }
38
39 struct Keyword {
40     name: Ident,
41     value: LitStr,
42 }
43
44 impl Parse for Keyword {
45     fn parse(input: ParseStream<'_>) -> Result<Self> {
46         let name = input.parse()?;
47         input.parse::<Token![:]>()?;
48         let value = input.parse()?;
49
50         Ok(Keyword { name, value })
51     }
52 }
53
54 struct Symbol {
55     name: Ident,
56     value: Option<LitStr>,
57 }
58
59 impl Parse for Symbol {
60     fn parse(input: ParseStream<'_>) -> Result<Self> {
61         let name = input.parse()?;
62         let value = match input.parse::<Token![:]>() {
63             Ok(_) => Some(input.parse()?),
64             Err(_) => None,
65         };
66
67         Ok(Symbol { name, value })
68     }
69 }
70
71 struct Input {
72     keywords: Punctuated<Keyword, Token![,]>,
73     symbols: Punctuated<Symbol, Token![,]>,
74 }
75
76 impl Parse for Input {
77     fn parse(input: ParseStream<'_>) -> Result<Self> {
78         input.parse::<kw::Keywords>()?;
79         let content;
80         braced!(content in input);
81         let keywords = Punctuated::parse_terminated(&content)?;
82
83         input.parse::<kw::Symbols>()?;
84         let content;
85         braced!(content in input);
86         let symbols = Punctuated::parse_terminated(&content)?;
87
88         Ok(Input { keywords, symbols })
89     }
90 }
91
92 #[derive(Default)]
93 struct Errors {
94     list: Vec<syn::Error>,
95 }
96
97 impl Errors {
98     fn error(&mut self, span: Span, message: String) {
99         self.list.push(syn::Error::new(span, message));
100     }
101 }
102
103 pub fn symbols(input: TokenStream) -> TokenStream {
104     let (mut output, errors) = symbols_with_errors(input);
105
106     // If we generated any errors, then report them as compiler_error!() macro calls.
107     // This lets the errors point back to the most relevant span. It also allows us
108     // to report as many errors as we can during a single run.
109     output.extend(errors.into_iter().map(|e| e.to_compile_error()));
110
111     output
112 }
113
114 fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
115     let mut errors = Errors::default();
116
117     let input: Input = match syn::parse2(input) {
118         Ok(input) => input,
119         Err(e) => {
120             // This allows us to display errors at the proper span, while minimizing
121             // unrelated errors caused by bailing out (and not generating code).
122             errors.list.push(e);
123             Input { keywords: Default::default(), symbols: Default::default() }
124         }
125     };
126
127     let mut keyword_stream = quote! {};
128     let mut symbols_stream = quote! {};
129     let mut prefill_stream = quote! {};
130     let mut counter = 0u32;
131     let mut keys =
132         HashMap::<String, Span>::with_capacity(input.keywords.len() + input.symbols.len() + 10);
133     let mut prev_key: Option<(Span, String)> = None;
134
135     let mut check_dup = |span: Span, str: &str, errors: &mut Errors| {
136         if let Some(prev_span) = keys.get(str) {
137             errors.error(span, format!("Symbol `{str}` is duplicated"));
138             errors.error(*prev_span, "location of previous definition".to_string());
139         } else {
140             keys.insert(str.to_string(), span);
141         }
142     };
143
144     let mut check_order = |span: Span, str: &str, errors: &mut Errors| {
145         if let Some((prev_span, ref prev_str)) = prev_key {
146             if str < prev_str {
147                 errors.error(span, format!("Symbol `{str}` must precede `{prev_str}`"));
148                 errors.error(prev_span, format!("location of previous symbol `{prev_str}`"));
149             }
150         }
151         prev_key = Some((span, str.to_string()));
152     };
153
154     // Generate the listed keywords.
155     for keyword in input.keywords.iter() {
156         let name = &keyword.name;
157         let value = &keyword.value;
158         let value_string = value.value();
159         check_dup(keyword.name.span(), &value_string, &mut errors);
160         prefill_stream.extend(quote! {
161             #value,
162         });
163         keyword_stream.extend(quote! {
164             pub const #name: Symbol = Symbol::new(#counter);
165         });
166         counter += 1;
167     }
168
169     // Generate the listed symbols.
170     for symbol in input.symbols.iter() {
171         let name = &symbol.name;
172         let value = match &symbol.value {
173             Some(value) => value.value(),
174             None => name.to_string(),
175         };
176         check_dup(symbol.name.span(), &value, &mut errors);
177         check_order(symbol.name.span(), &name.to_string(), &mut errors);
178
179         prefill_stream.extend(quote! {
180             #value,
181         });
182         symbols_stream.extend(quote! {
183             pub const #name: Symbol = Symbol::new(#counter);
184         });
185         counter += 1;
186     }
187
188     // Generate symbols for the strings "0", "1", ..., "9".
189     let digits_base = counter;
190     counter += 10;
191     for n in 0..10 {
192         let n = n.to_string();
193         check_dup(Span::call_site(), &n, &mut errors);
194         prefill_stream.extend(quote! {
195             #n,
196         });
197     }
198
199     let output = quote! {
200         const SYMBOL_DIGITS_BASE: u32 = #digits_base;
201         const PREINTERNED_SYMBOLS_COUNT: u32 = #counter;
202
203         #[doc(hidden)]
204         #[allow(non_upper_case_globals)]
205         mod kw_generated {
206             use super::Symbol;
207             #keyword_stream
208         }
209
210         #[allow(non_upper_case_globals)]
211         #[doc(hidden)]
212         pub mod sym_generated {
213             use super::Symbol;
214             #symbols_stream
215         }
216
217         impl Interner {
218             pub(crate) fn fresh() -> Self {
219                 Interner::prefill(&[
220                     #prefill_stream
221                 ])
222             }
223         }
224     };
225
226     (output, errors.list)
227
228     // To see the generated code, use the "cargo expand" command.
229     // Do this once to install:
230     //      cargo install cargo-expand
231     //
232     // Then, cd to rustc_span and run:
233     //      cargo expand > /tmp/rustc_span_expanded.rs
234     //
235     // and read that file.
236 }