rustc_macros/
query.rs

1use proc_macro::TokenStream;
2use quote::{quote, quote_spanned};
3use syn::parse::{Parse, ParseStream, Result};
4use syn::punctuated::Punctuated;
5use syn::spanned::Spanned;
6use syn::{
7    AttrStyle, Attribute, Block, Error, Expr, Ident, Pat, ReturnType, Token, Type, braced,
8    parenthesized, parse_macro_input, parse_quote, token,
9};
10
11mod kw {
12    syn::custom_keyword!(query);
13}
14
15/// Ensures only doc comment attributes are used
16fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
17    let inner = |attr: Attribute| {
18        if !attr.path().is_ident("doc") {
19            Err(Error::new(attr.span(), "attributes not supported on queries"))
20        } else if attr.style != AttrStyle::Outer {
21            Err(Error::new(
22                attr.span(),
23                "attributes must be outer attributes (`///`), not inner attributes",
24            ))
25        } else {
26            Ok(attr)
27        }
28    };
29    attrs.into_iter().map(inner).collect()
30}
31
32/// A compiler query. `query ... { ... }`
33struct Query {
34    doc_comments: Vec<Attribute>,
35    modifiers: QueryModifiers,
36    name: Ident,
37    key: Pat,
38    arg: Type,
39    result: ReturnType,
40}
41
42impl Parse for Query {
43    fn parse(input: ParseStream<'_>) -> Result<Self> {
44        let mut doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
45
46        // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>`
47        input.parse::<kw::query>()?;
48        let name: Ident = input.parse()?;
49        let arg_content;
50        parenthesized!(arg_content in input);
51        let key = Pat::parse_single(&arg_content)?;
52        arg_content.parse::<Token![:]>()?;
53        let arg = arg_content.parse()?;
54        let result = input.parse()?;
55
56        // Parse the query modifiers
57        let content;
58        braced!(content in input);
59        let modifiers = parse_query_modifiers(&content)?;
60
61        // If there are no doc-comments, give at least some idea of what
62        // it does by showing the query description.
63        if doc_comments.is_empty() {
64            doc_comments.push(doc_comment_from_desc(&modifiers.desc.1)?);
65        }
66
67        Ok(Query { doc_comments, modifiers, name, key, arg, result })
68    }
69}
70
71/// A type used to greedily parse another type until the input is empty.
72struct List<T>(Vec<T>);
73
74impl<T: Parse> Parse for List<T> {
75    fn parse(input: ParseStream<'_>) -> Result<Self> {
76        let mut list = Vec::new();
77        while !input.is_empty() {
78            list.push(input.parse()?);
79        }
80        Ok(List(list))
81    }
82}
83
84struct QueryModifiers {
85    /// The description of the query.
86    desc: (Option<Ident>, Punctuated<Expr, Token![,]>),
87
88    /// Use this type for the in-memory cache.
89    arena_cache: Option<Ident>,
90
91    /// Cache the query to disk if the `Block` returns true.
92    cache: Option<(Option<Pat>, Block)>,
93
94    /// A cycle error for this query aborting the compilation with a fatal error.
95    fatal_cycle: Option<Ident>,
96
97    /// A cycle error results in a delay_bug call
98    cycle_delay_bug: Option<Ident>,
99
100    /// A cycle error results in a stashed cycle error that can be unstashed and canceled later
101    cycle_stash: Option<Ident>,
102
103    /// Don't hash the result, instead just mark a query red if it runs
104    no_hash: Option<Ident>,
105
106    /// Generate a dep node based on the dependencies of the query
107    anon: Option<Ident>,
108
109    /// Always evaluate the query, ignoring its dependencies
110    eval_always: Option<Ident>,
111
112    /// Whether the query has a call depth limit
113    depth_limit: Option<Ident>,
114
115    /// Use a separate query provider for local and extern crates
116    separate_provide_extern: Option<Ident>,
117
118    /// Generate a `feed` method to set the query's value from another query.
119    feedable: Option<Ident>,
120
121    /// When this query is called via `tcx.ensure_ok()`, it returns
122    /// `Result<(), ErrorGuaranteed>` instead of `()`. If the query needs to
123    /// be executed, and that execution returns an error, the error result is
124    /// returned to the caller.
125    ///
126    /// If execution is skipped, a synthetic `Ok(())` is returned, on the
127    /// assumption that a query with all-green inputs must have succeeded.
128    ///
129    /// Can only be applied to queries with a return value of
130    /// `Result<_, ErrorGuaranteed>`.
131    return_result_from_ensure_ok: Option<Ident>,
132}
133
134fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
135    let mut arena_cache = None;
136    let mut cache = None;
137    let mut desc = None;
138    let mut fatal_cycle = None;
139    let mut cycle_delay_bug = None;
140    let mut cycle_stash = None;
141    let mut no_hash = None;
142    let mut anon = None;
143    let mut eval_always = None;
144    let mut depth_limit = None;
145    let mut separate_provide_extern = None;
146    let mut feedable = None;
147    let mut return_result_from_ensure_ok = None;
148
149    while !input.is_empty() {
150        let modifier: Ident = input.parse()?;
151
152        macro_rules! try_insert {
153            ($name:ident = $expr:expr) => {
154                if $name.is_some() {
155                    return Err(Error::new(modifier.span(), "duplicate modifier"));
156                }
157                $name = Some($expr);
158            };
159        }
160
161        if modifier == "desc" {
162            // Parse a description modifier like:
163            // `desc { |tcx| "foo {}", tcx.item_path(key) }`
164            let attr_content;
165            braced!(attr_content in input);
166            let tcx = if attr_content.peek(Token![|]) {
167                attr_content.parse::<Token![|]>()?;
168                let tcx = attr_content.parse()?;
169                attr_content.parse::<Token![|]>()?;
170                Some(tcx)
171            } else {
172                None
173            };
174            let list = attr_content.parse_terminated(Expr::parse, Token![,])?;
175            try_insert!(desc = (tcx, list));
176        } else if modifier == "cache_on_disk_if" {
177            // Parse a cache modifier like:
178            // `cache(tcx) { |tcx| key.is_local() }`
179            let args = if input.peek(token::Paren) {
180                let args;
181                parenthesized!(args in input);
182                let tcx = Pat::parse_single(&args)?;
183                Some(tcx)
184            } else {
185                None
186            };
187            let block = input.parse()?;
188            try_insert!(cache = (args, block));
189        } else if modifier == "arena_cache" {
190            try_insert!(arena_cache = modifier);
191        } else if modifier == "fatal_cycle" {
192            try_insert!(fatal_cycle = modifier);
193        } else if modifier == "cycle_delay_bug" {
194            try_insert!(cycle_delay_bug = modifier);
195        } else if modifier == "cycle_stash" {
196            try_insert!(cycle_stash = modifier);
197        } else if modifier == "no_hash" {
198            try_insert!(no_hash = modifier);
199        } else if modifier == "anon" {
200            try_insert!(anon = modifier);
201        } else if modifier == "eval_always" {
202            try_insert!(eval_always = modifier);
203        } else if modifier == "depth_limit" {
204            try_insert!(depth_limit = modifier);
205        } else if modifier == "separate_provide_extern" {
206            try_insert!(separate_provide_extern = modifier);
207        } else if modifier == "feedable" {
208            try_insert!(feedable = modifier);
209        } else if modifier == "return_result_from_ensure_ok" {
210            try_insert!(return_result_from_ensure_ok = modifier);
211        } else {
212            return Err(Error::new(modifier.span(), "unknown query modifier"));
213        }
214    }
215    let Some(desc) = desc else {
216        return Err(input.error("no description provided"));
217    };
218    Ok(QueryModifiers {
219        arena_cache,
220        cache,
221        desc,
222        fatal_cycle,
223        cycle_delay_bug,
224        cycle_stash,
225        no_hash,
226        anon,
227        eval_always,
228        depth_limit,
229        separate_provide_extern,
230        feedable,
231        return_result_from_ensure_ok,
232    })
233}
234
235fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
236    use ::syn::*;
237    let mut iter = list.iter();
238    let format_str: String = match iter.next() {
239        Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
240            lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
241        }
242        _ => return Err(Error::new(list.span(), "Expected a string literal")),
243    };
244    let mut fmt_fragments = format_str.split("{}");
245    let mut doc_string = fmt_fragments.next().unwrap().to_string();
246    iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
247        |(tts, next_fmt_fragment)| {
248            use ::core::fmt::Write;
249            write!(
250                &mut doc_string,
251                " `{}` {}",
252                tts.to_string().replace(" . ", "."),
253                next_fmt_fragment,
254            )
255            .unwrap();
256        },
257    );
258    let doc_string = format!("[query description - consider adding a doc-comment!] {doc_string}");
259    Ok(parse_quote! { #[doc = #doc_string] })
260}
261
262/// Add the impl of QueryDescription for the query to `impls` if one is requested
263fn add_query_desc_cached_impl(
264    query: &Query,
265    descs: &mut proc_macro2::TokenStream,
266    cached: &mut proc_macro2::TokenStream,
267) {
268    let Query { name, key, modifiers, .. } = &query;
269
270    // Find out if we should cache the query on disk
271    let cache = if let Some((args, expr)) = modifiers.cache.as_ref() {
272        let tcx = args.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ });
273        // expr is a `Block`, meaning that `{ #expr }` gets expanded
274        // to `{ { stmts... } }`, which triggers the `unused_braces` lint.
275        // we're taking `key` by reference, but some rustc types usually prefer being passed by value
276        quote! {
277            #[allow(unused_variables, unused_braces, rustc::pass_by_value)]
278            #[inline]
279            pub fn #name<'tcx>(#tcx: TyCtxt<'tcx>, #key: &crate::query::queries::#name::Key<'tcx>) -> bool {
280                #expr
281            }
282        }
283    } else {
284        quote! {
285            // we're taking `key` by reference, but some rustc types usually prefer being passed by value
286            #[allow(rustc::pass_by_value)]
287            #[inline]
288            pub fn #name<'tcx>(_: TyCtxt<'tcx>, _: &crate::query::queries::#name::Key<'tcx>) -> bool {
289                false
290            }
291        }
292    };
293
294    let (tcx, desc) = &modifiers.desc;
295    let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
296
297    let desc = quote! {
298        #[allow(unused_variables)]
299        pub fn #name<'tcx>(tcx: TyCtxt<'tcx>, key: crate::query::queries::#name::Key<'tcx>) -> String {
300            let (#tcx, #key) = (tcx, key);
301            ::rustc_middle::ty::print::with_no_trimmed_paths!(
302                format!(#desc)
303            )
304        }
305    };
306
307    descs.extend(quote! {
308        #desc
309    });
310
311    cached.extend(quote! {
312        #cache
313    });
314}
315
316pub(super) fn rustc_queries(input: TokenStream) -> TokenStream {
317    let queries = parse_macro_input!(input as List<Query>);
318
319    let mut query_stream = quote! {};
320    let mut query_description_stream = quote! {};
321    let mut query_cached_stream = quote! {};
322    let mut feedable_queries = quote! {};
323    let mut errors = quote! {};
324
325    macro_rules! assert {
326        ( $cond:expr, $span:expr, $( $tt:tt )+ ) => {
327            if !$cond {
328                errors.extend(
329                    Error::new($span, format!($($tt)+)).into_compile_error(),
330                );
331            }
332        }
333    }
334
335    for query in queries.0 {
336        let Query { name, arg, modifiers, .. } = &query;
337        let result_full = &query.result;
338        let result = match query.result {
339            ReturnType::Default => quote! { -> () },
340            _ => quote! { #result_full },
341        };
342
343        let mut attributes = Vec::new();
344
345        macro_rules! passthrough {
346            ( $( $modifier:ident ),+ $(,)? ) => {
347                $( if let Some($modifier) = &modifiers.$modifier {
348                    attributes.push(quote! { (#$modifier) });
349                }; )+
350            }
351        }
352
353        passthrough!(
354            fatal_cycle,
355            arena_cache,
356            cycle_delay_bug,
357            cycle_stash,
358            no_hash,
359            anon,
360            eval_always,
361            depth_limit,
362            separate_provide_extern,
363            return_result_from_ensure_ok,
364        );
365
366        if modifiers.cache.is_some() {
367            attributes.push(quote! { (cache) });
368        }
369        // Pass on the cache modifier
370        if modifiers.cache.is_some() {
371            attributes.push(quote! { (cache) });
372        }
373
374        // This uses the span of the query definition for the commas,
375        // which can be important if we later encounter any ambiguity
376        // errors with any of the numerous macro_rules! macros that
377        // we use. Using the call-site span would result in a span pointing
378        // at the entire `rustc_queries!` invocation, which wouldn't
379        // be very useful.
380        let span = name.span();
381        let attribute_stream = quote_spanned! {span=> #(#attributes),*};
382        let doc_comments = &query.doc_comments;
383        // Add the query to the group
384        query_stream.extend(quote! {
385            #(#doc_comments)*
386            [#attribute_stream] fn #name(#arg) #result,
387        });
388
389        if let Some(feedable) = &modifiers.feedable {
390            assert!(
391                modifiers.anon.is_none(),
392                feedable.span(),
393                "Query {name} cannot be both `feedable` and `anon`."
394            );
395            assert!(
396                modifiers.eval_always.is_none(),
397                feedable.span(),
398                "Query {name} cannot be both `feedable` and `eval_always`."
399            );
400            feedable_queries.extend(quote! {
401                #(#doc_comments)*
402                [#attribute_stream] fn #name(#arg) #result,
403            });
404        }
405
406        add_query_desc_cached_impl(&query, &mut query_description_stream, &mut query_cached_stream);
407    }
408
409    TokenStream::from(quote! {
410        #[macro_export]
411        macro_rules! rustc_query_append {
412            ($macro:ident! $( [$($other:tt)*] )?) => {
413                $macro! {
414                    $( $($other)* )?
415                    #query_stream
416                }
417            }
418        }
419        macro_rules! rustc_feedable_queries {
420            ( $macro:ident! ) => {
421                $macro!(#feedable_queries);
422            }
423        }
424        pub mod descs {
425            use super::*;
426            #query_description_stream
427        }
428        pub mod cached {
429            use super::*;
430            #query_cached_stream
431        }
432        #errors
433    })
434}