diff options
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_macros/src/symbols.rs | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/compiler/rustc_macros/src/symbols.rs b/compiler/rustc_macros/src/symbols.rs new file mode 100644 index 000000000..1b245f2a7 --- /dev/null +++ b/compiler/rustc_macros/src/symbols.rs @@ -0,0 +1,236 @@ +//! 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(crate) 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. +} |