1 use itertools::Itertools;
2 use proc_macro::TokenStream;
3 use proc_macro2::{Delimiter, TokenTree};
6 use syn::parse::{Parse, ParseStream, Result};
7 use syn::punctuated::Punctuated;
8 use syn::spanned::Spanned;
10 braced, parenthesized, parse_macro_input, Attribute, Block, Error, Expr, Ident, ReturnType,
14 #[allow(non_camel_case_types)]
16 syn::custom_keyword!(query);
19 /// Ident or a wildcard `_`.
20 struct IdentOrWild(Ident);
22 impl Parse for IdentOrWild {
23 fn parse(input: ParseStream<'_>) -> Result<Self> {
24 Ok(if input.peek(Token![_]) {
25 let underscore = input.parse::<Token![_]>()?;
26 IdentOrWild(Ident::new("_", underscore.span()))
28 IdentOrWild(input.parse()?)
33 /// A modifier for a query
35 /// The description of the query.
36 Desc(Option<Ident>, Punctuated<Expr, Token![,]>),
38 /// Cache the query to disk if the `Expr` returns true.
39 Cache(Option<(IdentOrWild, 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 /// Don't force the query
56 /// Generate a dep node based on the dependencies of the query
59 /// Always evaluate the query, ignoring its depdendencies
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()?;
93 args.parse::<Token![,]>()?;
94 let value = args.parse()?;
99 let block = input.parse()?;
100 Ok(QueryModifier::Cache(args, block))
101 } else if modifier == "load_cached" {
102 // Parse a load_cached modifier like:
103 // `load_cached(tcx, id) { tcx.queries.on_disk_cache.try_load_query_result(tcx, id) }`
105 parenthesized!(args in input);
106 let tcx = args.parse()?;
107 args.parse::<Token![,]>()?;
108 let id = args.parse()?;
109 let block = input.parse()?;
110 Ok(QueryModifier::LoadCached(tcx, id, block))
111 } else if modifier == "fatal_cycle" {
112 Ok(QueryModifier::FatalCycle)
113 } else if modifier == "cycle_delay_bug" {
114 Ok(QueryModifier::CycleDelayBug)
115 } else if modifier == "no_hash" {
116 Ok(QueryModifier::NoHash)
117 } else if modifier == "no_force" {
118 Ok(QueryModifier::NoForce)
119 } else if modifier == "anon" {
120 Ok(QueryModifier::Anon)
121 } else if modifier == "eval_always" {
122 Ok(QueryModifier::EvalAlways)
124 Err(Error::new(modifier.span(), "unknown query modifier"))
129 /// Ensures only doc comment attributes are used
130 fn check_attributes(attrs: Vec<Attribute>) -> Result<()> {
132 if !attr.path.is_ident("doc") {
133 return Err(Error::new(attr.span(), "attributes not supported on queries"));
139 /// A compiler query. `query ... { ... }`
141 modifiers: List<QueryModifier>,
148 impl Parse for Query {
149 fn parse(input: ParseStream<'_>) -> Result<Self> {
150 check_attributes(input.call(Attribute::parse_outer)?)?;
152 // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>`
153 input.parse::<kw::query>()?;
154 let name: Ident = input.parse()?;
156 parenthesized!(arg_content in input);
157 let key = arg_content.parse()?;
158 arg_content.parse::<Token![:]>()?;
159 let arg = arg_content.parse()?;
160 let result = input.parse()?;
162 // Parse the query modifiers
164 braced!(content in input);
165 let modifiers = content.parse()?;
167 Ok(Query { modifiers, name, key, arg, result })
171 /// A type used to greedily parse another type until the input is empty.
172 struct List<T>(Vec<T>);
174 impl<T: Parse> Parse for List<T> {
175 fn parse(input: ParseStream<'_>) -> Result<Self> {
176 let mut list = Vec::new();
177 while !input.is_empty() {
178 list.push(input.parse()?);
184 /// A named group containing queries.
187 queries: List<Query>,
190 impl Parse for Group {
191 fn parse(input: ParseStream<'_>) -> Result<Self> {
192 let name: Ident = input.parse()?;
194 braced!(content in input);
195 Ok(Group { name, queries: content.parse()? })
199 struct QueryModifiers {
200 /// The description of the query.
201 desc: Option<(Option<Ident>, Punctuated<Expr, Token![,]>)>,
203 /// Cache the query to disk if the `Block` returns true.
204 cache: Option<(Option<(IdentOrWild, IdentOrWild)>, Block)>,
206 /// Custom code to load the query from disk.
207 load_cached: Option<(Ident, Ident, Block)>,
209 /// A cycle error for this query aborting the compilation with a fatal error.
212 /// A cycle error results in a delay_bug call
213 cycle_delay_bug: bool,
215 /// Don't hash the result, instead just mark a query red if it runs
218 /// Don't force the query
221 /// Generate a dep node based on the dependencies of the query
224 // Always evaluate the query, ignoring its depdendencies
228 /// Process query modifiers into a struct, erroring on duplicates
229 fn process_modifiers(query: &mut Query) -> QueryModifiers {
230 let mut load_cached = None;
231 let mut cache = None;
233 let mut fatal_cycle = false;
234 let mut cycle_delay_bug = false;
235 let mut no_hash = false;
236 let mut no_force = false;
237 let mut anon = false;
238 let mut eval_always = false;
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::Cache(args, expr) => {
249 panic!("duplicate modifier `cache` for query `{}`", query.name);
251 cache = Some((args, expr));
253 QueryModifier::Desc(tcx, list) => {
255 panic!("duplicate modifier `desc` for query `{}`", query.name);
257 desc = Some((tcx, list));
259 QueryModifier::FatalCycle => {
261 panic!("duplicate modifier `fatal_cycle` for query `{}`", query.name);
265 QueryModifier::CycleDelayBug => {
267 panic!("duplicate modifier `cycle_delay_bug` for query `{}`", query.name);
269 cycle_delay_bug = true;
271 QueryModifier::NoHash => {
273 panic!("duplicate modifier `no_hash` for query `{}`", query.name);
277 QueryModifier::NoForce => {
279 panic!("duplicate modifier `no_force` for query `{}`", query.name);
283 QueryModifier::Anon => {
285 panic!("duplicate modifier `anon` for query `{}`", query.name);
289 QueryModifier::EvalAlways => {
291 panic!("duplicate modifier `eval_always` for query `{}`", query.name);
310 /// Add the impl of QueryDescription for the query to `impls` if one is requested
311 fn add_query_description_impl(
313 modifiers: QueryModifiers,
314 impls: &mut proc_macro2::TokenStream,
316 let name = &query.name;
317 let arg = &query.arg;
318 let key = &query.key.0;
320 // Find out if we should cache the query on disk
321 let cache = modifiers.cache.as_ref().map(|(args, expr)| {
322 let try_load_from_disk = if let Some((tcx, id, block)) = modifiers.load_cached.as_ref() {
323 // Use custom code to load the query from disk
326 fn try_load_from_disk(
328 #id: SerializedDepNodeIndex
329 ) -> Option<Self::Value> {
334 // Use the default code to load the query from disk
337 fn try_load_from_disk(
339 id: SerializedDepNodeIndex
340 ) -> Option<Self::Value> {
341 tcx.queries.on_disk_cache.try_load_query_result(tcx, id)
352 .unwrap_or(quote! { _ });
359 .unwrap_or(quote! { _ });
362 #[allow(unused_variables)]
366 #value: Option<&Self::Value>
375 if cache.is_none() && modifiers.load_cached.is_some() {
376 panic!("load_cached modifier on query `{}` without a cache modifier", name);
379 let desc = modifiers.desc.as_ref().map(|(tcx, desc)| {
380 let tcx = tcx.as_ref().map(|t| quote! { #t }).unwrap_or(quote! { _ });
382 #[allow(unused_variables)]
386 ) -> Cow<'static, str> {
387 format!(#desc).into()
392 if desc.is_some() || cache.is_some() {
393 let cache = cache.unwrap_or(quote! {});
394 let desc = desc.unwrap_or(quote! {});
396 impls.extend(quote! {
397 impl<'tcx> QueryDescription<'tcx> for queries::#name<'tcx> {
405 pub fn rustc_queries(input: TokenStream) -> TokenStream {
406 let groups = parse_macro_input!(input as List<Group>);
408 let mut query_stream = quote! {};
409 let mut query_description_stream = quote! {};
410 let mut dep_node_def_stream = quote! {};
411 let mut dep_node_force_stream = quote! {};
412 let mut try_load_from_on_disk_cache_stream = quote! {};
413 let mut no_force_queries = Vec::new();
414 let mut cached_queries = quote! {};
416 for group in groups.0 {
417 let mut group_stream = quote! {};
418 for mut query in group.queries.0 {
419 let modifiers = process_modifiers(&mut query);
420 let name = &query.name;
421 let arg = &query.arg;
422 let result_full = &query.result;
423 let result = match query.result {
424 ReturnType::Default => quote! { -> () },
425 _ => quote! { #result_full },
428 if modifiers.cache.is_some() {
429 cached_queries.extend(quote! {
434 if modifiers.cache.is_some() && !modifiers.no_force {
435 try_load_from_on_disk_cache_stream.extend(quote! {
437 debug_assert!(tcx.dep_graph
439 .map(|c| c.is_green())
442 let key = RecoverKey::recover(tcx, self).unwrap();
443 if queries::#name::cache_on_disk(tcx, key, None) {
444 let _ = tcx.#name(key);
450 let mut attributes = Vec::new();
452 // Pass on the fatal_cycle modifier
453 if modifiers.fatal_cycle {
454 attributes.push(quote! { fatal_cycle });
456 // Pass on the cycle_delay_bug modifier
457 if modifiers.cycle_delay_bug {
458 attributes.push(quote! { cycle_delay_bug });
460 // Pass on the no_hash modifier
461 if modifiers.no_hash {
462 attributes.push(quote! { no_hash });
464 // Pass on the anon modifier
466 attributes.push(quote! { anon });
468 // Pass on the eval_always modifier
469 if modifiers.eval_always {
470 attributes.push(quote! { eval_always });
473 let mut attribute_stream = quote! {};
474 for e in attributes.into_iter().intersperse(quote! {,}) {
475 attribute_stream.extend(e);
478 // Add the query to the group
479 group_stream.extend(quote! {
480 [#attribute_stream] fn #name: #name(#arg) #result,
483 // Create a dep node for the query
484 dep_node_def_stream.extend(quote! {
485 [#attribute_stream] #name(#arg),
488 if modifiers.no_force {
489 no_force_queries.push(name.clone());
491 // Add a match arm to force the query given the dep node
492 dep_node_force_stream.extend(quote! {
494 if let Some(key) = RecoverKey::recover($tcx, $dep_node) {
495 $tcx.force_query::<crate::ty::query::queries::#name<'_>>(
507 add_query_description_impl(&query, modifiers, &mut query_description_stream);
509 let name = &group.name;
510 query_stream.extend(quote! {
511 #name { #group_stream },
515 // Add an arm for the no force queries to panic when trying to force them
516 for query in no_force_queries {
517 dep_node_force_stream.extend(quote! {
521 dep_node_force_stream.extend(quote! {
523 bug!("Cannot force dep node: {:?}", $dep_node)
527 TokenStream::from(quote! {
528 macro_rules! rustc_query_append {
529 ([$($macro:tt)*][$($other:tt)*]) => {
538 macro_rules! rustc_dep_node_append {
539 ([$($macro:tt)*][$($other:tt)*]) => {
547 macro_rules! rustc_dep_node_force {
548 ([$dep_node:expr, $tcx:expr] $($other:tt)*) => {
549 match $dep_node.kind {
552 #dep_node_force_stream
556 macro_rules! rustc_cached_queries {
558 $($macro)*(#cached_queries);
562 #query_description_stream
565 /// Check whether the query invocation corresponding to the given
566 /// DepNode is eligible for on-disk-caching. If so, this is method
567 /// will execute the query corresponding to the given DepNode.
568 /// Also, as a sanity check, it expects that the corresponding query
569 /// invocation has been marked as green already.
570 pub fn try_load_from_on_disk_cache(&self, tcx: TyCtxt<'_>) {
572 #try_load_from_on_disk_cache_stream