]>
Commit | Line | Data |
---|---|---|
fc512014 XL |
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}; | |
48663c56 | 26 | use quote::quote; |
fc512014 | 27 | use std::collections::HashMap; |
dfeec247 | 28 | use syn::parse::{Parse, ParseStream, Result}; |
fc512014 XL |
29 | use syn::{braced, punctuated::Punctuated, Ident, LitStr, Token}; |
30 | ||
31 | #[cfg(test)] | |
32 | mod tests; | |
48663c56 | 33 | |
48663c56 XL |
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()?; | |
48663c56 | 49 | |
dfeec247 | 50 | Ok(Keyword { name, value }) |
48663c56 XL |
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 | }; | |
48663c56 | 66 | |
dfeec247 | 67 | Ok(Symbol { name, value }) |
48663c56 XL |
68 | } |
69 | } | |
70 | ||
48663c56 | 71 | struct Input { |
fc512014 XL |
72 | keywords: Punctuated<Keyword, Token![,]>, |
73 | symbols: Punctuated<Symbol, Token![,]>, | |
48663c56 XL |
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); | |
fc512014 | 81 | let keywords = Punctuated::parse_terminated(&content)?; |
48663c56 XL |
82 | |
83 | input.parse::<kw::Symbols>()?; | |
84 | let content; | |
85 | braced!(content in input); | |
fc512014 | 86 | let symbols = Punctuated::parse_terminated(&content)?; |
48663c56 | 87 | |
dfeec247 | 88 | Ok(Input { keywords, symbols }) |
48663c56 XL |
89 | } |
90 | } | |
91 | ||
fc512014 XL |
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 | ||
48663c56 | 103 | pub fn symbols(input: TokenStream) -> TokenStream { |
fc512014 XL |
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 | }; | |
48663c56 XL |
126 | |
127 | let mut keyword_stream = quote! {}; | |
128 | let mut symbols_stream = quote! {}; | |
129 | let mut prefill_stream = quote! {}; | |
48663c56 | 130 | let mut counter = 0u32; |
fc512014 XL |
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 `{}` is duplicated", str)); | |
94222f64 | 138 | errors.error(*prev_span, "location of previous definition".to_string()); |
fc512014 XL |
139 | } else { |
140 | keys.insert(str.to_string(), span); | |
48663c56 XL |
141 | } |
142 | }; | |
143 | ||
fc512014 XL |
144 | let mut check_order = |span: Span, str: &str, errors: &mut Errors| { |
145 | if let Some((prev_span, ref prev_str)) = prev_key { | |
3dfed10e | 146 | if str < prev_str { |
fc512014 XL |
147 | errors.error(span, format!("Symbol `{}` must precede `{}`", str, prev_str)); |
148 | errors.error(prev_span, format!("location of previous symbol `{}`", prev_str)); | |
3dfed10e XL |
149 | } |
150 | } | |
fc512014 | 151 | prev_key = Some((span, str.to_string())); |
3dfed10e XL |
152 | }; |
153 | ||
dc9dc135 | 154 | // Generate the listed keywords. |
fc512014 | 155 | for keyword in input.keywords.iter() { |
48663c56 XL |
156 | let name = &keyword.name; |
157 | let value = &keyword.value; | |
fc512014 XL |
158 | let value_string = value.value(); |
159 | check_dup(keyword.name.span(), &value_string, &mut errors); | |
48663c56 XL |
160 | prefill_stream.extend(quote! { |
161 | #value, | |
162 | }); | |
163 | keyword_stream.extend(quote! { | |
dc9dc135 | 164 | pub const #name: Symbol = Symbol::new(#counter); |
48663c56 XL |
165 | }); |
166 | counter += 1; | |
167 | } | |
168 | ||
dc9dc135 | 169 | // Generate the listed symbols. |
fc512014 | 170 | for symbol in input.symbols.iter() { |
48663c56 XL |
171 | let name = &symbol.name; |
172 | let value = match &symbol.value { | |
173 | Some(value) => value.value(), | |
174 | None => name.to_string(), | |
175 | }; | |
fc512014 XL |
176 | check_dup(symbol.name.span(), &value, &mut errors); |
177 | check_order(symbol.name.span(), &name.to_string(), &mut errors); | |
178 | ||
48663c56 XL |
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 | ||
dc9dc135 | 188 | // Generate symbols for the strings "0", "1", ..., "9". |
fc512014 XL |
189 | let digits_base = counter; |
190 | counter += 10; | |
dc9dc135 XL |
191 | for n in 0..10 { |
192 | let n = n.to_string(); | |
fc512014 | 193 | check_dup(Span::call_site(), &n, &mut errors); |
dc9dc135 XL |
194 | prefill_stream.extend(quote! { |
195 | #n, | |
196 | }); | |
dc9dc135 | 197 | } |
fc512014 | 198 | let _ = counter; // for future use |
dc9dc135 | 199 | |
fc512014 XL |
200 | let output = quote! { |
201 | const SYMBOL_DIGITS_BASE: u32 = #digits_base; | |
3dfed10e | 202 | |
fc512014 XL |
203 | #[doc(hidden)] |
204 | #[allow(non_upper_case_globals)] | |
205 | mod kw_generated { | |
206 | use super::Symbol; | |
207 | #keyword_stream | |
48663c56 XL |
208 | } |
209 | ||
fc512014 XL |
210 | #[allow(non_upper_case_globals)] |
211 | #[doc(hidden)] | |
212 | pub mod sym_generated { | |
213 | use super::Symbol; | |
214 | #symbols_stream | |
48663c56 XL |
215 | } |
216 | ||
217 | impl Interner { | |
c295e0f8 | 218 | pub(crate) fn fresh() -> Self { |
48663c56 XL |
219 | Interner::prefill(&[ |
220 | #prefill_stream | |
221 | ]) | |
222 | } | |
223 | } | |
fc512014 | 224 | }; |
48663c56 | 225 | |
fc512014 | 226 | (output, errors.list) |
48663c56 | 227 | |
fc512014 XL |
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. | |
48663c56 | 236 | } |