summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros/src/symbols.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compiler/rustc_macros/src/symbols.rs236
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.
+}