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