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, Error, Expr, Ident, Pat, ReturnType, Token, Type, braced, parenthesized,
9    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    // This ident is always `desc` but we need it for its span, for `crate::query::modifiers`.
132    modifier: Ident,
133    expr_list: Punctuated<Expr, Token![,]>,
134}
135
136/// See `rustc_middle::query::modifiers` for documentation of each query modifier.
137struct QueryModifiers {
138    // tidy-alphabetical-start
139    arena_cache: Option<Ident>,
140    cache_on_disk: Option<Ident>,
141    depth_limit: Option<Ident>,
142    desc: Desc,
143    eval_always: Option<Ident>,
144    feedable: Option<Ident>,
145    handle_cycle_error: Option<Ident>,
146    no_force: Option<Ident>,
147    no_hash: Option<Ident>,
148    separate_provide_extern: Option<Ident>,
149    // tidy-alphabetical-end
150}
151
152fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
153    // tidy-alphabetical-start
154    let mut arena_cache = None;
155    let mut cache_on_disk = None;
156    let mut depth_limit = None;
157    let mut desc = None;
158    let mut eval_always = None;
159    let mut feedable = None;
160    let mut handle_cycle_error = None;
161    let mut no_force = None;
162    let mut no_hash = None;
163    let mut separate_provide_extern = None;
164    // tidy-alphabetical-end
165
166    while !input.is_empty() {
167        let modifier: Ident = input.parse()?;
168
169        macro_rules! try_insert {
170            ($name:ident = $expr:expr) => {
171                if $name.is_some() {
172                    return Err(Error::new(modifier.span(), "duplicate modifier"));
173                }
174                $name = Some($expr);
175            };
176        }
177
178        if modifier == "arena_cache" {
179            try_insert!(arena_cache = modifier);
180        } else if modifier == "cache_on_disk" {
181            try_insert!(cache_on_disk = modifier);
182        } else if modifier == "depth_limit" {
183            try_insert!(depth_limit = modifier);
184        } else if modifier == "desc" {
185            // Parse a description modifier like:
186            // `desc { "foo {}", tcx.item_path(key) }`
187            let attr_content;
188            braced!(attr_content in input);
189            let expr_list = attr_content.parse_terminated(Expr::parse, Token![,])?;
190            try_insert!(desc = Desc { modifier, expr_list });
191        } else if modifier == "eval_always" {
192            try_insert!(eval_always = modifier);
193        } else if modifier == "feedable" {
194            try_insert!(feedable = modifier);
195        } else if modifier == "handle_cycle_error" {
196            try_insert!(handle_cycle_error = modifier);
197        } else if modifier == "no_force" {
198            try_insert!(no_force = modifier);
199        } else if modifier == "no_hash" {
200            try_insert!(no_hash = modifier);
201        } else if modifier == "separate_provide_extern" {
202            try_insert!(separate_provide_extern = modifier);
203        } else {
204            return Err(Error::new(modifier.span(), "unknown query modifier"));
205        }
206    }
207    let Some(desc) = desc else {
208        return Err(input.error("no description provided"));
209    };
210    Ok(QueryModifiers {
211        // tidy-alphabetical-start
212        arena_cache,
213        cache_on_disk,
214        depth_limit,
215        desc,
216        eval_always,
217        feedable,
218        handle_cycle_error,
219        no_force,
220        no_hash,
221        separate_provide_extern,
222        // tidy-alphabetical-end
223    })
224}
225
226// Does `ret_ty` match `Result<_, ErrorGuaranteed>`?
227fn returns_error_guaranteed(ret_ty: &ReturnType) -> bool {
228    use ::syn::*;
229    if let ReturnType::Type(_, ret_ty) = ret_ty
230        && let Type::Path(type_path) = ret_ty.as_ref()
231        && let Some(PathSegment { ident, arguments }) = type_path.path.segments.last()
232        && ident == "Result"
233        && let PathArguments::AngleBracketed(args) = arguments
234        && args.args.len() == 2
235        && let GenericArgument::Type(ty) = &args.args[1]
236        && let Type::Path(type_path) = ty
237        && type_path.path.is_ident("ErrorGuaranteed")
238    {
239        true
240    } else {
241        false
242    }
243}
244
245fn make_modifiers_stream(query: &Query) -> proc_macro2::TokenStream {
246    let QueryModifiers {
247        // tidy-alphabetical-start
248        arena_cache,
249        cache_on_disk,
250        depth_limit,
251        desc,
252        eval_always,
253        feedable,
254        handle_cycle_error,
255        no_force,
256        no_hash,
257        separate_provide_extern,
258        // tidy-alphabetical-end
259    } = &query.modifiers;
260
261    // tidy-alphabetical-start
262    let arena_cache = arena_cache.is_some();
263    let cache_on_disk = cache_on_disk.is_some();
264    let depth_limit = depth_limit.is_some();
265    let desc = {
266        // Put a description closure in the `desc` modifier.
267        let key_pat = &query.key_pat;
268        let key_ty = &query.key_ty;
269        let desc_expr_list = &desc.expr_list;
270        quote! {
271            {
272                #[allow(unused_variables)]
273                |tcx: TyCtxt<'tcx>, #key_pat: #key_ty| format!(#desc_expr_list)
274            }
275        }
276    };
277    let eval_always = eval_always.is_some();
278    let feedable = feedable.is_some();
279    let handle_cycle_error = handle_cycle_error.is_some();
280    let no_force = no_force.is_some();
281    let no_hash = no_hash.is_some();
282    let returns_error_guaranteed = returns_error_guaranteed(&query.return_ty);
283    let separate_provide_extern = separate_provide_extern.is_some();
284    // tidy-alphabetical-end
285
286    // Giving an input span to the modifier names in the modifier list seems
287    // to give slightly more helpful errors when one of the callback macros
288    // fails to parse the modifier list.
289    let query_name_span = query.name.span();
290    quote_spanned! {
291        query_name_span =>
292        // Search for (QMODLIST) to find all occurrences of this query modifier list.
293        // tidy-alphabetical-start
294        arena_cache: #arena_cache,
295        cache_on_disk: #cache_on_disk,
296        depth_limit: #depth_limit,
297        desc: #desc,
298        eval_always: #eval_always,
299        feedable: #feedable,
300        handle_cycle_error: #handle_cycle_error,
301        no_force: #no_force,
302        no_hash: #no_hash,
303        returns_error_guaranteed: #returns_error_guaranteed,
304        separate_provide_extern: #separate_provide_extern,
305        // tidy-alphabetical-end
306    }
307}
308
309fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
310    use ::syn::*;
311    let mut iter = list.iter();
312    let format_str: String = match iter.next() {
313        Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
314            lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
315        }
316        _ => return Err(Error::new(list.span(), "Expected a string literal")),
317    };
318    let mut fmt_fragments = format_str.split("{}");
319    let mut doc_string = fmt_fragments.next().unwrap().to_string();
320    iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
321        |(tts, next_fmt_fragment)| {
322            use ::core::fmt::Write;
323            write!(
324                &mut doc_string,
325                " `{}` {}",
326                tts.to_string().replace(" . ", "."),
327                next_fmt_fragment,
328            )
329            .unwrap();
330        },
331    );
332    let doc_string = format!("[query description - consider adding a doc-comment!] {doc_string}");
333    Ok(parse_quote! { #[doc = #doc_string] })
334}
335
336/// Add hints for rust-analyzer
337fn add_to_analyzer_stream(query: &Query, analyzer_stream: &mut proc_macro2::TokenStream) {
338    // Add links to relevant modifiers
339
340    let modifiers = &query.modifiers;
341
342    let mut modifiers_stream = quote! {};
343
344    let name = &modifiers.desc.modifier;
345    modifiers_stream.extend(quote! {
346        crate::query::modifiers::#name;
347    });
348
349    macro_rules! doc_link {
350        ( $( $modifier:ident ),+ $(,)? ) => {
351            $(
352                if let Some(name) = &modifiers.$modifier {
353                    modifiers_stream.extend(quote! {
354                        crate::query::modifiers::#name;
355                    });
356                }
357            )+
358        }
359    }
360
361    doc_link!(
362        // tidy-alphabetical-start
363        arena_cache,
364        cache_on_disk,
365        depth_limit,
366        // `desc` is handled above
367        eval_always,
368        feedable,
369        handle_cycle_error,
370        no_force,
371        no_hash,
372        separate_provide_extern,
373        // tidy-alphabetical-end
374    );
375
376    let name = &query.name;
377
378    // Replace span for `name` to make rust-analyzer ignore it.
379    let mut erased_name = name.clone();
380    erased_name.set_span(Span::call_site());
381
382    let result = &query.return_ty;
383
384    // This dead code exists to instruct rust-analyzer about the link between the `rustc_queries`
385    // query names and the corresponding produced provider. The issue is that by nature of this
386    // macro producing a higher order macro that has all its token in the macro declaration we lose
387    // any meaningful spans, resulting in rust-analyzer being unable to make the connection between
388    // the query name and the corresponding providers field. The trick to fix this is to have
389    // `rustc_queries` emit a field access with the given name's span which allows it to
390    // successfully show references / go to definition to the corresponding provider assignment
391    // which is usually the more interesting place.
392    let ra_hint = quote! {
393        let crate::query::Providers { #name: _, .. };
394    };
395
396    analyzer_stream.extend(quote! {
397        #[inline(always)]
398        fn #erased_name<'tcx>() #result {
399            #ra_hint
400            #modifiers_stream
401            loop {}
402        }
403    });
404}
405
406pub(super) fn rustc_queries(input: TokenStream) -> TokenStream {
407    let queries = parse_macro_input!(input as List<QueryEntry>);
408
409    let mut query_stream = quote! {};
410    let mut non_query_stream = quote! {};
411    let mut analyzer_stream = quote! {};
412    let mut errors = quote! {};
413
414    macro_rules! assert {
415        ( $cond:expr, $span:expr, $( $tt:tt )+ ) => {
416            if !$cond {
417                errors.extend(
418                    Error::new($span, format!($($tt)+)).into_compile_error(),
419                );
420            }
421        }
422    }
423
424    for query in queries.0 {
425        let query = match query {
426            QueryEntry::Query(query) => query,
427            QueryEntry::NonQuery(NonQuery { doc_comments, name }) => {
428                // Get the exceptional non-query case out of the way first.
429                non_query_stream.extend(quote! {
430                    #(#doc_comments)*
431                    #name,
432                });
433                continue;
434            }
435        };
436
437        let Query { doc_comments, name, key_ty, return_ty, modifiers, .. } = &query;
438
439        // Normalize an absent return type into `-> ()` to make macro-rules parsing easier.
440        let return_ty = match return_ty {
441            ReturnType::Default => quote! { -> () },
442            ReturnType::Type(..) => quote! { #return_ty },
443        };
444
445        let modifiers_stream = make_modifiers_stream(&query);
446
447        // Add the query to the group
448        query_stream.extend(quote! {
449            #(#doc_comments)*
450            fn #name(#key_ty) #return_ty
451            { #modifiers_stream }
452        });
453
454        if let Some(feedable) = &modifiers.feedable {
455            assert!(
456                modifiers.eval_always.is_none(),
457                feedable.span(),
458                "Query {name} cannot be both `feedable` and `eval_always`."
459            );
460        }
461
462        add_to_analyzer_stream(&query, &mut analyzer_stream);
463    }
464
465    TokenStream::from(quote! {
466        /// Higher-order macro that invokes the specified macro with (a) a list of all query
467        /// signatures (including modifiers), and (b) a list of non-query names. This allows
468        /// multiple simpler macros to each have access to these lists.
469        #[rustc_macro_transparency = "semiopaque"] // Use `macro_rules!` hygiene.
470        pub macro rustc_with_all_queries {
471            (
472                // The macro to invoke once, on all queries and non-queries.
473                $macro:ident!
474            ) => {
475                $macro! {
476                    queries { #query_stream }
477                    non_queries { #non_query_stream }
478                }
479            }
480        }
481
482        // Add hints for rust-analyzer
483        mod _analyzer_hints {
484            use super::*;
485            #analyzer_stream
486        }
487
488        #errors
489    })
490}