]>
Commit | Line | Data |
---|---|---|
532ac7d7 | 1 | use proc_macro::TokenStream; |
94222f64 | 2 | use quote::{quote, quote_spanned}; |
dfeec247 XL |
3 | use syn::parse::{Parse, ParseStream, Result}; |
4 | use syn::punctuated::Punctuated; | |
5 | use syn::spanned::Spanned; | |
532ac7d7 | 6 | use syn::{ |
f2b60f7d FG |
7 | braced, parenthesized, parse_macro_input, parse_quote, token, AttrStyle, Attribute, Block, |
8 | Error, Expr, Ident, Pat, ReturnType, Token, Type, | |
532ac7d7 | 9 | }; |
532ac7d7 | 10 | |
532ac7d7 XL |
11 | mod kw { |
12 | syn::custom_keyword!(query); | |
13 | } | |
14 | ||
532ac7d7 | 15 | /// Ensures only doc comment attributes are used |
1b1a35ee XL |
16 | fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> { |
17 | let inner = |attr: Attribute| { | |
532ac7d7 | 18 | if !attr.path.is_ident("doc") { |
1b1a35ee XL |
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) | |
532ac7d7 | 27 | } |
1b1a35ee XL |
28 | }; |
29 | attrs.into_iter().map(inner).collect() | |
532ac7d7 XL |
30 | } |
31 | ||
32 | /// A compiler query. `query ... { ... }` | |
33 | struct Query { | |
1b1a35ee | 34 | doc_comments: Vec<Attribute>, |
f2b60f7d | 35 | modifiers: QueryModifiers, |
532ac7d7 | 36 | name: Ident, |
f2b60f7d | 37 | key: Pat, |
532ac7d7 XL |
38 | arg: Type, |
39 | result: ReturnType, | |
40 | } | |
41 | ||
42 | impl Parse for Query { | |
43 | fn parse(input: ParseStream<'_>) -> Result<Self> { | |
f2b60f7d | 44 | let mut doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?; |
532ac7d7 XL |
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); | |
f2b60f7d FG |
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 | } | |
532ac7d7 | 66 | |
1b1a35ee | 67 | Ok(Query { doc_comments, modifiers, name, key, arg, result }) |
532ac7d7 XL |
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 | ||
532ac7d7 XL |
84 | struct QueryModifiers { |
85 | /// The description of the query. | |
f9f354fc | 86 | desc: (Option<Ident>, Punctuated<Expr, Token![,]>), |
532ac7d7 | 87 | |
74b04a01 | 88 | /// Use this type for the in-memory cache. |
f2b60f7d | 89 | arena_cache: Option<Ident>, |
74b04a01 | 90 | |
dc9dc135 | 91 | /// Cache the query to disk if the `Block` returns true. |
f2b60f7d | 92 | cache: Option<(Option<Pat>, Block)>, |
532ac7d7 XL |
93 | |
94 | /// A cycle error for this query aborting the compilation with a fatal error. | |
94222f64 | 95 | fatal_cycle: Option<Ident>, |
532ac7d7 XL |
96 | |
97 | /// A cycle error results in a delay_bug call | |
94222f64 | 98 | cycle_delay_bug: Option<Ident>, |
532ac7d7 XL |
99 | |
100 | /// Don't hash the result, instead just mark a query red if it runs | |
94222f64 | 101 | no_hash: Option<Ident>, |
532ac7d7 | 102 | |
532ac7d7 | 103 | /// Generate a dep node based on the dependencies of the query |
94222f64 | 104 | anon: Option<Ident>, |
532ac7d7 | 105 | |
f2b60f7d | 106 | /// Always evaluate the query, ignoring its dependencies |
94222f64 | 107 | eval_always: Option<Ident>, |
3c0e092e | 108 | |
f2b60f7d FG |
109 | /// Whether the query has a call depth limit |
110 | depth_limit: Option<Ident>, | |
111 | ||
3c0e092e XL |
112 | /// Use a separate query provider for local and extern crates |
113 | separate_provide_extern: Option<Ident>, | |
a2a8927a XL |
114 | |
115 | /// Always remap the ParamEnv's constness before hashing. | |
116 | remap_env_constness: Option<Ident>, | |
487cf647 FG |
117 | |
118 | /// Generate a `feed` method to set the query's value from another query. | |
119 | feedable: Option<Ident>, | |
532ac7d7 XL |
120 | } |
121 | ||
f2b60f7d FG |
122 | fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> { |
123 | let mut arena_cache = None; | |
532ac7d7 XL |
124 | let mut cache = None; |
125 | let mut desc = None; | |
94222f64 XL |
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; | |
f2b60f7d | 131 | let mut depth_limit = None; |
3c0e092e | 132 | let mut separate_provide_extern = None; |
a2a8927a | 133 | let mut remap_env_constness = None; |
487cf647 | 134 | let mut feedable = None; |
f2b60f7d FG |
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")); | |
a2a8927a | 143 | } |
f2b60f7d FG |
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); | |
487cf647 FG |
194 | } else if modifier == "feedable" { |
195 | try_insert!(feedable = modifier); | |
f2b60f7d FG |
196 | } else { |
197 | return Err(Error::new(modifier.span(), "unknown query modifier")); | |
532ac7d7 XL |
198 | } |
199 | } | |
f2b60f7d FG |
200 | let Some(desc) = desc else { |
201 | return Err(input.error("no description provided")); | |
202 | }; | |
203 | Ok(QueryModifiers { | |
204 | arena_cache, | |
532ac7d7 XL |
205 | cache, |
206 | desc, | |
207 | fatal_cycle, | |
208 | cycle_delay_bug, | |
209 | no_hash, | |
532ac7d7 XL |
210 | anon, |
211 | eval_always, | |
f2b60f7d | 212 | depth_limit, |
3c0e092e | 213 | separate_provide_extern, |
a2a8927a | 214 | remap_env_constness, |
487cf647 | 215 | feedable, |
f2b60f7d FG |
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] }) | |
532ac7d7 XL |
244 | } |
245 | ||
246 | /// Add the impl of QueryDescription for the query to `impls` if one is requested | |
2b03887a FG |
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; | |
532ac7d7 XL |
253 | |
254 | // Find out if we should cache the query on disk | |
f9f354fc | 255 | let cache = if let Some((args, expr)) = modifiers.cache.as_ref() { |
f2b60f7d | 256 | let tcx = args.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ }); |
ba9703b0 XL |
257 | // expr is a `Block`, meaning that `{ #expr }` gets expanded |
258 | // to `{ { stmts... } }`, which triggers the `unused_braces` lint. | |
2b03887a | 259 | // we're taking `key` by reference, but some rustc types usually prefer being passed by value |
532ac7d7 | 260 | quote! { |
2b03887a | 261 | #[allow(unused_variables, unused_braces, rustc::pass_by_value)] |
3c0e092e | 262 | #[inline] |
2b03887a | 263 | pub fn #name<'tcx>(#tcx: TyCtxt<'tcx>, #key: &crate::ty::query::query_keys::#name<'tcx>) -> bool { |
532ac7d7 XL |
264 | #expr |
265 | } | |
532ac7d7 | 266 | } |
f9f354fc | 267 | } else { |
3c0e092e | 268 | quote! { |
2b03887a FG |
269 | // we're taking `key` by reference, but some rustc types usually prefer being passed by value |
270 | #[allow(rustc::pass_by_value)] | |
3c0e092e | 271 | #[inline] |
2b03887a | 272 | pub fn #name<'tcx>(_: TyCtxt<'tcx>, _: &crate::ty::query::query_keys::#name<'tcx>) -> bool { |
3c0e092e XL |
273 | false |
274 | } | |
3c0e092e | 275 | } |
f9f354fc XL |
276 | }; |
277 | ||
f2b60f7d | 278 | let (tcx, desc) = &modifiers.desc; |
6a06907d | 279 | let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t }); |
f9f354fc XL |
280 | |
281 | let desc = quote! { | |
282 | #[allow(unused_variables)] | |
2b03887a FG |
283 | pub fn #name<'tcx>(tcx: TyCtxt<'tcx>, key: crate::ty::query::query_keys::#name<'tcx>) -> String { |
284 | let (#tcx, #key) = (tcx, key); | |
5e7ed085 FG |
285 | ::rustc_middle::ty::print::with_no_trimmed_paths!( |
286 | format!(#desc) | |
287 | ) | |
f9f354fc XL |
288 | } |
289 | }; | |
532ac7d7 | 290 | |
2b03887a FG |
291 | descs.extend(quote! { |
292 | #desc | |
293 | }); | |
294 | ||
295 | cached.extend(quote! { | |
296 | #cache | |
532ac7d7 | 297 | }); |
532ac7d7 XL |
298 | } |
299 | ||
300 | pub fn rustc_queries(input: TokenStream) -> TokenStream { | |
5869c6ff | 301 | let queries = parse_macro_input!(input as List<Query>); |
532ac7d7 XL |
302 | |
303 | let mut query_stream = quote! {}; | |
304 | let mut query_description_stream = quote! {}; | |
2b03887a | 305 | let mut query_cached_stream = quote! {}; |
487cf647 | 306 | let mut feedable_queries = quote! {}; |
532ac7d7 | 307 | |
f2b60f7d FG |
308 | for query in queries.0 { |
309 | let Query { name, arg, modifiers, .. } = &query; | |
5869c6ff XL |
310 | let result_full = &query.result; |
311 | let result = match query.result { | |
312 | ReturnType::Default => quote! { -> () }, | |
313 | _ => quote! { #result_full }, | |
314 | }; | |
532ac7d7 | 315 | |
5869c6ff | 316 | let mut attributes = Vec::new(); |
532ac7d7 | 317 | |
f2b60f7d FG |
318 | macro_rules! passthrough { |
319 | ( $( $modifier:ident ),+ $(,)? ) => { | |
320 | $( if let Some($modifier) = &modifiers.$modifier { | |
321 | attributes.push(quote! { (#$modifier) }); | |
322 | }; )+ | |
323 | } | |
3c0e092e | 324 | } |
f2b60f7d FG |
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) }); | |
a2a8927a | 344 | } |
532ac7d7 | 345 | |
94222f64 XL |
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),*}; | |
f2b60f7d | 354 | let doc_comments = &query.doc_comments; |
5869c6ff XL |
355 | // Add the query to the group |
356 | query_stream.extend(quote! { | |
357 | #(#doc_comments)* | |
358 | [#attribute_stream] fn #name(#arg) #result, | |
359 | }); | |
dc9dc135 | 360 | |
487cf647 FG |
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 | ||
2b03887a | 373 | add_query_desc_cached_impl(&query, &mut query_description_stream, &mut query_cached_stream); |
532ac7d7 XL |
374 | } |
375 | ||
532ac7d7 | 376 | TokenStream::from(quote! { |
6a06907d | 377 | #[macro_export] |
532ac7d7 | 378 | macro_rules! rustc_query_append { |
f2b60f7d FG |
379 | ($macro:ident! $( [$($other:tt)*] )?) => { |
380 | $macro! { | |
381 | $( $($other)* )? | |
532ac7d7 | 382 | #query_stream |
532ac7d7 XL |
383 | } |
384 | } | |
385 | } | |
487cf647 FG |
386 | macro_rules! rustc_feedable_queries { |
387 | ( $macro:ident! ) => { | |
388 | $macro!(#feedable_queries); | |
389 | } | |
390 | } | |
2b03887a FG |
391 | pub mod descs { |
392 | use super::*; | |
3c0e092e | 393 | #query_description_stream |
6a06907d | 394 | } |
2b03887a FG |
395 | pub mod cached { |
396 | use super::*; | |
397 | #query_cached_stream | |
398 | } | |
532ac7d7 XL |
399 | }) |
400 | } |