]> git.proxmox.com Git - rustc.git/blame - vendor/rustc-ap-rustc_macros/src/query.rs
Update upstream source from tag 'upstream/1.52.1+dfsg1'
[rustc.git] / vendor / rustc-ap-rustc_macros / src / query.rs
CommitLineData
f20569fa
XL
1use proc_macro::TokenStream;
2use proc_macro2::{Delimiter, TokenTree};
3use quote::quote;
4use syn::parse::{Parse, ParseStream, Result};
5use syn::punctuated::Punctuated;
6use syn::spanned::Spanned;
7use syn::{
8 braced, parenthesized, parse_macro_input, parse_quote, AttrStyle, Attribute, Block, Error,
9 Expr, Ident, ReturnType, Token, Type,
10};
11
12mod kw {
13 syn::custom_keyword!(query);
14}
15
16/// Ident or a wildcard `_`.
17struct IdentOrWild(Ident);
18
19impl 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
31enum 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
60impl 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
130fn 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 ... { ... }`
147struct Query {
148 doc_comments: Vec<Attribute>,
149 modifiers: List<QueryModifier>,
150 name: Ident,
151 key: IdentOrWild,
152 arg: Type,
153 result: ReturnType,
154}
155
156impl 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.
180struct List<T>(Vec<T>);
181
182impl<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
192struct 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
222fn 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
341fn 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
433pub 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}