| //! Proc macro which builds the Symbol table |
| //! |
| //! # Debugging |
| //! |
| //! Since this proc-macro does some non-trivial work, debugging it is important. |
| //! This proc-macro can be invoked as an ordinary unit test, like so: |
| //! |
| //! ```bash |
| //! cd compiler/rustc_macros |
| //! cargo test symbols::test_symbols -- --nocapture |
| //! ``` |
| //! |
| //! This unit test finds the `symbols!` invocation in `compiler/rustc_span/src/symbol.rs` |
| //! and runs it. It verifies that the output token stream can be parsed as valid module |
| //! items and that no errors were produced. |
| //! |
| //! You can also view the generated code by using `cargo expand`: |
| //! |
| //! ```bash |
| //! cargo install cargo-expand # this is necessary only once |
| //! cd compiler/rustc_span |
| //! cargo expand > /tmp/rustc_span.rs # it's a big file |
| //! ``` |
| |
| use proc_macro2::{Span, TokenStream}; |
| use quote::quote; |
| use std::collections::HashMap; |
| use syn::parse::{Parse, ParseStream, Result}; |
| use syn::{braced, punctuated::Punctuated, Ident, LitStr, Token}; |
| |
| #[cfg(test)] |
| mod tests; |
| |
| mod kw { |
| syn::custom_keyword!(Keywords); |
| syn::custom_keyword!(Symbols); |
| } |
| |
| struct Keyword { |
| name: Ident, |
| value: LitStr, |
| } |
| |
| impl Parse for Keyword { |
| fn parse(input: ParseStream<'_>) -> Result<Self> { |
| let name = input.parse()?; |
| input.parse::<Token![:]>()?; |
| let value = input.parse()?; |
| |
| Ok(Keyword { name, value }) |
| } |
| } |
| |
| struct Symbol { |
| name: Ident, |
| value: Option<LitStr>, |
| } |
| |
| impl Parse for Symbol { |
| fn parse(input: ParseStream<'_>) -> Result<Self> { |
| let name = input.parse()?; |
| let value = match input.parse::<Token![:]>() { |
| Ok(_) => Some(input.parse()?), |
| Err(_) => None, |
| }; |
| |
| Ok(Symbol { name, value }) |
| } |
| } |
| |
| struct Input { |
| keywords: Punctuated<Keyword, Token![,]>, |
| symbols: Punctuated<Symbol, Token![,]>, |
| } |
| |
| impl Parse for Input { |
| fn parse(input: ParseStream<'_>) -> Result<Self> { |
| input.parse::<kw::Keywords>()?; |
| let content; |
| braced!(content in input); |
| let keywords = Punctuated::parse_terminated(&content)?; |
| |
| input.parse::<kw::Symbols>()?; |
| let content; |
| braced!(content in input); |
| let symbols = Punctuated::parse_terminated(&content)?; |
| |
| Ok(Input { keywords, symbols }) |
| } |
| } |
| |
| #[derive(Default)] |
| struct Errors { |
| list: Vec<syn::Error>, |
| } |
| |
| impl Errors { |
| fn error(&mut self, span: Span, message: String) { |
| self.list.push(syn::Error::new(span, message)); |
| } |
| } |
| |
| pub fn symbols(input: TokenStream) -> TokenStream { |
| let (mut output, errors) = symbols_with_errors(input); |
| |
| // If we generated any errors, then report them as compiler_error!() macro calls. |
| // This lets the errors point back to the most relevant span. It also allows us |
| // to report as many errors as we can during a single run. |
| output.extend(errors.into_iter().map(|e| e.to_compile_error())); |
| |
| output |
| } |
| |
| fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) { |
| let mut errors = Errors::default(); |
| |
| let input: Input = match syn::parse2(input) { |
| Ok(input) => input, |
| Err(e) => { |
| // This allows us to display errors at the proper span, while minimizing |
| // unrelated errors caused by bailing out (and not generating code). |
| errors.list.push(e); |
| Input { keywords: Default::default(), symbols: Default::default() } |
| } |
| }; |
| |
| let mut keyword_stream = quote! {}; |
| let mut symbols_stream = quote! {}; |
| let mut prefill_stream = quote! {}; |
| let mut counter = 0u32; |
| let mut keys = |
| HashMap::<String, Span>::with_capacity(input.keywords.len() + input.symbols.len() + 10); |
| let mut prev_key: Option<(Span, String)> = None; |
| |
| let mut check_dup = |span: Span, str: &str, errors: &mut Errors| { |
| if let Some(prev_span) = keys.get(str) { |
| errors.error(span, format!("Symbol `{}` is duplicated", str)); |
| errors.error(*prev_span, "location of previous definition".to_string()); |
| } else { |
| keys.insert(str.to_string(), span); |
| } |
| }; |
| |
| let mut check_order = |span: Span, str: &str, errors: &mut Errors| { |
| if let Some((prev_span, ref prev_str)) = prev_key { |
| if str < prev_str { |
| errors.error(span, format!("Symbol `{}` must precede `{}`", str, prev_str)); |
| errors.error(prev_span, format!("location of previous symbol `{}`", prev_str)); |
| } |
| } |
| prev_key = Some((span, str.to_string())); |
| }; |
| |
| // Generate the listed keywords. |
| for keyword in input.keywords.iter() { |
| let name = &keyword.name; |
| let value = &keyword.value; |
| let value_string = value.value(); |
| check_dup(keyword.name.span(), &value_string, &mut errors); |
| prefill_stream.extend(quote! { |
| #value, |
| }); |
| keyword_stream.extend(quote! { |
| pub const #name: Symbol = Symbol::new(#counter); |
| }); |
| counter += 1; |
| } |
| |
| // Generate the listed symbols. |
| for symbol in input.symbols.iter() { |
| let name = &symbol.name; |
| let value = match &symbol.value { |
| Some(value) => value.value(), |
| None => name.to_string(), |
| }; |
| check_dup(symbol.name.span(), &value, &mut errors); |
| check_order(symbol.name.span(), &name.to_string(), &mut errors); |
| |
| prefill_stream.extend(quote! { |
| #value, |
| }); |
| symbols_stream.extend(quote! { |
| pub const #name: Symbol = Symbol::new(#counter); |
| }); |
| counter += 1; |
| } |
| |
| // Generate symbols for the strings "0", "1", ..., "9". |
| let digits_base = counter; |
| counter += 10; |
| for n in 0..10 { |
| let n = n.to_string(); |
| check_dup(Span::call_site(), &n, &mut errors); |
| prefill_stream.extend(quote! { |
| #n, |
| }); |
| } |
| let _ = counter; // for future use |
| |
| let output = quote! { |
| const SYMBOL_DIGITS_BASE: u32 = #digits_base; |
| |
| #[doc(hidden)] |
| #[allow(non_upper_case_globals)] |
| mod kw_generated { |
| use super::Symbol; |
| #keyword_stream |
| } |
| |
| #[allow(non_upper_case_globals)] |
| #[doc(hidden)] |
| pub mod sym_generated { |
| use super::Symbol; |
| #symbols_stream |
| } |
| |
| impl Interner { |
| pub fn fresh() -> Self { |
| Interner::prefill(&[ |
| #prefill_stream |
| ]) |
| } |
| } |
| }; |
| |
| (output, errors.list) |
| |
| // To see the generated code, use the "cargo expand" command. |
| // Do this once to install: |
| // cargo install cargo-expand |
| // |
| // Then, cd to rustc_span and run: |
| // cargo expand > /tmp/rustc_span_expanded.rs |
| // |
| // and read that file. |
| } |