Skip to main content

rustc_macros/
query.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{quote, quote_spanned};
4use syn::parse::{Parse, ParseStream, Result};
5use syn::punctuated::Punctuated;
6use syn::spanned::Spanned;
7use syn::{
8    AttrStyle, Attribute, Block, Error, Expr, Ident, Pat, ReturnType, Token, Type, braced,
9    parenthesized, parse_macro_input, token,
10};
11
12mod kw {
13    syn::custom_keyword!(non_query);
14    syn::custom_keyword!(query);
15}
16
17/// Ensures only doc comment attributes are used
18fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
19    let inner = |attr: Attribute| {
20        if !attr.path().is_ident("doc") {
21            Err(Error::new(attr.span(), "attributes not supported on queries"))
22        } else if attr.style != AttrStyle::Outer {
23            Err(Error::new(
24                attr.span(),
25                "attributes must be outer attributes (`///`), not inner attributes",
26            ))
27        } else {
28            Ok(attr)
29        }
30    };
31    attrs.into_iter().map(inner).collect()
32}
33
34/// Declaration of a compiler query.
35///
36/// ```ignore (illustrative)
37/// /// Doc comment for `my_query`.
38/// //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^              doc_comments
39/// query my_query(key: DefId) -> Value { anon }
40/// //    ^^^^^^^^                               name
41/// //             ^^^                           key_pat
42/// //                  ^^^^^                    key_ty
43/// //                         ^^^^^^^^          return_ty
44/// //                                    ^^^^   modifiers
45/// ```
46struct Query {
47    doc_comments: Vec<Attribute>,
48    name: Ident,
49
50    /// Parameter name for the key, or an arbitrary irrefutable pattern (e.g. `_`).
51    key_pat: Pat,
52    key_ty: Type,
53    return_ty: ReturnType,
54
55    modifiers: QueryModifiers,
56}
57
58/// Declaration of a non-query dep kind.
59/// ```ignore (illustrative)
60/// /// Doc comment for `MyNonQuery`.
61/// //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  doc_comments
62/// non_query MyNonQuery
63/// //        ^^^^^^^^^^               name
64/// ```
65struct NonQuery {
66    doc_comments: Vec<Attribute>,
67    name: Ident,
68}
69
70enum QueryEntry {
71    Query(Query),
72    NonQuery(NonQuery),
73}
74
75impl Parse for QueryEntry {
76    fn parse(input: ParseStream<'_>) -> Result<Self> {
77        let mut doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
78
79        // Try the non-query case first.
80        if input.parse::<kw::non_query>().is_ok() {
81            let name: Ident = input.parse()?;
82            return Ok(QueryEntry::NonQuery(NonQuery { doc_comments, name }));
83        }
84
85        // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>`
86        if input.parse::<kw::query>().is_err() {
87            return Err(input.error("expected `query` or `non_query`"));
88        }
89        let name: Ident = input.parse()?;
90
91        // `(key: DefId)`
92        let parens_content;
93        parenthesized!(parens_content in input);
94        let key_pat = Pat::parse_single(&parens_content)?;
95        parens_content.parse::<Token![:]>()?;
96        let key_ty = parens_content.parse::<Type>()?;
97        let _trailing_comma = parens_content.parse::<Option<Token![,]>>()?;
98
99        // `-> Value`
100        let return_ty = input.parse::<ReturnType>()?;
101
102        // Parse the query modifiers
103        let braces_content;
104        braced!(braces_content in input);
105        let modifiers = parse_query_modifiers(&braces_content)?;
106
107        // If there are no doc-comments, give at least some idea of what
108        // it does by showing the query description.
109        if doc_comments.is_empty() {
110            doc_comments.push(doc_comment_from_desc(&modifiers.desc.expr_list)?);
111        }
112
113        Ok(QueryEntry::Query(Query { doc_comments, modifiers, name, key_pat, key_ty, return_ty }))
114    }
115}
116
117/// A type used to greedily parse another type until the input is empty.
118struct List<T>(Vec<T>);
119
120impl<T: Parse> Parse for List<T> {
121    fn parse(input: ParseStream<'_>) -> Result<Self> {
122        let mut list = Vec::new();
123        while !input.is_empty() {
124            list.push(input.parse()?);
125        }
126        Ok(List(list))
127    }
128}
129
130struct Desc {
131    modifier: Ident,
132    expr_list: Punctuated<Expr, Token![,]>,
133}
134
135struct CacheOnDiskIf {
136    modifier: Ident,
137    block: Block,
138}
139
140/// See `rustc_middle::query::modifiers` for documentation of each query modifier.
141struct QueryModifiers {
142    // tidy-alphabetical-start
143    anon: Option<Ident>,
144    arena_cache: Option<Ident>,
145    cache_on_disk_if: Option<CacheOnDiskIf>,
146    cycle_delay_bug: Option<Ident>,
147    cycle_fatal: Option<Ident>,
148    cycle_stash: Option<Ident>,
149    depth_limit: Option<Ident>,
150    desc: Desc,
151    eval_always: Option<Ident>,
152    feedable: Option<Ident>,
153    no_hash: Option<Ident>,
154    return_result_from_ensure_ok: Option<Ident>,
155    separate_provide_extern: Option<Ident>,
156    // tidy-alphabetical-end
157}
158
159fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
160    let mut arena_cache = None;
161    let mut cache_on_disk_if = None;
162    let mut desc = None;
163    let mut cycle_fatal = None;
164    let mut cycle_delay_bug = None;
165    let mut cycle_stash = None;
166    let mut no_hash = None;
167    let mut anon = None;
168    let mut eval_always = None;
169    let mut depth_limit = None;
170    let mut separate_provide_extern = None;
171    let mut feedable = None;
172    let mut return_result_from_ensure_ok = None;
173
174    while !input.is_empty() {
175        let modifier: Ident = input.parse()?;
176
177        macro_rules! try_insert {
178            ($name:ident = $expr:expr) => {
179                if $name.is_some() {
180                    return Err(Error::new(modifier.span(), "duplicate modifier"));
181                }
182                $name = Some($expr);
183            };
184        }
185
186        if modifier == "desc" {
187            // Parse a description modifier like:
188            // `desc { "foo {}", tcx.item_path(key) }`
189            let attr_content;
190            braced!(attr_content in input);
191            let expr_list = attr_content.parse_terminated(Expr::parse, Token![,])?;
192            try_insert!(desc = Desc { modifier, expr_list });
193        } else if modifier == "cache_on_disk_if" {
194            // Parse a cache-on-disk modifier like:
195            // `cache_on_disk_if { tcx.is_typeck_child(key.to_def_id()) }`
196            let block = input.parse()?;
197            try_insert!(cache_on_disk_if = CacheOnDiskIf { modifier, block });
198        } else if modifier == "arena_cache" {
199            try_insert!(arena_cache = modifier);
200        } else if modifier == "cycle_fatal" {
201            try_insert!(cycle_fatal = modifier);
202        } else if modifier == "cycle_delay_bug" {
203            try_insert!(cycle_delay_bug = modifier);
204        } else if modifier == "cycle_stash" {
205            try_insert!(cycle_stash = modifier);
206        } else if modifier == "no_hash" {
207            try_insert!(no_hash = modifier);
208        } else if modifier == "anon" {
209            try_insert!(anon = modifier);
210        } else if modifier == "eval_always" {
211            try_insert!(eval_always = modifier);
212        } else if modifier == "depth_limit" {
213            try_insert!(depth_limit = modifier);
214        } else if modifier == "separate_provide_extern" {
215            try_insert!(separate_provide_extern = modifier);
216        } else if modifier == "feedable" {
217            try_insert!(feedable = modifier);
218        } else if modifier == "return_result_from_ensure_ok" {
219            try_insert!(return_result_from_ensure_ok = modifier);
220        } else {
221            return Err(Error::new(modifier.span(), "unknown query modifier"));
222        }
223    }
224    let Some(desc) = desc else {
225        return Err(input.error("no description provided"));
226    };
227    Ok(QueryModifiers {
228        arena_cache,
229        cache_on_disk_if,
230        desc,
231        cycle_fatal,
232        cycle_delay_bug,
233        cycle_stash,
234        no_hash,
235        anon,
236        eval_always,
237        depth_limit,
238        separate_provide_extern,
239        feedable,
240        return_result_from_ensure_ok,
241    })
242}
243
244fn make_modifiers_stream(query: &Query, modifiers: &QueryModifiers) -> proc_macro2::TokenStream {
245    let QueryModifiers {
246        // tidy-alphabetical-start
247        anon,
248        arena_cache,
249        cache_on_disk_if,
250        cycle_delay_bug,
251        cycle_fatal,
252        cycle_stash,
253        depth_limit,
254        desc: _,
255        eval_always,
256        feedable,
257        no_hash,
258        return_result_from_ensure_ok,
259        separate_provide_extern,
260        // tidy-alphabetical-end
261    } = modifiers;
262
263    let anon = anon.is_some();
264    let arena_cache = arena_cache.is_some();
265    let cache_on_disk = cache_on_disk_if.is_some();
266
267    let cycle_error_handling = if cycle_delay_bug.is_some() {
268        quote! { DelayBug }
269    } else if cycle_fatal.is_some() {
270        quote! { Fatal }
271    } else if cycle_stash.is_some() {
272        quote! { Stash }
273    } else {
274        quote! { Error }
275    };
276
277    let depth_limit = depth_limit.is_some();
278    let eval_always = eval_always.is_some();
279    let feedable = feedable.is_some();
280    let no_hash = no_hash.is_some();
281    let return_result_from_ensure_ok = return_result_from_ensure_ok.is_some();
282    let separate_provide_extern = separate_provide_extern.is_some();
283
284    // Giving an input span to the modifier names in the modifier list seems
285    // to give slightly more helpful errors when one of the callback macros
286    // fails to parse the modifier list.
287    let query_name_span = query.name.span();
288    quote_spanned! {
289        query_name_span =>
290        // Search for (QMODLIST) to find all occurrences of this query modifier list.
291        // tidy-alphabetical-start
292        anon: #anon,
293        arena_cache: #arena_cache,
294        cache_on_disk: #cache_on_disk,
295        cycle_error_handling: #cycle_error_handling,
296        depth_limit: #depth_limit,
297        eval_always: #eval_always,
298        feedable: #feedable,
299        no_hash: #no_hash,
300        return_result_from_ensure_ok: #return_result_from_ensure_ok,
301        separate_provide_extern: #separate_provide_extern,
302        // tidy-alphabetical-end
303    }
304}
305
306fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
307    use ::syn::*;
308    let mut iter = list.iter();
309    let format_str: String = match iter.next() {
310        Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
311            lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
312        }
313        _ => return Err(Error::new(list.span(), "Expected a string literal")),
314    };
315    let mut fmt_fragments = format_str.split("{}");
316    let mut doc_string = fmt_fragments.next().unwrap().to_string();
317    iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
318        |(tts, next_fmt_fragment)| {
319            use ::core::fmt::Write;
320            write!(
321                &mut doc_string,
322                " `{}` {}",
323                tts.to_string().replace(" . ", "."),
324                next_fmt_fragment,
325            )
326            .unwrap();
327        },
328    );
329    let doc_string = format!("[query description - consider adding a doc-comment!] {doc_string}");
330    Ok(parse_quote! { #[doc = #doc_string] })
331}
332
333/// Contains token streams that are used to accumulate per-query helper
334/// functions, to be used by the final output of `rustc_queries!`.
335///
336/// Helper items typically have the same name as the query they relate to,
337/// and expect to be interpolated into a dedicated module.
338#[derive(Default)]
339struct HelperTokenStreams {
340    description_fns_stream: proc_macro2::TokenStream,
341    cache_on_disk_if_fns_stream: proc_macro2::TokenStream,
342}
343
344fn make_helpers_for_query(query: &Query, streams: &mut HelperTokenStreams) {
345    let Query { name, key_pat, key_ty, modifiers, .. } = &query;
346
347    // Replace span for `name` to make rust-analyzer ignore it.
348    let mut erased_name = name.clone();
349    erased_name.set_span(Span::call_site());
350
351    // Generate a function to check whether we should cache the query to disk, for some key.
352    if let Some(CacheOnDiskIf { block, .. }) = modifiers.cache_on_disk_if.as_ref() {
353        // `disallowed_pass_by_ref` is needed because some keys are `rustc_pass_by_value`.
354        streams.cache_on_disk_if_fns_stream.extend(quote! {
355            #[cfg_attr(not(bootstrap), allow(unused_variables, rustc::disallowed_pass_by_ref))]
356            #[cfg_attr(bootstrap, allow(unused_variables, rustc::pass_by_value))]
357            #[inline]
358            pub fn #erased_name<'tcx>(tcx: TyCtxt<'tcx>, #key_pat: &#key_ty) -> bool
359            #block
360        });
361    }
362
363    let Desc { expr_list, .. } = &modifiers.desc;
364
365    let desc = quote! {
366        #[allow(unused_variables)]
367        pub fn #erased_name<'tcx>(tcx: TyCtxt<'tcx>, #key_pat: #key_ty) -> String {
368            format!(#expr_list)
369        }
370    };
371
372    streams.description_fns_stream.extend(quote! {
373        #desc
374    });
375}
376
377/// Add hints for rust-analyzer
378fn add_to_analyzer_stream(query: &Query, analyzer_stream: &mut proc_macro2::TokenStream) {
379    // Add links to relevant modifiers
380
381    let modifiers = &query.modifiers;
382
383    let mut modifiers_stream = quote! {};
384
385    let name = &modifiers.desc.modifier;
386    modifiers_stream.extend(quote! {
387        crate::query::modifiers::#name;
388    });
389
390    if let Some(CacheOnDiskIf { modifier, .. }) = &modifiers.cache_on_disk_if {
391        modifiers_stream.extend(quote! {
392            crate::query::modifiers::#modifier;
393        });
394    }
395
396    macro_rules! doc_link {
397        ( $( $modifier:ident ),+ $(,)? ) => {
398            $(
399                if let Some(name) = &modifiers.$modifier {
400                    modifiers_stream.extend(quote! {
401                        crate::query::modifiers::#name;
402                    });
403                }
404            )+
405        }
406    }
407
408    doc_link!(
409        arena_cache,
410        cycle_fatal,
411        cycle_delay_bug,
412        cycle_stash,
413        no_hash,
414        anon,
415        eval_always,
416        depth_limit,
417        separate_provide_extern,
418        feedable,
419        return_result_from_ensure_ok,
420    );
421
422    let name = &query.name;
423
424    // Replace span for `name` to make rust-analyzer ignore it.
425    let mut erased_name = name.clone();
426    erased_name.set_span(Span::call_site());
427
428    let result = &query.return_ty;
429
430    // This dead code exists to instruct rust-analyzer about the link between the `rustc_queries`
431    // query names and the corresponding produced provider. The issue is that by nature of this
432    // macro producing a higher order macro that has all its token in the macro declaration we lose
433    // any meaningful spans, resulting in rust-analyzer being unable to make the connection between
434    // the query name and the corresponding providers field. The trick to fix this is to have
435    // `rustc_queries` emit a field access with the given name's span which allows it to
436    // successfully show references / go to definition to the corresponding provider assignment
437    // which is usually the more interesting place.
438    let ra_hint = quote! {
439        let crate::query::Providers { #name: _, .. };
440    };
441
442    analyzer_stream.extend(quote! {
443        #[inline(always)]
444        fn #erased_name<'tcx>() #result {
445            #ra_hint
446            #modifiers_stream
447            loop {}
448        }
449    });
450}
451
452pub(super) fn rustc_queries(input: TokenStream) -> TokenStream {
453    let queries = parse_macro_input!(input as List<QueryEntry>);
454
455    let mut query_stream = quote! {};
456    let mut non_query_stream = quote! {};
457    let mut helpers = HelperTokenStreams::default();
458    let mut analyzer_stream = quote! {};
459    let mut errors = quote! {};
460
461    macro_rules! assert {
462        ( $cond:expr, $span:expr, $( $tt:tt )+ ) => {
463            if !$cond {
464                errors.extend(
465                    Error::new($span, format!($($tt)+)).into_compile_error(),
466                );
467            }
468        }
469    }
470
471    for query in queries.0 {
472        let query = match query {
473            QueryEntry::Query(query) => query,
474            QueryEntry::NonQuery(NonQuery { doc_comments, name }) => {
475                // Get the exceptional non-query case out of the way first.
476                non_query_stream.extend(quote! {
477                    #(#doc_comments)*
478                    #name,
479                });
480                continue;
481            }
482        };
483
484        let Query { doc_comments, name, key_ty, return_ty, modifiers, .. } = &query;
485
486        // Normalize an absent return type into `-> ()` to make macro-rules parsing easier.
487        let return_ty = match return_ty {
488            ReturnType::Default => quote! { -> () },
489            ReturnType::Type(..) => quote! { #return_ty },
490        };
491
492        let modifiers_stream = make_modifiers_stream(&query, modifiers);
493
494        // Add the query to the group
495        query_stream.extend(quote! {
496            #(#doc_comments)*
497            fn #name(#key_ty) #return_ty
498            { #modifiers_stream }
499        });
500
501        if let Some(feedable) = &modifiers.feedable {
502            assert!(
503                modifiers.anon.is_none(),
504                feedable.span(),
505                "Query {name} cannot be both `feedable` and `anon`."
506            );
507            assert!(
508                modifiers.eval_always.is_none(),
509                feedable.span(),
510                "Query {name} cannot be both `feedable` and `eval_always`."
511            );
512        }
513
514        add_to_analyzer_stream(&query, &mut analyzer_stream);
515        make_helpers_for_query(&query, &mut helpers);
516    }
517
518    let HelperTokenStreams { description_fns_stream, cache_on_disk_if_fns_stream } = helpers;
519
520    TokenStream::from(quote! {
521        /// Higher-order macro that invokes the specified macro with (a) a list of all query
522        /// signatures (including modifiers), and (b) a list of non-query names. This allows
523        /// multiple simpler macros to each have access to these lists.
524        #[macro_export]
525        macro_rules! rustc_with_all_queries {
526            (
527                // The macro to invoke once, on all queries and non-queries.
528                $macro:ident!
529            ) => {
530                $macro! {
531                    queries { #query_stream }
532                    non_queries { #non_query_stream }
533                }
534            }
535        }
536
537        // Add hints for rust-analyzer
538        mod _analyzer_hints {
539            use super::*;
540            #analyzer_stream
541        }
542
543        /// Functions that format a human-readable description of each query
544        /// and its key, as specified by the `desc` query modifier.
545        ///
546        /// (The leading `_` avoids collisions with actual query names when
547        /// expanded in `rustc_middle::queries`, and makes this macro-generated
548        /// module easier to search for.)
549        pub mod _description_fns {
550            use super::*;
551            #description_fns_stream
552        }
553
554        // FIXME(Zalathar): Instead of declaring these functions directly, can
555        // we put them in a macro and then expand that macro downstream in
556        // `rustc_query_impl`, where the functions are actually used?
557        pub mod _cache_on_disk_if_fns {
558            use super::*;
559            #cache_on_disk_if_fns_stream
560        }
561
562        #errors
563    })
564}