]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_macros/src/query.rs
Auto merge of #104054 - RalfJung:byte-provenance, r=oli-obk
[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
119 fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
120     let mut arena_cache = None;
121     let mut cache = None;
122     let mut desc = None;
123     let mut fatal_cycle = None;
124     let mut cycle_delay_bug = None;
125     let mut no_hash = None;
126     let mut anon = None;
127     let mut eval_always = None;
128     let mut depth_limit = None;
129     let mut separate_provide_extern = None;
130     let mut remap_env_constness = None;
131
132     while !input.is_empty() {
133         let modifier: Ident = input.parse()?;
134
135         macro_rules! try_insert {
136             ($name:ident = $expr:expr) => {
137                 if $name.is_some() {
138                     return Err(Error::new(modifier.span(), "duplicate modifier"));
139                 }
140                 $name = Some($expr);
141             };
142         }
143
144         if modifier == "desc" {
145             // Parse a description modifier like:
146             // `desc { |tcx| "foo {}", tcx.item_path(key) }`
147             let attr_content;
148             braced!(attr_content in input);
149             let tcx = if attr_content.peek(Token![|]) {
150                 attr_content.parse::<Token![|]>()?;
151                 let tcx = attr_content.parse()?;
152                 attr_content.parse::<Token![|]>()?;
153                 Some(tcx)
154             } else {
155                 None
156             };
157             let list = attr_content.parse_terminated(Expr::parse)?;
158             try_insert!(desc = (tcx, list));
159         } else if modifier == "cache_on_disk_if" {
160             // Parse a cache modifier like:
161             // `cache(tcx) { |tcx| key.is_local() }`
162             let args = if input.peek(token::Paren) {
163                 let args;
164                 parenthesized!(args in input);
165                 let tcx = args.parse()?;
166                 Some(tcx)
167             } else {
168                 None
169             };
170             let block = input.parse()?;
171             try_insert!(cache = (args, block));
172         } else if modifier == "arena_cache" {
173             try_insert!(arena_cache = modifier);
174         } else if modifier == "fatal_cycle" {
175             try_insert!(fatal_cycle = modifier);
176         } else if modifier == "cycle_delay_bug" {
177             try_insert!(cycle_delay_bug = modifier);
178         } else if modifier == "no_hash" {
179             try_insert!(no_hash = modifier);
180         } else if modifier == "anon" {
181             try_insert!(anon = modifier);
182         } else if modifier == "eval_always" {
183             try_insert!(eval_always = modifier);
184         } else if modifier == "depth_limit" {
185             try_insert!(depth_limit = modifier);
186         } else if modifier == "separate_provide_extern" {
187             try_insert!(separate_provide_extern = modifier);
188         } else if modifier == "remap_env_constness" {
189             try_insert!(remap_env_constness = modifier);
190         } else {
191             return Err(Error::new(modifier.span(), "unknown query modifier"));
192         }
193     }
194     let Some(desc) = desc else {
195         return Err(input.error("no description provided"));
196     };
197     Ok(QueryModifiers {
198         arena_cache,
199         cache,
200         desc,
201         fatal_cycle,
202         cycle_delay_bug,
203         no_hash,
204         anon,
205         eval_always,
206         depth_limit,
207         separate_provide_extern,
208         remap_env_constness,
209     })
210 }
211
212 fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
213     use ::syn::*;
214     let mut iter = list.iter();
215     let format_str: String = match iter.next() {
216         Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
217             lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
218         }
219         _ => return Err(Error::new(list.span(), "Expected a string literal")),
220     };
221     let mut fmt_fragments = format_str.split("{}");
222     let mut doc_string = fmt_fragments.next().unwrap().to_string();
223     iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
224         |(tts, next_fmt_fragment)| {
225             use ::core::fmt::Write;
226             write!(
227                 &mut doc_string,
228                 " `{}` {}",
229                 tts.to_string().replace(" . ", "."),
230                 next_fmt_fragment,
231             )
232             .unwrap();
233         },
234     );
235     let doc_string = format!("[query description - consider adding a doc-comment!] {}", doc_string);
236     Ok(parse_quote! { #[doc = #doc_string] })
237 }
238
239 /// Add the impl of QueryDescription for the query to `impls` if one is requested
240 fn add_query_desc_cached_impl(
241     query: &Query,
242     descs: &mut proc_macro2::TokenStream,
243     cached: &mut proc_macro2::TokenStream,
244 ) {
245     let Query { name, key, modifiers, .. } = &query;
246
247     // Find out if we should cache the query on disk
248     let cache = if let Some((args, expr)) = modifiers.cache.as_ref() {
249         let tcx = args.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ });
250         // expr is a `Block`, meaning that `{ #expr }` gets expanded
251         // to `{ { stmts... } }`, which triggers the `unused_braces` lint.
252         // we're taking `key` by reference, but some rustc types usually prefer being passed by value
253         quote! {
254             #[allow(unused_variables, unused_braces, rustc::pass_by_value)]
255             #[inline]
256             pub fn #name<'tcx>(#tcx: TyCtxt<'tcx>, #key: &crate::ty::query::query_keys::#name<'tcx>) -> bool {
257                 #expr
258             }
259         }
260     } else {
261         quote! {
262             // we're taking `key` by reference, but some rustc types usually prefer being passed by value
263             #[allow(rustc::pass_by_value)]
264             #[inline]
265             pub fn #name<'tcx>(_: TyCtxt<'tcx>, _: &crate::ty::query::query_keys::#name<'tcx>) -> bool {
266                 false
267             }
268         }
269     };
270
271     let (tcx, desc) = &modifiers.desc;
272     let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
273
274     let desc = quote! {
275         #[allow(unused_variables)]
276         pub fn #name<'tcx>(tcx: TyCtxt<'tcx>, key: crate::ty::query::query_keys::#name<'tcx>) -> String {
277             let (#tcx, #key) = (tcx, key);
278             ::rustc_middle::ty::print::with_no_trimmed_paths!(
279                 format!(#desc)
280             )
281         }
282     };
283
284     descs.extend(quote! {
285         #desc
286     });
287
288     cached.extend(quote! {
289         #cache
290     });
291 }
292
293 pub fn rustc_queries(input: TokenStream) -> TokenStream {
294     let queries = parse_macro_input!(input as List<Query>);
295
296     let mut query_stream = quote! {};
297     let mut query_description_stream = quote! {};
298     let mut query_cached_stream = quote! {};
299
300     for query in queries.0 {
301         let Query { name, arg, modifiers, .. } = &query;
302         let result_full = &query.result;
303         let result = match query.result {
304             ReturnType::Default => quote! { -> () },
305             _ => quote! { #result_full },
306         };
307
308         let mut attributes = Vec::new();
309
310         macro_rules! passthrough {
311             ( $( $modifier:ident ),+ $(,)? ) => {
312                 $( if let Some($modifier) = &modifiers.$modifier {
313                     attributes.push(quote! { (#$modifier) });
314                 }; )+
315             }
316         }
317
318         passthrough!(
319             fatal_cycle,
320             arena_cache,
321             cycle_delay_bug,
322             no_hash,
323             anon,
324             eval_always,
325             depth_limit,
326             separate_provide_extern,
327             remap_env_constness,
328         );
329
330         if modifiers.cache.is_some() {
331             attributes.push(quote! { (cache) });
332         }
333         // Pass on the cache modifier
334         if modifiers.cache.is_some() {
335             attributes.push(quote! { (cache) });
336         }
337
338         // This uses the span of the query definition for the commas,
339         // which can be important if we later encounter any ambiguity
340         // errors with any of the numerous macro_rules! macros that
341         // we use. Using the call-site span would result in a span pointing
342         // at the entire `rustc_queries!` invocation, which wouldn't
343         // be very useful.
344         let span = name.span();
345         let attribute_stream = quote_spanned! {span=> #(#attributes),*};
346         let doc_comments = &query.doc_comments;
347         // Add the query to the group
348         query_stream.extend(quote! {
349             #(#doc_comments)*
350             [#attribute_stream] fn #name(#arg) #result,
351         });
352
353         add_query_desc_cached_impl(&query, &mut query_description_stream, &mut query_cached_stream);
354     }
355
356     TokenStream::from(quote! {
357         #[macro_export]
358         macro_rules! rustc_query_append {
359             ($macro:ident! $( [$($other:tt)*] )?) => {
360                 $macro! {
361                     $( $($other)* )?
362                     #query_stream
363                 }
364             }
365         }
366
367         pub mod descs {
368             use super::*;
369             #query_description_stream
370         }
371         pub mod cached {
372             use super::*;
373             #query_cached_stream
374         }
375     })
376 }