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