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