1 //! Proc macro which builds the Symbol table
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:
9 //! cd compiler/rustc_macros
10 //! cargo test symbols::test_symbols -- --nocapture
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.
17 //! You can also view the generated code by using `cargo expand`:
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
25 use proc_macro2
::{Span, TokenStream}
;
27 use std
::collections
::HashMap
;
28 use syn
::parse
::{Parse, ParseStream, Result}
;
29 use syn
::{braced, punctuated::Punctuated, Ident, LitStr, Token}
;
35 syn
::custom_keyword
!(Keywords
);
36 syn
::custom_keyword
!(Symbols
);
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()?
;
50 Ok(Keyword { name, value }
)
56 value
: Option
<LitStr
>,
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()?
),
67 Ok(Symbol { name, value }
)
72 keywords
: Punctuated
<Keyword
, Token
![,]>,
73 symbols
: Punctuated
<Symbol
, Token
![,]>,
76 impl Parse
for Input
{
77 fn parse(input
: ParseStream
<'_
>) -> Result
<Self> {
78 input
.parse
::<kw
::Keywords
>()?
;
80 braced
!(content
in input
);
81 let keywords
= Punctuated
::parse_terminated(&content
)?
;
83 input
.parse
::<kw
::Symbols
>()?
;
85 braced
!(content
in input
);
86 let symbols
= Punctuated
::parse_terminated(&content
)?
;
88 Ok(Input { keywords, symbols }
)
94 list
: Vec
<syn
::Error
>,
98 fn error(&mut self, span
: Span
, message
: String
) {
99 self.list
.push(syn
::Error
::new(span
, message
));
103 pub fn symbols(input
: TokenStream
) -> TokenStream
{
104 let (mut output
, errors
) = symbols_with_errors(input
);
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()));
114 fn symbols_with_errors(input
: TokenStream
) -> (TokenStream
, Vec
<syn
::Error
>) {
115 let mut errors
= Errors
::default();
117 let input
: Input
= match syn
::parse2(input
) {
120 // This allows us to display errors at the proper span, while minimizing
121 // unrelated errors caused by bailing out (and not generating code).
123 Input { keywords: Default::default(), symbols: Default::default() }
127 let mut keyword_stream
= quote
! {}
;
128 let mut symbols_stream
= quote
! {}
;
129 let mut prefill_stream
= quote
! {}
;
130 let mut counter
= 0u32;
132 HashMap
::<String
, Span
>::with_capacity(input
.keywords
.len() + input
.symbols
.len() + 10);
133 let mut prev_key
: Option
<(Span
, String
)> = None
;
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));
138 errors
.error(*prev_span
, "location of previous definition".to_string());
140 keys
.insert(str.to_string(), span
);
144 let mut check_order
= |span
: Span
, str: &str, errors
: &mut Errors
| {
145 if let Some((prev_span
, ref prev_str
)) = prev_key
{
147 errors
.error(span
, format
!("Symbol `{}` must precede `{}`", str, prev_str
));
148 errors
.error(prev_span
, format
!("location of previous symbol `{}`", prev_str
));
151 prev_key
= Some((span
, str.to_string()));
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
! {
163 keyword_stream
.extend(quote
! {
164 pub const #name: Symbol = Symbol::new(#counter);
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(),
176 check_dup(symbol
.name
.span(), &value
, &mut errors
);
177 check_order(symbol
.name
.span(), &name
.to_string(), &mut errors
);
179 prefill_stream
.extend(quote
! {
182 symbols_stream
.extend(quote
! {
183 pub const #name: Symbol = Symbol::new(#counter);
188 // Generate symbols for the strings "0", "1", ..., "9".
189 let digits_base
= counter
;
192 let n
= n
.to_string();
193 check_dup(Span
::call_site(), &n
, &mut errors
);
194 prefill_stream
.extend(quote
! {
199 let output
= quote
! {
200 const SYMBOL_DIGITS_BASE
: u32 = #digits_base;
201 const PREINTERNED_SYMBOLS_COUNT
: u32 = #counter;
204 #[allow(non_upper_case_globals)]
210 #[allow(non_upper_case_globals)]
212 pub mod sym_generated
{
218 pub(crate) fn fresh() -> Self {
226 (output
, errors
.list
)
228 // To see the generated code, use the "cargo expand" command.
229 // Do this once to install:
230 // cargo install cargo-expand
232 // Then, cd to rustc_span and run:
233 // cargo expand > /tmp/rustc_span_expanded.rs
235 // and read that file.