1 use proc_macro::TokenStream;
2 use quote::{quote, quote_spanned};
3 use syn::parse::{Parse, ParseStream, Result};
4 use syn::punctuated::Punctuated;
5 use syn::spanned::Spanned;
7 braced, parenthesized, parse_macro_input, parse_quote, token, AttrStyle, Attribute, Block,
8 Error, Expr, Ident, Pat, ReturnType, Token, Type,
12 syn::custom_keyword!(query);
15 /// Ensures only doc comment attributes are used
16 fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
17 let inner = |attr: Attribute| {
18 if !attr.path.is_ident("doc") {
19 Err(Error::new(attr.span(), "attributes not supported on queries"))
20 } else if attr.style != AttrStyle::Outer {
23 "attributes must be outer attributes (`///`), not inner attributes",
29 attrs.into_iter().map(inner).collect()
32 /// A compiler query. `query ... { ... }`
34 doc_comments: Vec<Attribute>,
35 modifiers: QueryModifiers,
42 impl Parse for Query {
43 fn parse(input: ParseStream<'_>) -> Result<Self> {
44 let mut doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
46 // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>`
47 input.parse::<kw::query>()?;
48 let name: Ident = input.parse()?;
50 parenthesized!(arg_content in input);
51 let key = arg_content.parse()?;
52 arg_content.parse::<Token![:]>()?;
53 let arg = arg_content.parse()?;
54 let result = input.parse()?;
56 // Parse the query modifiers
58 braced!(content in input);
59 let modifiers = parse_query_modifiers(&content)?;
61 // If there are no doc-comments, give at least some idea of what
62 // it does by showing the query description.
63 if doc_comments.is_empty() {
64 doc_comments.push(doc_comment_from_desc(&modifiers.desc.1)?);
67 Ok(Query { doc_comments, modifiers, name, key, arg, result })
71 /// A type used to greedily parse another type until the input is empty.
72 struct List<T>(Vec<T>);
74 impl<T: Parse> Parse for List<T> {
75 fn parse(input: ParseStream<'_>) -> Result<Self> {
76 let mut list = Vec::new();
77 while !input.is_empty() {
78 list.push(input.parse()?);
84 struct QueryModifiers {
85 /// The description of the query.
86 desc: (Option<Ident>, Punctuated<Expr, Token![,]>),
88 /// Use this type for the in-memory cache.
89 storage: Option<Type>,
91 /// Cache the query to disk if the `Block` returns true.
92 cache: Option<(Option<Pat>, Block)>,
94 /// Custom code to load the query from disk.
95 load_cached: Option<(Ident, Ident, Block)>,
97 /// A cycle error for this query aborting the compilation with a fatal error.
98 fatal_cycle: Option<Ident>,
100 /// A cycle error results in a delay_bug call
101 cycle_delay_bug: Option<Ident>,
103 /// Don't hash the result, instead just mark a query red if it runs
104 no_hash: Option<Ident>,
106 /// Generate a dep node based on the dependencies of the query
109 /// Always evaluate the query, ignoring its dependencies
110 eval_always: Option<Ident>,
112 /// Whether the query has a call depth limit
113 depth_limit: Option<Ident>,
115 /// Use a separate query provider for local and extern crates
116 separate_provide_extern: Option<Ident>,
118 /// Always remap the ParamEnv's constness before hashing.
119 remap_env_constness: Option<Ident>,
122 fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
123 let mut load_cached = None;
124 let mut storage = None;
125 let mut cache = None;
127 let mut fatal_cycle = None;
128 let mut cycle_delay_bug = None;
129 let mut no_hash = None;
131 let mut eval_always = None;
132 let mut depth_limit = None;
133 let mut separate_provide_extern = None;
134 let mut remap_env_constness = None;
136 while !input.is_empty() {
137 let modifier: Ident = input.parse()?;
139 macro_rules! try_insert {
140 ($name:ident = $expr:expr) => {
142 return Err(Error::new(modifier.span(), "duplicate modifier"));
148 if modifier == "desc" {
149 // Parse a description modifier like:
150 // `desc { |tcx| "foo {}", tcx.item_path(key) }`
152 braced!(attr_content in input);
153 let tcx = if attr_content.peek(Token![|]) {
154 attr_content.parse::<Token![|]>()?;
155 let tcx = attr_content.parse()?;
156 attr_content.parse::<Token![|]>()?;
161 let list = attr_content.parse_terminated(Expr::parse)?;
162 try_insert!(desc = (tcx, list));
163 } else if modifier == "cache_on_disk_if" {
164 // Parse a cache modifier like:
165 // `cache(tcx) { |tcx| key.is_local() }`
166 let args = if input.peek(token::Paren) {
168 parenthesized!(args in input);
169 let tcx = args.parse()?;
174 let block = input.parse()?;
175 try_insert!(cache = (args, block));
176 } else if modifier == "load_cached" {
177 // Parse a load_cached modifier like:
178 // `load_cached(tcx, id) { tcx.on_disk_cache.try_load_query_result(tcx, id) }`
180 parenthesized!(args in input);
181 let tcx = args.parse()?;
182 args.parse::<Token![,]>()?;
183 let id = args.parse()?;
184 let block = input.parse()?;
185 try_insert!(load_cached = (tcx, id, block));
186 } else if modifier == "storage" {
188 parenthesized!(args in input);
189 let ty = args.parse()?;
190 try_insert!(storage = ty);
191 } else if modifier == "fatal_cycle" {
192 try_insert!(fatal_cycle = modifier);
193 } else if modifier == "cycle_delay_bug" {
194 try_insert!(cycle_delay_bug = modifier);
195 } else if modifier == "no_hash" {
196 try_insert!(no_hash = modifier);
197 } else if modifier == "anon" {
198 try_insert!(anon = modifier);
199 } else if modifier == "eval_always" {
200 try_insert!(eval_always = modifier);
201 } else if modifier == "depth_limit" {
202 try_insert!(depth_limit = modifier);
203 } else if modifier == "separate_provide_extern" {
204 try_insert!(separate_provide_extern = modifier);
205 } else if modifier == "remap_env_constness" {
206 try_insert!(remap_env_constness = modifier);
208 return Err(Error::new(modifier.span(), "unknown query modifier"));
211 let Some(desc) = desc else {
212 return Err(input.error("no description provided"));
225 separate_provide_extern,
230 fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
232 let mut iter = list.iter();
233 let format_str: String = match iter.next() {
234 Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
235 lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
237 _ => return Err(Error::new(list.span(), "Expected a string literal")),
239 let mut fmt_fragments = format_str.split("{}");
240 let mut doc_string = fmt_fragments.next().unwrap().to_string();
241 iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
242 |(tts, next_fmt_fragment)| {
243 use ::core::fmt::Write;
247 tts.to_string().replace(" . ", "."),
253 let doc_string = format!("[query description - consider adding a doc-comment!] {}", doc_string);
254 Ok(parse_quote! { #[doc = #doc_string] })
257 /// Add the impl of QueryDescription for the query to `impls` if one is requested
258 fn add_query_description_impl(query: &Query, impls: &mut proc_macro2::TokenStream) {
259 let name = &query.name;
260 let key = &query.key;
261 let modifiers = &query.modifiers;
263 // Find out if we should cache the query on disk
264 let cache = if let Some((args, expr)) = modifiers.cache.as_ref() {
265 let try_load_from_disk = if let Some((tcx, id, block)) = modifiers.load_cached.as_ref() {
266 // Use custom code to load the query from disk
268 const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<'tcx>, SerializedDepNodeIndex) -> Option<Self::Value>>
269 = Some(|#tcx, #id| { #block });
272 // Use the default code to load the query from disk
274 const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<'tcx>, SerializedDepNodeIndex) -> Option<Self::Value>>
275 = Some(|tcx, id| tcx.on_disk_cache().as_ref()?.try_load_query_result(*tcx, id));
279 let tcx = args.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ });
280 // expr is a `Block`, meaning that `{ #expr }` gets expanded
281 // to `{ { stmts... } }`, which triggers the `unused_braces` lint.
283 #[allow(unused_variables, unused_braces)]
285 fn cache_on_disk(#tcx: TyCtxt<'tcx>, #key: &Self::Key) -> bool {
292 if modifiers.load_cached.is_some() {
293 panic!("load_cached modifier on query `{}` without a cache modifier", name);
297 fn cache_on_disk(_: TyCtxt<'tcx>, _: &Self::Key) -> bool {
301 const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<'tcx>, SerializedDepNodeIndex) -> Option<Self::Value>> = None;
305 let (tcx, desc) = &modifiers.desc;
306 let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
309 #[allow(unused_variables)]
310 fn describe(tcx: QueryCtxt<'tcx>, key: Self::Key) -> String {
311 let (#tcx, #key) = (*tcx, key);
312 ::rustc_middle::ty::print::with_no_trimmed_paths!(
318 impls.extend(quote! {
326 pub fn rustc_queries(input: TokenStream) -> TokenStream {
327 let queries = parse_macro_input!(input as List<Query>);
329 let mut query_stream = quote! {};
330 let mut query_description_stream = quote! {};
331 let mut dep_node_def_stream = quote! {};
332 let mut cached_queries = quote! {};
334 for query in queries.0 {
335 let Query { name, arg, modifiers, .. } = &query;
336 let result_full = &query.result;
337 let result = match query.result {
338 ReturnType::Default => quote! { -> () },
339 _ => quote! { #result_full },
342 if modifiers.cache.is_some() {
343 cached_queries.extend(quote! {
348 let mut attributes = Vec::new();
350 // Pass on the fatal_cycle modifier
351 if let Some(fatal_cycle) = &modifiers.fatal_cycle {
352 attributes.push(quote! { (#fatal_cycle) });
354 // Pass on the storage modifier
355 if let Some(ref ty) = modifiers.storage {
356 let span = ty.span();
357 attributes.push(quote_spanned! {span=> (storage #ty) });
359 // Pass on the cycle_delay_bug modifier
360 if let Some(cycle_delay_bug) = &modifiers.cycle_delay_bug {
361 attributes.push(quote! { (#cycle_delay_bug) });
363 // Pass on the no_hash modifier
364 if let Some(no_hash) = &modifiers.no_hash {
365 attributes.push(quote! { (#no_hash) });
367 // Pass on the anon modifier
368 if let Some(anon) = &modifiers.anon {
369 attributes.push(quote! { (#anon) });
371 // Pass on the eval_always modifier
372 if let Some(eval_always) = &modifiers.eval_always {
373 attributes.push(quote! { (#eval_always) });
375 // Pass on the depth_limit modifier
376 if let Some(depth_limit) = &modifiers.depth_limit {
377 attributes.push(quote! { (#depth_limit) });
379 // Pass on the separate_provide_extern modifier
380 if let Some(separate_provide_extern) = &modifiers.separate_provide_extern {
381 attributes.push(quote! { (#separate_provide_extern) });
383 // Pass on the remap_env_constness modifier
384 if let Some(remap_env_constness) = &modifiers.remap_env_constness {
385 attributes.push(quote! { (#remap_env_constness) });
388 // This uses the span of the query definition for the commas,
389 // which can be important if we later encounter any ambiguity
390 // errors with any of the numerous macro_rules! macros that
391 // we use. Using the call-site span would result in a span pointing
392 // at the entire `rustc_queries!` invocation, which wouldn't
394 let span = name.span();
395 let attribute_stream = quote_spanned! {span=> #(#attributes),*};
396 let doc_comments = query.doc_comments.iter();
397 // Add the query to the group
398 query_stream.extend(quote! {
400 [#attribute_stream] fn #name(#arg) #result,
403 // Create a dep node for the query
404 dep_node_def_stream.extend(quote! {
405 [#attribute_stream] #name(#arg),
408 add_query_description_impl(&query, &mut query_description_stream);
411 TokenStream::from(quote! {
413 macro_rules! rustc_query_append {
414 ($macro:ident !) => {
420 macro_rules! rustc_dep_node_append {
421 ($macro:ident! [$($other:tt)*]) => {
430 macro_rules! rustc_cached_queries {
431 ( $macro:ident! ) => {
432 $macro!(#cached_queries);
436 macro_rules! rustc_query_description {
437 #query_description_stream