flux_macros/
symbols.rs

1//! This is based on the implementation of the `symbols` macro in rustc.
2
3use std::collections::HashMap;
4
5use proc_macro2::{Span, TokenStream};
6use quote::quote;
7use syn::{
8    Expr, Ident, Lit, LitStr, Result, Token, braced,
9    parse::{Parse, ParseStream},
10    punctuated::Punctuated,
11};
12
13pub(super) fn symbols(input: TokenStream) -> TokenStream {
14    let (mut output, errors) = symbols_with_errors(input);
15
16    // If we generated any errors, then report them as compiler_error!() macro calls.
17    // This lets the errors point back to the most relevant span. It also allows us
18    // to report as many errors as we can during a single run.
19    output.extend(errors.into_iter().map(|e| e.to_compile_error()));
20
21    output
22}
23
24fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
25    let mut errors = Errors::default();
26
27    let input: Input = match syn::parse2(input) {
28        Ok(input) => input,
29        Err(e) => {
30            errors.list.push(e);
31            return (TokenStream::default(), errors.list);
32        }
33    };
34
35    let mut prefill_stream = quote! {};
36    let mut entries = Entries::with_capacity(input.keywords.len());
37
38    // Generate the listed keywords.
39    let mut keyword_stream = quote! {};
40    for keyword in &input.keywords {
41        let name = &keyword.name;
42        let value = &keyword.value;
43        let value_string = value.value();
44        let idx = entries.insert(keyword.name.span(), &value_string, &mut errors);
45        prefill_stream.extend(quote! {
46            #value,
47        });
48        keyword_stream.extend(quote! {
49            pub const #name: Symbol = Symbol::new(rustc_span::symbol::PREDEFINED_SYMBOLS_COUNT + #idx);
50        });
51    }
52
53    // Generate the listed symbols.
54    let mut symbols_stream = quote! {};
55    let mut prev_key: Option<(Span, String)> = None;
56    let mut check_order = |span: Span, s: &str, errors: &mut Errors| {
57        if let Some((prev_span, ref prev_str)) = prev_key
58            && s < prev_str
59        {
60            errors.error(span, format!("Symbol `{s}` must precede `{prev_str}`"));
61            errors.error(prev_span, format!("location of previous symbol `{prev_str}`"));
62        }
63        prev_key = Some((span, s.to_string()));
64    };
65    for symbol in &input.symbols {
66        let name = &symbol.name;
67        check_order(symbol.name.span(), &name.to_string(), &mut errors);
68        let value = match &symbol.value {
69            Value::SameAsName => name.to_string(),
70            Value::String(lit) => lit.value(),
71            Value::Unsupported(expr) => {
72                errors.list.push(syn::Error::new_spanned(
73                    expr,
74                    concat!(
75                        "unsupported expression for symbol value; implement support for this in ",
76                        file!(),
77                    ),
78                ));
79                continue;
80            }
81        };
82        let idx = entries.insert(symbol.name.span(), &value, &mut errors);
83
84        prefill_stream.extend(quote! {
85            #value,
86        });
87        symbols_stream.extend(quote! {
88            pub const #name: Symbol = Symbol::new(rustc_span::symbol::PREDEFINED_SYMBOLS_COUNT + #idx);
89        });
90    }
91
92    let predefined_flux_symbols_count = entries.len();
93    let predefined_flux_symbols_count_usize = predefined_flux_symbols_count as usize;
94    let output = quote! {
95        pub const PREDEFINED_FLUX_SYMBOLS_COUNT: u32 = #predefined_flux_symbols_count;
96
97        #[doc(hidden)]
98        #[allow(non_upper_case_globals)]
99        mod kw_generated {
100            use rustc_span::Symbol;
101
102            #keyword_stream
103        }
104
105        #[doc(hidden)]
106        #[allow(non_upper_case_globals)]
107        pub mod sym_generated {
108            use rustc_span::Symbol;
109            #symbols_stream
110        }
111
112        pub const PREDEFINED_FLUX_SYMBOLS: [&'static str; #predefined_flux_symbols_count_usize] =
113            [#prefill_stream];
114    };
115    (output, errors.list)
116}
117
118struct Keyword {
119    name: Ident,
120    value: LitStr,
121}
122
123impl Parse for Keyword {
124    fn parse(input: ParseStream<'_>) -> Result<Self> {
125        let name = input.parse()?;
126        input.parse::<Token![:]>()?;
127        let value = input.parse()?;
128
129        Ok(Keyword { name, value })
130    }
131}
132
133struct Symbol {
134    name: Ident,
135    value: Value,
136}
137
138enum Value {
139    SameAsName,
140    String(LitStr),
141    Unsupported(Expr),
142}
143
144impl Parse for Symbol {
145    fn parse(input: ParseStream<'_>) -> Result<Self> {
146        let name = input.parse()?;
147        let colon_token: Option<Token![:]> = input.parse()?;
148        let value = if colon_token.is_some() { input.parse()? } else { Value::SameAsName };
149
150        Ok(Symbol { name, value })
151    }
152}
153
154impl Parse for Value {
155    fn parse(input: ParseStream<'_>) -> Result<Self> {
156        let expr: Expr = input.parse()?;
157        if let Expr::Lit(expr) = &expr
158            && let Lit::Str(lit) = &expr.lit
159        {
160            Ok(Value::String(lit.clone()))
161        } else {
162            Ok(Value::Unsupported(expr))
163        }
164    }
165}
166
167struct Input {
168    keywords: Punctuated<Keyword, Token![,]>,
169    symbols: Punctuated<Symbol, Token![,]>,
170}
171
172impl Parse for Input {
173    fn parse(input: ParseStream) -> Result<Self> {
174        input.parse::<kw::Keywords>()?;
175        let content;
176        braced!(content in input);
177        let keywords = Punctuated::parse_terminated(&content)?;
178
179        input.parse::<kw::Symbols>()?;
180        let content;
181        braced!(content in input);
182        let symbols = Punctuated::parse_terminated(&content)?;
183
184        Ok(Input { keywords, symbols })
185    }
186}
187
188mod kw {
189    syn::custom_keyword!(Keywords);
190    syn::custom_keyword!(Symbols);
191}
192
193#[derive(Default)]
194struct Errors {
195    list: Vec<syn::Error>,
196}
197
198impl Errors {
199    fn error(&mut self, span: Span, message: String) {
200        self.list.push(syn::Error::new(span, message));
201    }
202}
203
204struct Entries {
205    map: HashMap<String, Predefined>,
206}
207
208impl Entries {
209    fn with_capacity(capacity: usize) -> Self {
210        Entries { map: HashMap::with_capacity(capacity) }
211    }
212
213    fn insert(&mut self, span: Span, s: &str, errors: &mut Errors) -> u32 {
214        if let Some(prev) = self.map.get(s) {
215            errors.error(span, format!("Symbol `{s}` is duplicated"));
216            errors.error(prev.span_of_name, "location of previous definition".to_string());
217            prev.idx
218        } else {
219            let idx = self.len();
220            self.map
221                .insert(s.to_string(), Predefined { idx, span_of_name: span });
222            idx
223        }
224    }
225
226    fn len(&self) -> u32 {
227        u32::try_from(self.map.len()).expect("way too many symbols")
228    }
229}
230
231struct Predefined {
232    idx: u32,
233    span_of_name: Span,
234}