rustc_ap_rustc_macros/
query.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Delimiter, TokenTree};
3use quote::quote;
4use syn::parse::{Parse, ParseStream, Result};
5use syn::punctuated::Punctuated;
6use syn::spanned::Spanned;
7use syn::{
8    braced, parenthesized, parse_macro_input, parse_quote, AttrStyle, Attribute, Block, Error,
9    Expr, Ident, ReturnType, Token, Type,
10};
11
12mod kw {
13    syn::custom_keyword!(query);
14}
15
16/// Ident or a wildcard `_`.
17struct IdentOrWild(Ident);
18
19impl Parse for IdentOrWild {
20    fn parse(input: ParseStream<'_>) -> Result<Self> {
21        Ok(if input.peek(Token![_]) {
22            let underscore = input.parse::<Token![_]>()?;
23            IdentOrWild(Ident::new("_", underscore.span()))
24        } else {
25            IdentOrWild(input.parse()?)
26        })
27    }
28}
29
30/// A modifier for a query
31enum QueryModifier {
32    /// The description of the query.
33    Desc(Option<Ident>, Punctuated<Expr, Token![,]>),
34
35    /// Use this type for the in-memory cache.
36    Storage(Type),
37
38    /// Cache the query to disk if the `Expr` returns true.
39    Cache(Option<(IdentOrWild, IdentOrWild)>, Block),
40
41    /// Custom code to load the query from disk.
42    LoadCached(Ident, Ident, Block),
43
44    /// A cycle error for this query aborting the compilation with a fatal error.
45    FatalCycle,
46
47    /// A cycle error results in a delay_bug call
48    CycleDelayBug,
49
50    /// Don't hash the result, instead just mark a query red if it runs
51    NoHash,
52
53    /// Generate a dep node based on the dependencies of the query
54    Anon,
55
56    /// Always evaluate the query, ignoring its dependencies
57    EvalAlways,
58}
59
60impl Parse for QueryModifier {
61    fn parse(input: ParseStream<'_>) -> Result<Self> {
62        let modifier: Ident = input.parse()?;
63        if modifier == "desc" {
64            // Parse a description modifier like:
65            // `desc { |tcx| "foo {}", tcx.item_path(key) }`
66            let attr_content;
67            braced!(attr_content in input);
68            let tcx = if attr_content.peek(Token![|]) {
69                attr_content.parse::<Token![|]>()?;
70                let tcx = attr_content.parse()?;
71                attr_content.parse::<Token![|]>()?;
72                Some(tcx)
73            } else {
74                None
75            };
76            let desc = attr_content.parse_terminated(Expr::parse)?;
77            Ok(QueryModifier::Desc(tcx, desc))
78        } else if modifier == "cache_on_disk_if" {
79            // Parse a cache modifier like:
80            // `cache(tcx, value) { |tcx| key.is_local() }`
81            let has_args = if let TokenTree::Group(group) = input.fork().parse()? {
82                group.delimiter() == Delimiter::Parenthesis
83            } else {
84                false
85            };
86            let args = if has_args {
87                let args;
88                parenthesized!(args in input);
89                let tcx = args.parse()?;
90                args.parse::<Token![,]>()?;
91                let value = args.parse()?;
92                Some((tcx, value))
93            } else {
94                None
95            };
96            let block = input.parse()?;
97            Ok(QueryModifier::Cache(args, block))
98        } else if modifier == "load_cached" {
99            // Parse a load_cached modifier like:
100            // `load_cached(tcx, id) { tcx.on_disk_cache.try_load_query_result(tcx, id) }`
101            let args;
102            parenthesized!(args in input);
103            let tcx = args.parse()?;
104            args.parse::<Token![,]>()?;
105            let id = args.parse()?;
106            let block = input.parse()?;
107            Ok(QueryModifier::LoadCached(tcx, id, block))
108        } else if modifier == "storage" {
109            let args;
110            parenthesized!(args in input);
111            let ty = args.parse()?;
112            Ok(QueryModifier::Storage(ty))
113        } else if modifier == "fatal_cycle" {
114            Ok(QueryModifier::FatalCycle)
115        } else if modifier == "cycle_delay_bug" {
116            Ok(QueryModifier::CycleDelayBug)
117        } else if modifier == "no_hash" {
118            Ok(QueryModifier::NoHash)
119        } else if modifier == "anon" {
120            Ok(QueryModifier::Anon)
121        } else if modifier == "eval_always" {
122            Ok(QueryModifier::EvalAlways)
123        } else {
124            Err(Error::new(modifier.span(), "unknown query modifier"))
125        }
126    }
127}
128
129/// Ensures only doc comment attributes are used
130fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
131    let inner = |attr: Attribute| {
132        if !attr.path.is_ident("doc") {
133            Err(Error::new(attr.span(), "attributes not supported on queries"))
134        } else if attr.style != AttrStyle::Outer {
135            Err(Error::new(
136                attr.span(),
137                "attributes must be outer attributes (`///`), not inner attributes",
138            ))
139        } else {
140            Ok(attr)
141        }
142    };
143    attrs.into_iter().map(inner).collect()
144}
145
146/// A compiler query. `query ... { ... }`
147struct Query {
148    doc_comments: Vec<Attribute>,
149    modifiers: List<QueryModifier>,
150    name: Ident,
151    key: IdentOrWild,
152    arg: Type,
153    result: ReturnType,
154}
155
156impl Parse for Query {
157    fn parse(input: ParseStream<'_>) -> Result<Self> {
158        let doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
159
160        // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>`
161        input.parse::<kw::query>()?;
162        let name: Ident = input.parse()?;
163        let arg_content;
164        parenthesized!(arg_content in input);
165        let key = arg_content.parse()?;
166        arg_content.parse::<Token![:]>()?;
167        let arg = arg_content.parse()?;
168        let result = input.parse()?;
169
170        // Parse the query modifiers
171        let content;
172        braced!(content in input);
173        let modifiers = content.parse()?;
174
175        Ok(Query { doc_comments, modifiers, name, key, arg, result })
176    }
177}
178
179/// A type used to greedily parse another type until the input is empty.
180struct List<T>(Vec<T>);
181
182impl<T: Parse> Parse for List<T> {
183    fn parse(input: ParseStream<'_>) -> Result<Self> {
184        let mut list = Vec::new();
185        while !input.is_empty() {
186            list.push(input.parse()?);
187        }
188        Ok(List(list))
189    }
190}
191
192struct QueryModifiers {
193    /// The description of the query.
194    desc: (Option<Ident>, Punctuated<Expr, Token![,]>),
195
196    /// Use this type for the in-memory cache.
197    storage: Option<Type>,
198
199    /// Cache the query to disk if the `Block` returns true.
200    cache: Option<(Option<(IdentOrWild, IdentOrWild)>, Block)>,
201
202    /// Custom code to load the query from disk.
203    load_cached: Option<(Ident, Ident, Block)>,
204
205    /// A cycle error for this query aborting the compilation with a fatal error.
206    fatal_cycle: bool,
207
208    /// A cycle error results in a delay_bug call
209    cycle_delay_bug: bool,
210
211    /// Don't hash the result, instead just mark a query red if it runs
212    no_hash: bool,
213
214    /// Generate a dep node based on the dependencies of the query
215    anon: bool,
216
217    // Always evaluate the query, ignoring its dependencies
218    eval_always: bool,
219}
220
221/// Process query modifiers into a struct, erroring on duplicates
222fn process_modifiers(query: &mut Query) -> QueryModifiers {
223    let mut load_cached = None;
224    let mut storage = None;
225    let mut cache = None;
226    let mut desc = None;
227    let mut fatal_cycle = false;
228    let mut cycle_delay_bug = false;
229    let mut no_hash = false;
230    let mut anon = false;
231    let mut eval_always = false;
232    for modifier in query.modifiers.0.drain(..) {
233        match modifier {
234            QueryModifier::LoadCached(tcx, id, block) => {
235                if load_cached.is_some() {
236                    panic!("duplicate modifier `load_cached` for query `{}`", query.name);
237                }
238                load_cached = Some((tcx, id, block));
239            }
240            QueryModifier::Storage(ty) => {
241                if storage.is_some() {
242                    panic!("duplicate modifier `storage` for query `{}`", query.name);
243                }
244                storage = Some(ty);
245            }
246            QueryModifier::Cache(args, expr) => {
247                if cache.is_some() {
248                    panic!("duplicate modifier `cache` for query `{}`", query.name);
249                }
250                cache = Some((args, expr));
251            }
252            QueryModifier::Desc(tcx, list) => {
253                if desc.is_some() {
254                    panic!("duplicate modifier `desc` for query `{}`", query.name);
255                }
256                // If there are no doc-comments, give at least some idea of what
257                // it does by showing the query description.
258                if query.doc_comments.is_empty() {
259                    use ::syn::*;
260                    let mut list = list.iter();
261                    let format_str: String = match list.next() {
262                        Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
263                            lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
264                        }
265                        _ => panic!("Expected a string literal"),
266                    };
267                    let mut fmt_fragments = format_str.split("{}");
268                    let mut doc_string = fmt_fragments.next().unwrap().to_string();
269                    list.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
270                        |(tts, next_fmt_fragment)| {
271                            use ::core::fmt::Write;
272                            write!(
273                                &mut doc_string,
274                                " `{}` {}",
275                                tts.to_string().replace(" . ", "."),
276                                next_fmt_fragment,
277                            )
278                            .unwrap();
279                        },
280                    );
281                    let doc_string = format!(
282                        "[query description - consider adding a doc-comment!] {}",
283                        doc_string
284                    );
285                    let comment = parse_quote! {
286                        #[doc = #doc_string]
287                    };
288                    query.doc_comments.push(comment);
289                }
290                desc = Some((tcx, list));
291            }
292            QueryModifier::FatalCycle => {
293                if fatal_cycle {
294                    panic!("duplicate modifier `fatal_cycle` for query `{}`", query.name);
295                }
296                fatal_cycle = true;
297            }
298            QueryModifier::CycleDelayBug => {
299                if cycle_delay_bug {
300                    panic!("duplicate modifier `cycle_delay_bug` for query `{}`", query.name);
301                }
302                cycle_delay_bug = true;
303            }
304            QueryModifier::NoHash => {
305                if no_hash {
306                    panic!("duplicate modifier `no_hash` for query `{}`", query.name);
307                }
308                no_hash = true;
309            }
310            QueryModifier::Anon => {
311                if anon {
312                    panic!("duplicate modifier `anon` for query `{}`", query.name);
313                }
314                anon = true;
315            }
316            QueryModifier::EvalAlways => {
317                if eval_always {
318                    panic!("duplicate modifier `eval_always` for query `{}`", query.name);
319                }
320                eval_always = true;
321            }
322        }
323    }
324    let desc = desc.unwrap_or_else(|| {
325        panic!("no description provided for query `{}`", query.name);
326    });
327    QueryModifiers {
328        load_cached,
329        storage,
330        cache,
331        desc,
332        fatal_cycle,
333        cycle_delay_bug,
334        no_hash,
335        anon,
336        eval_always,
337    }
338}
339
340/// Add the impl of QueryDescription for the query to `impls` if one is requested
341fn add_query_description_impl(
342    query: &Query,
343    modifiers: QueryModifiers,
344    impls: &mut proc_macro2::TokenStream,
345) {
346    let name = &query.name;
347    let key = &query.key.0;
348
349    // Find out if we should cache the query on disk
350    let cache = if let Some((args, expr)) = modifiers.cache.as_ref() {
351        let try_load_from_disk = if let Some((tcx, id, block)) = modifiers.load_cached.as_ref() {
352            // Use custom code to load the query from disk
353            quote! {
354                #[inline]
355                fn try_load_from_disk(
356                    #tcx: QueryCtxt<'tcx>,
357                    #id: SerializedDepNodeIndex
358                ) -> Option<Self::Value> {
359                    #block
360                }
361            }
362        } else {
363            // Use the default code to load the query from disk
364            quote! {
365                #[inline]
366                fn try_load_from_disk(
367                    tcx: QueryCtxt<'tcx>,
368                    id: SerializedDepNodeIndex
369                ) -> Option<Self::Value> {
370                    tcx.on_disk_cache.as_ref()?.try_load_query_result(*tcx, id)
371                }
372            }
373        };
374
375        let tcx = args
376            .as_ref()
377            .map(|t| {
378                let t = &(t.0).0;
379                quote! { #t }
380            })
381            .unwrap_or_else(|| quote! { _ });
382        let value = args
383            .as_ref()
384            .map(|t| {
385                let t = &(t.1).0;
386                quote! { #t }
387            })
388            .unwrap_or_else(|| quote! { _ });
389        // expr is a `Block`, meaning that `{ #expr }` gets expanded
390        // to `{ { stmts... } }`, which triggers the `unused_braces` lint.
391        quote! {
392            #[inline]
393            #[allow(unused_variables, unused_braces)]
394            fn cache_on_disk(
395                #tcx: QueryCtxt<'tcx>,
396                #key: &Self::Key,
397                #value: Option<&Self::Value>
398            ) -> bool {
399                #expr
400            }
401
402            #try_load_from_disk
403        }
404    } else {
405        if modifiers.load_cached.is_some() {
406            panic!("load_cached modifier on query `{}` without a cache modifier", name);
407        }
408        quote! {}
409    };
410
411    let (tcx, desc) = modifiers.desc;
412    let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
413
414    let desc = quote! {
415        #[allow(unused_variables)]
416        fn describe(tcx: QueryCtxt<'tcx>, key: Self::Key) -> String {
417            let (#tcx, #key) = (*tcx, key);
418            ::rustc_middle::ty::print::with_no_trimmed_paths(|| format!(#desc).into())
419        }
420    };
421
422    impls.extend(quote! {
423        impl<'tcx> QueryDescription<QueryCtxt<'tcx>> for queries::#name<'tcx> {
424            #desc
425            #cache
426        }
427    });
428}
429
430pub fn rustc_queries(input: TokenStream) -> TokenStream {
431    let queries = parse_macro_input!(input as List<Query>);
432
433    let mut query_stream = quote! {};
434    let mut query_description_stream = quote! {};
435    let mut dep_node_def_stream = quote! {};
436    let mut cached_queries = quote! {};
437
438    for mut query in queries.0 {
439        let modifiers = process_modifiers(&mut query);
440        let name = &query.name;
441        let arg = &query.arg;
442        let result_full = &query.result;
443        let result = match query.result {
444            ReturnType::Default => quote! { -> () },
445            _ => quote! { #result_full },
446        };
447
448        if modifiers.cache.is_some() {
449            cached_queries.extend(quote! {
450                #name,
451            });
452        }
453
454        let mut attributes = Vec::new();
455
456        // Pass on the fatal_cycle modifier
457        if modifiers.fatal_cycle {
458            attributes.push(quote! { fatal_cycle });
459        };
460        // Pass on the storage modifier
461        if let Some(ref ty) = modifiers.storage {
462            attributes.push(quote! { storage(#ty) });
463        };
464        // Pass on the cycle_delay_bug modifier
465        if modifiers.cycle_delay_bug {
466            attributes.push(quote! { cycle_delay_bug });
467        };
468        // Pass on the no_hash modifier
469        if modifiers.no_hash {
470            attributes.push(quote! { no_hash });
471        };
472        // Pass on the anon modifier
473        if modifiers.anon {
474            attributes.push(quote! { anon });
475        };
476        // Pass on the eval_always modifier
477        if modifiers.eval_always {
478            attributes.push(quote! { eval_always });
479        };
480
481        let attribute_stream = quote! {#(#attributes),*};
482        let doc_comments = query.doc_comments.iter();
483        // Add the query to the group
484        query_stream.extend(quote! {
485            #(#doc_comments)*
486            [#attribute_stream] fn #name(#arg) #result,
487        });
488
489        // Create a dep node for the query
490        dep_node_def_stream.extend(quote! {
491            [#attribute_stream] #name(#arg),
492        });
493
494        add_query_description_impl(&query, modifiers, &mut query_description_stream);
495    }
496
497    TokenStream::from(quote! {
498        #[macro_export]
499        macro_rules! rustc_query_append {
500            ([$($macro:tt)*][$($other:tt)*]) => {
501                $($macro)* {
502                    $($other)*
503
504                    #query_stream
505
506                }
507            }
508        }
509        macro_rules! rustc_dep_node_append {
510            ([$($macro:tt)*][$($other:tt)*]) => {
511                $($macro)*(
512                    $($other)*
513
514                    #dep_node_def_stream
515                );
516            }
517        }
518        #[macro_export]
519        macro_rules! rustc_cached_queries {
520            ($($macro:tt)*) => {
521                $($macro)*(#cached_queries);
522            }
523        }
524        #[macro_export]
525        macro_rules! rustc_query_description {
526            () => { #query_description_stream }
527        }
528    })
529}