]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_macros/src/query.rs
Auto merge of #106275 - Nilstrieb:const-eval-select-me-some-compile-time, r=thomcc
[rust.git] / compiler / rustc_macros / src / query.rs
1 use proc_macro::TokenStream;
2 use quote::{quote, quote_spanned};
3 use syn::parse::{Parse, ParseStream, Result};
4 use syn::punctuated::Punctuated;
5 use syn::spanned::Spanned;
6 use syn::{
7     braced, parenthesized, parse_macro_input, parse_quote, token, AttrStyle, Attribute, Block,
8     Error, Expr, Ident, Pat, ReturnType, Token, Type,
9 };
10
11 mod kw {
12     syn::custom_keyword!(query);
13 }
14
15 /// Ensures only doc comment attributes are used
16 fn 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 ... { ... }`
33 struct Query {
34     doc_comments: Vec<Attribute>,
35     modifiers: QueryModifiers,
36     name: Ident,
37     key: Pat,
38     arg: Type,
39     result: ReturnType,
40 }
41
42 impl 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 = arg_content.parse()?;
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.
72 struct List<T>(Vec<T>);
73
74 impl<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
84 struct 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     /// Don't hash the result, instead just mark a query red if it runs
101     no_hash: Option<Ident>,
102
103     /// Generate a dep node based on the dependencies of the query
104     anon: Option<Ident>,
105
106     /// Always evaluate the query, ignoring its dependencies
107     eval_always: Option<Ident>,
108
109     /// Whether the query has a call depth limit
110     depth_limit: Option<Ident>,
111
112     /// Use a separate query provider for local and extern crates
113     separate_provide_extern: Option<Ident>,
114
115     /// Always remap the ParamEnv's constness before hashing.
116     remap_env_constness: Option<Ident>,
117
118     /// Generate a `feed` method to set the query's value from another query.
119     feedable: Option<Ident>,
120 }
121
122 fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
123     let mut arena_cache = None;
124     let mut cache = None;
125     let mut desc = None;
126     let mut fatal_cycle = None;
127     let mut cycle_delay_bug = None;
128     let mut no_hash = None;
129     let mut anon = None;
130     let mut eval_always = None;
131     let mut depth_limit = None;
132     let mut separate_provide_extern = None;
133     let mut remap_env_constness = None;
134     let mut feedable = None;
135
136     while !input.is_empty() {
137         let modifier: Ident = input.parse()?;
138
139         macro_rules! try_insert {
140             ($name:ident = $expr:expr) => {
141                 if $name.is_some() {
142                     return Err(Error::new(modifier.span(), "duplicate modifier"));
143                 }
144                 $name = Some($expr);
145             };
146         }
147
148         if modifier == "desc" {
149             // Parse a description modifier like:
150             // `desc { |tcx| "foo {}", tcx.item_path(key) }`
151             let attr_content;
152             braced!(attr_content in input);
153             let tcx = if attr_content.peek(Token![|]) {
154                 attr_content.parse::<Token![|]>()?;
155                 let tcx = attr_content.parse()?;
156                 attr_content.parse::<Token![|]>()?;
157                 Some(tcx)
158             } else {
159                 None
160             };
161             let list = attr_content.parse_terminated(Expr::parse)?;
162             try_insert!(desc = (tcx, list));
163         } else if modifier == "cache_on_disk_if" {
164             // Parse a cache modifier like:
165             // `cache(tcx) { |tcx| key.is_local() }`
166             let args = if input.peek(token::Paren) {
167                 let args;
168                 parenthesized!(args in input);
169                 let tcx = args.parse()?;
170                 Some(tcx)
171             } else {
172                 None
173             };
174             let block = input.parse()?;
175             try_insert!(cache = (args, block));
176         } else if modifier == "arena_cache" {
177             try_insert!(arena_cache = modifier);
178         } else if modifier == "fatal_cycle" {
179             try_insert!(fatal_cycle = modifier);
180         } else if modifier == "cycle_delay_bug" {
181             try_insert!(cycle_delay_bug = modifier);
182         } else if modifier == "no_hash" {
183             try_insert!(no_hash = modifier);
184         } else if modifier == "anon" {
185             try_insert!(anon = modifier);
186         } else if modifier == "eval_always" {
187             try_insert!(eval_always = modifier);
188         } else if modifier == "depth_limit" {
189             try_insert!(depth_limit = modifier);
190         } else if modifier == "separate_provide_extern" {
191             try_insert!(separate_provide_extern = modifier);
192         } else if modifier == "remap_env_constness" {
193             try_insert!(remap_env_constness = modifier);
194         } else if modifier == "feedable" {
195             try_insert!(feedable = modifier);
196         } else {
197             return Err(Error::new(modifier.span(), "unknown query modifier"));
198         }
199     }
200     let Some(desc) = desc else {
201         return Err(input.error("no description provided"));
202     };
203     Ok(QueryModifiers {
204         arena_cache,
205         cache,
206         desc,
207         fatal_cycle,
208         cycle_delay_bug,
209         no_hash,
210         anon,
211         eval_always,
212         depth_limit,
213         separate_provide_extern,
214         remap_env_constness,
215         feedable,
216     })
217 }
218
219 fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
220     use ::syn::*;
221     let mut iter = list.iter();
222     let format_str: String = match iter.next() {
223         Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
224             lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
225         }
226         _ => return Err(Error::new(list.span(), "Expected a string literal")),
227     };
228     let mut fmt_fragments = format_str.split("{}");
229     let mut doc_string = fmt_fragments.next().unwrap().to_string();
230     iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
231         |(tts, next_fmt_fragment)| {
232             use ::core::fmt::Write;
233             write!(
234                 &mut doc_string,
235                 " `{}` {}",
236                 tts.to_string().replace(" . ", "."),
237                 next_fmt_fragment,
238             )
239             .unwrap();
240         },
241     );
242     let doc_string = format!("[query description - consider adding a doc-comment!] {}", doc_string);
243     Ok(parse_quote! { #[doc = #doc_string] })
244 }
245
246 /// Add the impl of QueryDescription for the query to `impls` if one is requested
247 fn add_query_desc_cached_impl(
248     query: &Query,
249     descs: &mut proc_macro2::TokenStream,
250     cached: &mut proc_macro2::TokenStream,
251 ) {
252     let Query { name, key, modifiers, .. } = &query;
253
254     // Find out if we should cache the query on disk
255     let cache = if let Some((args, expr)) = modifiers.cache.as_ref() {
256         let tcx = args.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ });
257         // expr is a `Block`, meaning that `{ #expr }` gets expanded
258         // to `{ { stmts... } }`, which triggers the `unused_braces` lint.
259         // we're taking `key` by reference, but some rustc types usually prefer being passed by value
260         quote! {
261             #[allow(unused_variables, unused_braces, rustc::pass_by_value)]
262             #[inline]
263             pub fn #name<'tcx>(#tcx: TyCtxt<'tcx>, #key: &crate::ty::query::query_keys::#name<'tcx>) -> bool {
264                 #expr
265             }
266         }
267     } else {
268         quote! {
269             // we're taking `key` by reference, but some rustc types usually prefer being passed by value
270             #[allow(rustc::pass_by_value)]
271             #[inline]
272             pub fn #name<'tcx>(_: TyCtxt<'tcx>, _: &crate::ty::query::query_keys::#name<'tcx>) -> bool {
273                 false
274             }
275         }
276     };
277
278     let (tcx, desc) = &modifiers.desc;
279     let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
280
281     let desc = quote! {
282         #[allow(unused_variables)]
283         pub fn #name<'tcx>(tcx: TyCtxt<'tcx>, key: crate::ty::query::query_keys::#name<'tcx>) -> String {
284             let (#tcx, #key) = (tcx, key);
285             ::rustc_middle::ty::print::with_no_trimmed_paths!(
286                 format!(#desc)
287             )
288         }
289     };
290
291     descs.extend(quote! {
292         #desc
293     });
294
295     cached.extend(quote! {
296         #cache
297     });
298 }
299
300 pub fn rustc_queries(input: TokenStream) -> TokenStream {
301     let queries = parse_macro_input!(input as List<Query>);
302
303     let mut query_stream = quote! {};
304     let mut query_description_stream = quote! {};
305     let mut query_cached_stream = quote! {};
306     let mut feedable_queries = quote! {};
307
308     for query in queries.0 {
309         let Query { name, arg, modifiers, .. } = &query;
310         let result_full = &query.result;
311         let result = match query.result {
312             ReturnType::Default => quote! { -> () },
313             _ => quote! { #result_full },
314         };
315
316         let mut attributes = Vec::new();
317
318         macro_rules! passthrough {
319             ( $( $modifier:ident ),+ $(,)? ) => {
320                 $( if let Some($modifier) = &modifiers.$modifier {
321                     attributes.push(quote! { (#$modifier) });
322                 }; )+
323             }
324         }
325
326         passthrough!(
327             fatal_cycle,
328             arena_cache,
329             cycle_delay_bug,
330             no_hash,
331             anon,
332             eval_always,
333             depth_limit,
334             separate_provide_extern,
335             remap_env_constness,
336         );
337
338         if modifiers.cache.is_some() {
339             attributes.push(quote! { (cache) });
340         }
341         // Pass on the cache modifier
342         if modifiers.cache.is_some() {
343             attributes.push(quote! { (cache) });
344         }
345
346         // This uses the span of the query definition for the commas,
347         // which can be important if we later encounter any ambiguity
348         // errors with any of the numerous macro_rules! macros that
349         // we use. Using the call-site span would result in a span pointing
350         // at the entire `rustc_queries!` invocation, which wouldn't
351         // be very useful.
352         let span = name.span();
353         let attribute_stream = quote_spanned! {span=> #(#attributes),*};
354         let doc_comments = &query.doc_comments;
355         // Add the query to the group
356         query_stream.extend(quote! {
357             #(#doc_comments)*
358             [#attribute_stream] fn #name(#arg) #result,
359         });
360
361         if modifiers.feedable.is_some() {
362             assert!(modifiers.anon.is_none(), "Query {name} cannot be both `feedable` and `anon`.");
363             assert!(
364                 modifiers.eval_always.is_none(),
365                 "Query {name} cannot be both `feedable` and `eval_always`."
366             );
367             feedable_queries.extend(quote! {
368                 #(#doc_comments)*
369                 [#attribute_stream] fn #name(#arg) #result,
370             });
371         }
372
373         add_query_desc_cached_impl(&query, &mut query_description_stream, &mut query_cached_stream);
374     }
375
376     TokenStream::from(quote! {
377         #[macro_export]
378         macro_rules! rustc_query_append {
379             ($macro:ident! $( [$($other:tt)*] )?) => {
380                 $macro! {
381                     $( $($other)* )?
382                     #query_stream
383                 }
384             }
385         }
386         macro_rules! rustc_feedable_queries {
387             ( $macro:ident! ) => {
388                 $macro!(#feedable_queries);
389             }
390         }
391         pub mod descs {
392             use super::*;
393             #query_description_stream
394         }
395         pub mod cached {
396             use super::*;
397             #query_cached_stream
398         }
399     })
400 }