]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_macros/src/symbols.rs
New upstream version 1.57.0+dfsg1
[rustc.git] / compiler / rustc_macros / src / symbols.rs
CommitLineData
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
25use proc_macro2::{Span, TokenStream};
48663c56 26use quote::quote;
fc512014 27use std::collections::HashMap;
dfeec247 28use syn::parse::{Parse, ParseStream, Result};
fc512014
XL
29use syn::{braced, punctuated::Punctuated, Ident, LitStr, Token};
30
31#[cfg(test)]
32mod tests;
48663c56 33
48663c56
XL
34mod kw {
35 syn::custom_keyword!(Keywords);
36 syn::custom_keyword!(Symbols);
37}
38
39struct Keyword {
40 name: Ident,
41 value: LitStr,
42}
43
44impl 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
54struct Symbol {
55 name: Ident,
56 value: Option<LitStr>,
57}
58
59impl 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 71struct Input {
fc512014
XL
72 keywords: Punctuated<Keyword, Token![,]>,
73 symbols: Punctuated<Symbol, Token![,]>,
48663c56
XL
74}
75
76impl 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)]
93struct Errors {
94 list: Vec<syn::Error>,
95}
96
97impl Errors {
98 fn error(&mut self, span: Span, message: String) {
99 self.list.push(syn::Error::new(span, message));
100 }
101}
102
48663c56 103pub 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
114fn 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}