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),
63 impl Parse for QueryModifier {
64 fn parse(input: ParseStream<'_>) -> Result<Self> {
65 let modifier: Ident = input.parse()?;
66 if modifier == "desc" {
67 // Parse a description modifier like:
68 // `desc { |tcx| "foo {}", tcx.item_path(key) }`
70 braced!(attr_content in input);
71 let tcx = if attr_content.peek(Token![|]) {
72 attr_content.parse::<Token![|]>()?;
73 let tcx = attr_content.parse()?;
74 attr_content.parse::<Token![|]>()?;
79 let desc = attr_content.parse_terminated(Expr::parse)?;
80 Ok(QueryModifier::Desc(tcx, desc))
81 } else if modifier == "cache_on_disk_if" {
82 // Parse a cache modifier like:
83 // `cache(tcx, value) { |tcx| key.is_local() }`
84 let has_args = if let TokenTree::Group(group) = input.fork().parse()? {
85 group.delimiter() == Delimiter::Parenthesis
89 let args = if has_args {
91 parenthesized!(args in input);
92 let tcx = args.parse()?;
97 let block = input.parse()?;
98 Ok(QueryModifier::Cache(args, block))
99 } else if modifier == "load_cached" {
100 // Parse a load_cached modifier like:
101 // `load_cached(tcx, id) { tcx.on_disk_cache.try_load_query_result(tcx, id) }`
103 parenthesized!(args in input);
104 let tcx = args.parse()?;
105 args.parse::<Token![,]>()?;
106 let id = args.parse()?;
107 let block = input.parse()?;
108 Ok(QueryModifier::LoadCached(tcx, id, block))
109 } else if modifier == "storage" {
111 parenthesized!(args in input);
112 let ty = args.parse()?;
113 Ok(QueryModifier::Storage(ty))
114 } else if modifier == "fatal_cycle" {
115 Ok(QueryModifier::FatalCycle(modifier))
116 } else if modifier == "cycle_delay_bug" {
117 Ok(QueryModifier::CycleDelayBug(modifier))
118 } else if modifier == "no_hash" {
119 Ok(QueryModifier::NoHash(modifier))
120 } else if modifier == "anon" {
121 Ok(QueryModifier::Anon(modifier))
122 } else if modifier == "eval_always" {
123 Ok(QueryModifier::EvalAlways(modifier))
124 } else if modifier == "separate_provide_extern" {
125 Ok(QueryModifier::SeparateProvideExtern(modifier))
127 Err(Error::new(modifier.span(), "unknown query modifier"))
132 /// Ensures only doc comment attributes are used
133 fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
134 let inner = |attr: Attribute| {
135 if !attr.path.is_ident("doc") {
136 Err(Error::new(attr.span(), "attributes not supported on queries"))
137 } else if attr.style != AttrStyle::Outer {
140 "attributes must be outer attributes (`///`), not inner attributes",
146 attrs.into_iter().map(inner).collect()
149 /// A compiler query. `query ... { ... }`
151 doc_comments: Vec<Attribute>,
152 modifiers: List<QueryModifier>,
159 impl Parse for Query {
160 fn parse(input: ParseStream<'_>) -> Result<Self> {
161 let doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
163 // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>`
164 input.parse::<kw::query>()?;
165 let name: Ident = input.parse()?;
167 parenthesized!(arg_content in input);
168 let key = arg_content.parse()?;
169 arg_content.parse::<Token![:]>()?;
170 let arg = arg_content.parse()?;
171 let result = input.parse()?;
173 // Parse the query modifiers
175 braced!(content in input);
176 let modifiers = content.parse()?;
178 Ok(Query { doc_comments, modifiers, name, key, arg, result })
182 /// A type used to greedily parse another type until the input is empty.
183 struct List<T>(Vec<T>);
185 impl<T: Parse> Parse for List<T> {
186 fn parse(input: ParseStream<'_>) -> Result<Self> {
187 let mut list = Vec::new();
188 while !input.is_empty() {
189 list.push(input.parse()?);
195 struct QueryModifiers {
196 /// The description of the query.
197 desc: (Option<Ident>, Punctuated<Expr, Token![,]>),
199 /// Use this type for the in-memory cache.
200 storage: Option<Type>,
202 /// Cache the query to disk if the `Block` returns true.
203 cache: Option<(Option<IdentOrWild>, Block)>,
205 /// Custom code to load the query from disk.
206 load_cached: Option<(Ident, Ident, Block)>,
208 /// A cycle error for this query aborting the compilation with a fatal error.
209 fatal_cycle: Option<Ident>,
211 /// A cycle error results in a delay_bug call
212 cycle_delay_bug: Option<Ident>,
214 /// Don't hash the result, instead just mark a query red if it runs
215 no_hash: Option<Ident>,
217 /// Generate a dep node based on the dependencies of the query
220 // Always evaluate the query, ignoring its dependencies
221 eval_always: Option<Ident>,
223 /// Use a separate query provider for local and extern crates
224 separate_provide_extern: Option<Ident>,
227 /// Process query modifiers into a struct, erroring on duplicates
228 fn process_modifiers(query: &mut Query) -> QueryModifiers {
229 let mut load_cached = None;
230 let mut storage = None;
231 let mut cache = None;
233 let mut fatal_cycle = None;
234 let mut cycle_delay_bug = None;
235 let mut no_hash = None;
237 let mut eval_always = None;
238 let mut separate_provide_extern = None;
239 for modifier in query.modifiers.0.drain(..) {
241 QueryModifier::LoadCached(tcx, id, block) => {
242 if load_cached.is_some() {
243 panic!("duplicate modifier `load_cached` for query `{}`", query.name);
245 load_cached = Some((tcx, id, block));
247 QueryModifier::Storage(ty) => {
248 if storage.is_some() {
249 panic!("duplicate modifier `storage` for query `{}`", query.name);
253 QueryModifier::Cache(args, expr) => {
255 panic!("duplicate modifier `cache` for query `{}`", query.name);
257 cache = Some((args, expr));
259 QueryModifier::Desc(tcx, list) => {
261 panic!("duplicate modifier `desc` for query `{}`", query.name);
263 // If there are no doc-comments, give at least some idea of what
264 // it does by showing the query description.
265 if query.doc_comments.is_empty() {
267 let mut list = list.iter();
268 let format_str: String = match list.next() {
269 Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
270 lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
272 _ => panic!("Expected a string literal"),
274 let mut fmt_fragments = format_str.split("{}");
275 let mut doc_string = fmt_fragments.next().unwrap().to_string();
276 list.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
277 |(tts, next_fmt_fragment)| {
278 use ::core::fmt::Write;
282 tts.to_string().replace(" . ", "."),
288 let doc_string = format!(
289 "[query description - consider adding a doc-comment!] {}",
292 let comment = parse_quote! {
295 query.doc_comments.push(comment);
297 desc = Some((tcx, list));
299 QueryModifier::FatalCycle(ident) => {
300 if fatal_cycle.is_some() {
301 panic!("duplicate modifier `fatal_cycle` for query `{}`", query.name);
303 fatal_cycle = Some(ident);
305 QueryModifier::CycleDelayBug(ident) => {
306 if cycle_delay_bug.is_some() {
307 panic!("duplicate modifier `cycle_delay_bug` for query `{}`", query.name);
309 cycle_delay_bug = Some(ident);
311 QueryModifier::NoHash(ident) => {
312 if no_hash.is_some() {
313 panic!("duplicate modifier `no_hash` for query `{}`", query.name);
315 no_hash = Some(ident);
317 QueryModifier::Anon(ident) => {
319 panic!("duplicate modifier `anon` for query `{}`", query.name);
323 QueryModifier::EvalAlways(ident) => {
324 if eval_always.is_some() {
325 panic!("duplicate modifier `eval_always` for query `{}`", query.name);
327 eval_always = Some(ident);
329 QueryModifier::SeparateProvideExtern(ident) => {
330 if separate_provide_extern.is_some() {
332 "duplicate modifier `separate_provide_extern` for query `{}`",
336 separate_provide_extern = Some(ident);
340 let desc = desc.unwrap_or_else(|| {
341 panic!("no description provided for query `{}`", query.name);
353 separate_provide_extern,
357 /// Add the impl of QueryDescription for the query to `impls` if one is requested
358 fn add_query_description_impl(
360 modifiers: QueryModifiers,
361 impls: &mut proc_macro2::TokenStream,
363 let name = &query.name;
364 let key = &query.key.0;
366 // Find out if we should cache the query on disk
367 let cache = if let Some((args, expr)) = modifiers.cache.as_ref() {
368 let try_load_from_disk = if let Some((tcx, id, block)) = modifiers.load_cached.as_ref() {
369 // Use custom code to load the query from disk
371 const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>>
372 = Some(|#tcx, #id| { #block });
375 // Use the default code to load the query from disk
377 const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>>
378 = Some(|tcx, id| tcx.on_disk_cache().as_ref()?.try_load_query_result(*tcx, id));
388 .unwrap_or_else(|| quote! { _ });
389 // expr is a `Block`, meaning that `{ #expr }` gets expanded
390 // to `{ { stmts... } }`, which triggers the `unused_braces` lint.
392 #[allow(unused_variables, unused_braces)]
394 fn cache_on_disk(#tcx: TyCtxt<'tcx>, #key: &Self::Key) -> bool {
401 if modifiers.load_cached.is_some() {
402 panic!("load_cached modifier on query `{}` without a cache modifier", name);
406 fn cache_on_disk(_: TyCtxt<'tcx>, _: &Self::Key) -> bool {
410 const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>> = None;
414 let (tcx, desc) = modifiers.desc;
415 let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
418 #[allow(unused_variables)]
419 fn describe(tcx: QueryCtxt<$tcx>, key: Self::Key) -> String {
420 let (#tcx, #key) = (*tcx, key);
421 ::rustc_middle::ty::print::with_no_trimmed_paths(|| format!(#desc).into())
425 impls.extend(quote! {
426 (#name<$tcx:tt>) => {
433 pub fn rustc_queries(input: TokenStream) -> TokenStream {
434 let queries = parse_macro_input!(input as List<Query>);
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! {};
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 },
451 if modifiers.cache.is_some() {
452 cached_queries.extend(quote! {
457 let mut attributes = Vec::new();
459 // Pass on the fatal_cycle modifier
460 if let Some(fatal_cycle) = &modifiers.fatal_cycle {
461 attributes.push(quote! { (#fatal_cycle) });
463 // Pass on the storage modifier
464 if let Some(ref ty) = modifiers.storage {
465 let span = ty.span();
466 attributes.push(quote_spanned! {span=> (storage #ty) });
468 // Pass on the cycle_delay_bug modifier
469 if let Some(cycle_delay_bug) = &modifiers.cycle_delay_bug {
470 attributes.push(quote! { (#cycle_delay_bug) });
472 // Pass on the no_hash modifier
473 if let Some(no_hash) = &modifiers.no_hash {
474 attributes.push(quote! { (#no_hash) });
476 // Pass on the anon modifier
477 if let Some(anon) = &modifiers.anon {
478 attributes.push(quote! { (#anon) });
480 // Pass on the eval_always modifier
481 if let Some(eval_always) = &modifiers.eval_always {
482 attributes.push(quote! { (#eval_always) });
484 // Pass on the separate_provide_extern modifier
485 if let Some(separate_provide_extern) = &modifiers.separate_provide_extern {
486 attributes.push(quote! { (#separate_provide_extern) });
489 // This uses the span of the query definition for the commas,
490 // which can be important if we later encounter any ambiguity
491 // errors with any of the numerous macro_rules! macros that
492 // we use. Using the call-site span would result in a span pointing
493 // at the entire `rustc_queries!` invocation, which wouldn't
495 let span = name.span();
496 let attribute_stream = quote_spanned! {span=> #(#attributes),*};
497 let doc_comments = query.doc_comments.iter();
498 // Add the query to the group
499 query_stream.extend(quote! {
501 [#attribute_stream] fn #name(#arg) #result,
504 // Create a dep node for the query
505 dep_node_def_stream.extend(quote! {
506 [#attribute_stream] #name(#arg),
509 add_query_description_impl(&query, modifiers, &mut query_description_stream);
512 TokenStream::from(quote! {
514 macro_rules! rustc_query_append {
515 ([$($macro:tt)*][$($other:tt)*]) => {
524 macro_rules! rustc_dep_node_append {
525 ([$($macro:tt)*][$($other:tt)*]) => {
534 macro_rules! rustc_cached_queries {
536 $($macro)*(#cached_queries);
540 macro_rules! rustc_query_description {
541 #query_description_stream