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;
8 braced, parenthesized, parse_macro_input, parse_quote, AttrStyle, Attribute, Block, Error,
9 Expr, Ident, ReturnType, Token, Type,
13 syn::custom_keyword!(query);
16 /// Ident or a wildcard `_`.
17 struct IdentOrWild(Ident);
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()))
25 IdentOrWild(input.parse()?)
30 /// A modifier for a query
32 /// The description of the query.
33 Desc(Option<Ident>, Punctuated<Expr, Token![,]>),
35 /// Use this type for the in-memory cache.
38 /// Cache the query to disk if the `Expr` returns true.
39 Cache(Option<IdentOrWild>, Block),
41 /// Custom code to load the query from disk.
42 LoadCached(Ident, Ident, Block),
44 /// A cycle error for this query aborting the compilation with a fatal error.
47 /// A cycle error results in a delay_bug call
50 /// Don't hash the result, instead just mark a query red if it runs
53 /// Generate a dep node based on the dependencies of the query
56 /// Always evaluate the query, ignoring its dependencies
59 /// Use a separate query provider for local and extern crates
60 SeparateProvideExtern(Ident),
62 /// Always remap the ParamEnv's constness before hashing and passing to the query provider
63 RemapEnvConstness(Ident),
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) }`
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![|]>()?;
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
92 let args = if has_args {
94 parenthesized!(args in input);
95 let tcx = args.parse()?;
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) }`
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" {
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))
132 Err(Error::new(modifier.span(), "unknown query modifier"))
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 {
145 "attributes must be outer attributes (`///`), not inner attributes",
151 attrs.into_iter().map(inner).collect()
154 /// A compiler query. `query ... { ... }`
156 doc_comments: Vec<Attribute>,
157 modifiers: List<QueryModifier>,
164 impl Parse for Query {
165 fn parse(input: ParseStream<'_>) -> Result<Self> {
166 let doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
168 // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>`
169 input.parse::<kw::query>()?;
170 let name: Ident = input.parse()?;
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()?;
178 // Parse the query modifiers
180 braced!(content in input);
181 let modifiers = content.parse()?;
183 Ok(Query { doc_comments, modifiers, name, key, arg, result })
187 /// A type used to greedily parse another type until the input is empty.
188 struct List<T>(Vec<T>);
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()?);
200 struct QueryModifiers {
201 /// The description of the query.
202 desc: (Option<Ident>, Punctuated<Expr, Token![,]>),
204 /// Use this type for the in-memory cache.
205 storage: Option<Type>,
207 /// Cache the query to disk if the `Block` returns true.
208 cache: Option<(Option<IdentOrWild>, Block)>,
210 /// Custom code to load the query from disk.
211 load_cached: Option<(Ident, Ident, Block)>,
213 /// A cycle error for this query aborting the compilation with a fatal error.
214 fatal_cycle: Option<Ident>,
216 /// A cycle error results in a delay_bug call
217 cycle_delay_bug: Option<Ident>,
219 /// Don't hash the result, instead just mark a query red if it runs
220 no_hash: Option<Ident>,
222 /// Generate a dep node based on the dependencies of the query
225 // Always evaluate the query, ignoring its dependencies
226 eval_always: Option<Ident>,
228 /// Use a separate query provider for local and extern crates
229 separate_provide_extern: Option<Ident>,
231 /// Always remap the ParamEnv's constness before hashing.
232 remap_env_constness: Option<Ident>,
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;
241 let mut fatal_cycle = None;
242 let mut cycle_delay_bug = None;
243 let mut no_hash = 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(..) {
250 QueryModifier::LoadCached(tcx, id, block) => {
251 if load_cached.is_some() {
252 panic!("duplicate modifier `load_cached` for query `{}`", query.name);
254 load_cached = Some((tcx, id, block));
256 QueryModifier::Storage(ty) => {
257 if storage.is_some() {
258 panic!("duplicate modifier `storage` for query `{}`", query.name);
262 QueryModifier::Cache(args, expr) => {
264 panic!("duplicate modifier `cache` for query `{}`", query.name);
266 cache = Some((args, expr));
268 QueryModifier::Desc(tcx, list) => {
270 panic!("duplicate modifier `desc` for query `{}`", query.name);
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() {
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
281 _ => panic!("Expected a string literal"),
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;
291 tts.to_string().replace(" . ", "."),
297 let doc_string = format!(
298 "[query description - consider adding a doc-comment!] {}",
301 let comment = parse_quote! {
304 query.doc_comments.push(comment);
306 desc = Some((tcx, list));
308 QueryModifier::FatalCycle(ident) => {
309 if fatal_cycle.is_some() {
310 panic!("duplicate modifier `fatal_cycle` for query `{}`", query.name);
312 fatal_cycle = Some(ident);
314 QueryModifier::CycleDelayBug(ident) => {
315 if cycle_delay_bug.is_some() {
316 panic!("duplicate modifier `cycle_delay_bug` for query `{}`", query.name);
318 cycle_delay_bug = Some(ident);
320 QueryModifier::NoHash(ident) => {
321 if no_hash.is_some() {
322 panic!("duplicate modifier `no_hash` for query `{}`", query.name);
324 no_hash = Some(ident);
326 QueryModifier::Anon(ident) => {
328 panic!("duplicate modifier `anon` for query `{}`", query.name);
332 QueryModifier::EvalAlways(ident) => {
333 if eval_always.is_some() {
334 panic!("duplicate modifier `eval_always` for query `{}`", query.name);
336 eval_always = Some(ident);
338 QueryModifier::SeparateProvideExtern(ident) => {
339 if separate_provide_extern.is_some() {
341 "duplicate modifier `separate_provide_extern` for query `{}`",
345 separate_provide_extern = Some(ident);
347 QueryModifier::RemapEnvConstness(ident) => {
348 if remap_env_constness.is_some() {
349 panic!("duplicate modifier `remap_env_constness` for query `{}`", query.name);
351 remap_env_constness = Some(ident)
355 let desc = desc.unwrap_or_else(|| {
356 panic!("no description provided for query `{}`", query.name);
368 separate_provide_extern,
373 /// Add the impl of QueryDescription for the query to `impls` if one is requested
374 fn add_query_description_impl(
376 modifiers: QueryModifiers,
377 impls: &mut proc_macro2::TokenStream,
379 let name = &query.name;
380 let key = &query.key.0;
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
387 const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>>
388 = Some(|#tcx, #id| { #block });
391 // Use the default code to load the query from disk
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));
404 .unwrap_or_else(|| quote! { _ });
405 // expr is a `Block`, meaning that `{ #expr }` gets expanded
406 // to `{ { stmts... } }`, which triggers the `unused_braces` lint.
408 #[allow(unused_variables, unused_braces)]
410 fn cache_on_disk(#tcx: TyCtxt<'tcx>, #key: &Self::Key) -> bool {
417 if modifiers.load_cached.is_some() {
418 panic!("load_cached modifier on query `{}` without a cache modifier", name);
422 fn cache_on_disk(_: TyCtxt<'tcx>, _: &Self::Key) -> bool {
426 const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>> = None;
430 let (tcx, desc) = modifiers.desc;
431 let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
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!(
443 impls.extend(quote! {
444 (#name<$tcx:tt>) => {
451 pub fn rustc_queries(input: TokenStream) -> TokenStream {
452 let queries = parse_macro_input!(input as List<Query>);
454 let mut query_stream = quote! {};
455 let mut query_description_stream = quote! {};
456 let mut dep_node_def_stream = quote! {};
457 let mut cached_queries = quote! {};
459 for mut query in queries.0 {
460 let modifiers = process_modifiers(&mut query);
461 let name = &query.name;
462 let arg = &query.arg;
463 let result_full = &query.result;
464 let result = match query.result {
465 ReturnType::Default => quote! { -> () },
466 _ => quote! { #result_full },
469 if modifiers.cache.is_some() {
470 cached_queries.extend(quote! {
475 let mut attributes = Vec::new();
477 // Pass on the fatal_cycle modifier
478 if let Some(fatal_cycle) = &modifiers.fatal_cycle {
479 attributes.push(quote! { (#fatal_cycle) });
481 // Pass on the storage modifier
482 if let Some(ref ty) = modifiers.storage {
483 let span = ty.span();
484 attributes.push(quote_spanned! {span=> (storage #ty) });
486 // Pass on the cycle_delay_bug modifier
487 if let Some(cycle_delay_bug) = &modifiers.cycle_delay_bug {
488 attributes.push(quote! { (#cycle_delay_bug) });
490 // Pass on the no_hash modifier
491 if let Some(no_hash) = &modifiers.no_hash {
492 attributes.push(quote! { (#no_hash) });
494 // Pass on the anon modifier
495 if let Some(anon) = &modifiers.anon {
496 attributes.push(quote! { (#anon) });
498 // Pass on the eval_always modifier
499 if let Some(eval_always) = &modifiers.eval_always {
500 attributes.push(quote! { (#eval_always) });
502 // Pass on the separate_provide_extern modifier
503 if let Some(separate_provide_extern) = &modifiers.separate_provide_extern {
504 attributes.push(quote! { (#separate_provide_extern) });
506 // Pass on the remap_env_constness modifier
507 if let Some(remap_env_constness) = &modifiers.remap_env_constness {
508 attributes.push(quote! { (#remap_env_constness) });
511 // This uses the span of the query definition for the commas,
512 // which can be important if we later encounter any ambiguity
513 // errors with any of the numerous macro_rules! macros that
514 // we use. Using the call-site span would result in a span pointing
515 // at the entire `rustc_queries!` invocation, which wouldn't
517 let span = name.span();
518 let attribute_stream = quote_spanned! {span=> #(#attributes),*};
519 let doc_comments = query.doc_comments.iter();
520 // Add the query to the group
521 query_stream.extend(quote! {
523 [#attribute_stream] fn #name(#arg) #result,
526 // Create a dep node for the query
527 dep_node_def_stream.extend(quote! {
528 [#attribute_stream] #name(#arg),
531 add_query_description_impl(&query, modifiers, &mut query_description_stream);
534 TokenStream::from(quote! {
536 macro_rules! rustc_query_append {
537 ([$($macro:tt)*][$($other:tt)*]) => {
546 macro_rules! rustc_dep_node_append {
547 ([$($macro:tt)*][$($other:tt)*]) => {
556 macro_rules! rustc_cached_queries {
558 $($macro)*(#cached_queries);
562 macro_rules! rustc_query_description {
563 #query_description_stream