1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{quote, quote_spanned};
4use syn::parse::{Parse, ParseStream, Result};
5use syn::punctuated::Punctuated;
6use syn::spanned::Spanned;
7use syn::{
8 AttrStyle, Attribute, Block, Error, Expr, Ident, Pat, ReturnType, Token, Type, braced,
9 parenthesized, parse_macro_input, token,
10};
11
12mod kw {
13 syn::custom_keyword!(query);
14}
15
16fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
18 let inner = |attr: Attribute| {
19 if !attr.path().is_ident("doc") {
20 Err(Error::new(attr.span(), "attributes not supported on queries"))
21 } else if attr.style != AttrStyle::Outer {
22 Err(Error::new(
23 attr.span(),
24 "attributes must be outer attributes (`///`), not inner attributes",
25 ))
26 } else {
27 Ok(attr)
28 }
29 };
30 attrs.into_iter().map(inner).collect()
31}
32
33struct Query {
35 doc_comments: Vec<Attribute>,
36 modifiers: QueryModifiers,
37 name: Ident,
38 key: Pat,
39 arg: Type,
40 result: ReturnType,
41}
42
43impl Parse for Query {
44 fn parse(input: ParseStream<'_>) -> Result<Self> {
45 let mut doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
46
47 input.parse::<kw::query>()?;
49 let name: Ident = input.parse()?;
50 let arg_content;
51 parenthesized!(arg_content in input);
52 let key = Pat::parse_single(&arg_content)?;
53 arg_content.parse::<Token![:]>()?;
54 let arg = arg_content.parse()?;
55 let _ = arg_content.parse::<Option<Token![,]>>()?;
56 let result = input.parse()?;
57
58 let content;
60 braced!(content in input);
61 let modifiers = parse_query_modifiers(&content)?;
62
63 if doc_comments.is_empty() {
66 doc_comments.push(doc_comment_from_desc(&modifiers.desc.expr_list)?);
67 }
68
69 Ok(Query { doc_comments, modifiers, name, key, arg, result })
70 }
71}
72
73struct List<T>(Vec<T>);
75
76impl<T: Parse> Parse for List<T> {
77 fn parse(input: ParseStream<'_>) -> Result<Self> {
78 let mut list = Vec::new();
79 while !input.is_empty() {
80 list.push(input.parse()?);
81 }
82 Ok(List(list))
83 }
84}
85
86struct Desc {
87 modifier: Ident,
88 tcx_binding: Option<Ident>,
89 expr_list: Punctuated<Expr, Token![,]>,
90}
91
92struct CacheOnDiskIf {
93 modifier: Ident,
94 tcx_binding: Option<Pat>,
95 block: Block,
96}
97
98struct QueryModifiers {
99 desc: Desc,
101
102 arena_cache: Option<Ident>,
104
105 cache_on_disk_if: Option<CacheOnDiskIf>,
107
108 cycle_fatal: Option<Ident>,
110
111 cycle_delay_bug: Option<Ident>,
113
114 cycle_stash: Option<Ident>,
116
117 no_hash: Option<Ident>,
119
120 anon: Option<Ident>,
122
123 eval_always: Option<Ident>,
125
126 depth_limit: Option<Ident>,
128
129 separate_provide_extern: Option<Ident>,
131
132 feedable: Option<Ident>,
134
135 return_result_from_ensure_ok: Option<Ident>,
146}
147
148fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
149 let mut arena_cache = None;
150 let mut cache_on_disk_if = None;
151 let mut desc = None;
152 let mut cycle_fatal = None;
153 let mut cycle_delay_bug = None;
154 let mut cycle_stash = None;
155 let mut no_hash = None;
156 let mut anon = None;
157 let mut eval_always = None;
158 let mut depth_limit = None;
159 let mut separate_provide_extern = None;
160 let mut feedable = None;
161 let mut return_result_from_ensure_ok = None;
162
163 while !input.is_empty() {
164 let modifier: Ident = input.parse()?;
165
166 macro_rules! try_insert {
167 ($name:ident = $expr:expr) => {
168 if $name.is_some() {
169 return Err(Error::new(modifier.span(), "duplicate modifier"));
170 }
171 $name = Some($expr);
172 };
173 }
174
175 if modifier == "desc" {
176 let attr_content;
179 braced!(attr_content in input);
180 let tcx_binding = if attr_content.peek(Token![|]) {
181 attr_content.parse::<Token![|]>()?;
182 let tcx = attr_content.parse()?;
183 attr_content.parse::<Token![|]>()?;
184 Some(tcx)
185 } else {
186 None
187 };
188 let expr_list = attr_content.parse_terminated(Expr::parse, Token![,])?;
189 try_insert!(desc = Desc { modifier, tcx_binding, expr_list });
190 } else if modifier == "cache_on_disk_if" {
191 let tcx_binding = if input.peek(token::Paren) {
197 let args;
198 parenthesized!(args in input);
199 let tcx = Pat::parse_single(&args)?;
200 Some(tcx)
201 } else {
202 None
203 };
204 let block = input.parse()?;
205 try_insert!(cache_on_disk_if = CacheOnDiskIf { modifier, tcx_binding, block });
206 } else if modifier == "arena_cache" {
207 try_insert!(arena_cache = modifier);
208 } else if modifier == "cycle_fatal" {
209 try_insert!(cycle_fatal = modifier);
210 } else if modifier == "cycle_delay_bug" {
211 try_insert!(cycle_delay_bug = modifier);
212 } else if modifier == "cycle_stash" {
213 try_insert!(cycle_stash = modifier);
214 } else if modifier == "no_hash" {
215 try_insert!(no_hash = modifier);
216 } else if modifier == "anon" {
217 try_insert!(anon = modifier);
218 } else if modifier == "eval_always" {
219 try_insert!(eval_always = modifier);
220 } else if modifier == "depth_limit" {
221 try_insert!(depth_limit = modifier);
222 } else if modifier == "separate_provide_extern" {
223 try_insert!(separate_provide_extern = modifier);
224 } else if modifier == "feedable" {
225 try_insert!(feedable = modifier);
226 } else if modifier == "return_result_from_ensure_ok" {
227 try_insert!(return_result_from_ensure_ok = modifier);
228 } else {
229 return Err(Error::new(modifier.span(), "unknown query modifier"));
230 }
231 }
232 let Some(desc) = desc else {
233 return Err(input.error("no description provided"));
234 };
235 Ok(QueryModifiers {
236 arena_cache,
237 cache_on_disk_if,
238 desc,
239 cycle_fatal,
240 cycle_delay_bug,
241 cycle_stash,
242 no_hash,
243 anon,
244 eval_always,
245 depth_limit,
246 separate_provide_extern,
247 feedable,
248 return_result_from_ensure_ok,
249 })
250}
251
252fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
253 use ::syn::*;
254 let mut iter = list.iter();
255 let format_str: String = match iter.next() {
256 Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
257 lit_str.value().replace("`{}`", "{}") }
259 _ => return Err(Error::new(list.span(), "Expected a string literal")),
260 };
261 let mut fmt_fragments = format_str.split("{}");
262 let mut doc_string = fmt_fragments.next().unwrap().to_string();
263 iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
264 |(tts, next_fmt_fragment)| {
265 use ::core::fmt::Write;
266 write!(
267 &mut doc_string,
268 " `{}` {}",
269 tts.to_string().replace(" . ", "."),
270 next_fmt_fragment,
271 )
272 .unwrap();
273 },
274 );
275 let doc_string = format!("[query description - consider adding a doc-comment!] {doc_string}");
276 Ok(parse_quote! { #[doc = #doc_string] })
277}
278
279#[derive(Default)]
285struct HelperTokenStreams {
286 description_fns_stream: proc_macro2::TokenStream,
287 cache_on_disk_if_fns_stream: proc_macro2::TokenStream,
288}
289
290fn make_helpers_for_query(query: &Query, streams: &mut HelperTokenStreams) {
291 let Query { name, key, modifiers, arg, .. } = &query;
292
293 let mut erased_name = name.clone();
295 erased_name.set_span(Span::call_site());
296
297 if let Some(CacheOnDiskIf { tcx_binding, block, .. }) = modifiers.cache_on_disk_if.as_ref() {
299 let tcx = tcx_binding.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ });
300 streams.cache_on_disk_if_fns_stream.extend(quote! {
302 #[allow(unused_variables, rustc::pass_by_value)]
303 #[inline]
304 pub fn #erased_name<'tcx>(#tcx: TyCtxt<'tcx>, #key: &crate::queries::#name::Key<'tcx>) -> bool
305 #block
306 });
307 }
308
309 let Desc { tcx_binding, expr_list, .. } = &modifiers.desc;
310 let tcx = tcx_binding.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
311
312 let desc = quote! {
313 #[allow(unused_variables)]
314 pub fn #erased_name<'tcx>(tcx: TyCtxt<'tcx>, key: #arg) -> String {
315 let (#tcx, #key) = (tcx, key);
316 format!(#expr_list)
317 }
318 };
319
320 streams.description_fns_stream.extend(quote! {
321 #desc
322 });
323}
324
325fn add_to_analyzer_stream(query: &Query, analyzer_stream: &mut proc_macro2::TokenStream) {
327 let modifiers = &query.modifiers;
330
331 let mut modifiers_stream = quote! {};
332
333 let name = &modifiers.desc.modifier;
334 modifiers_stream.extend(quote! {
335 crate::query::modifiers::#name;
336 });
337
338 if let Some(CacheOnDiskIf { modifier, .. }) = &modifiers.cache_on_disk_if {
339 modifiers_stream.extend(quote! {
340 crate::query::modifiers::#modifier;
341 });
342 }
343
344 macro_rules! doc_link {
345 ( $( $modifier:ident ),+ $(,)? ) => {
346 $(
347 if let Some(name) = &modifiers.$modifier {
348 modifiers_stream.extend(quote! {
349 crate::query::modifiers::#name;
350 });
351 }
352 )+
353 }
354 }
355
356 doc_link!(
357 arena_cache,
358 cycle_fatal,
359 cycle_delay_bug,
360 cycle_stash,
361 no_hash,
362 anon,
363 eval_always,
364 depth_limit,
365 separate_provide_extern,
366 feedable,
367 return_result_from_ensure_ok,
368 );
369
370 let name = &query.name;
371
372 let mut erased_name = name.clone();
374 erased_name.set_span(Span::call_site());
375
376 let result = &query.result;
377
378 let ra_hint = quote! {
387 let crate::query::Providers { #name: _, .. };
388 };
389
390 analyzer_stream.extend(quote! {
391 #[inline(always)]
392 fn #erased_name<'tcx>() #result {
393 #ra_hint
394 #modifiers_stream
395 loop {}
396 }
397 });
398}
399
400pub(super) fn rustc_queries(input: TokenStream) -> TokenStream {
401 let queries = parse_macro_input!(input as List<Query>);
402
403 let mut query_stream = quote! {};
404 let mut helpers = HelperTokenStreams::default();
405 let mut feedable_queries = quote! {};
406 let mut analyzer_stream = quote! {};
407 let mut errors = quote! {};
408
409 macro_rules! assert {
410 ( $cond:expr, $span:expr, $( $tt:tt )+ ) => {
411 if !$cond {
412 errors.extend(
413 Error::new($span, format!($($tt)+)).into_compile_error(),
414 );
415 }
416 }
417 }
418
419 for query in queries.0 {
420 let Query { name, arg, modifiers, .. } = &query;
421 let result_full = &query.result;
422 let result = match query.result {
423 ReturnType::Default => quote! { -> () },
424 _ => quote! { #result_full },
425 };
426
427 let mut attributes = Vec::new();
428
429 macro_rules! passthrough {
430 ( $( $modifier:ident ),+ $(,)? ) => {
431 $( if let Some($modifier) = &modifiers.$modifier {
432 attributes.push(quote! { (#$modifier) });
433 }; )+
434 }
435 }
436
437 passthrough!(
438 arena_cache,
439 cycle_fatal,
440 cycle_delay_bug,
441 cycle_stash,
442 no_hash,
443 anon,
444 eval_always,
445 feedable,
446 depth_limit,
447 separate_provide_extern,
448 return_result_from_ensure_ok,
449 );
450
451 if modifiers.cache_on_disk_if.is_some() {
455 attributes.push(quote! { (cache_on_disk) });
456 }
457
458 let span = name.span();
465 let attribute_stream = quote_spanned! {span=> #(#attributes),*};
466 let doc_comments = &query.doc_comments;
467 query_stream.extend(quote! {
469 #(#doc_comments)*
470 [#attribute_stream] fn #name(#arg) #result,
471 });
472
473 if let Some(feedable) = &modifiers.feedable {
474 assert!(
475 modifiers.anon.is_none(),
476 feedable.span(),
477 "Query {name} cannot be both `feedable` and `anon`."
478 );
479 assert!(
480 modifiers.eval_always.is_none(),
481 feedable.span(),
482 "Query {name} cannot be both `feedable` and `eval_always`."
483 );
484 feedable_queries.extend(quote! {
485 [#attribute_stream] fn #name(#arg) #result,
486 });
487 }
488
489 add_to_analyzer_stream(&query, &mut analyzer_stream);
490 make_helpers_for_query(&query, &mut helpers);
491 }
492
493 let HelperTokenStreams { description_fns_stream, cache_on_disk_if_fns_stream } = helpers;
494
495 TokenStream::from(quote! {
496 #[macro_export]
502 macro_rules! rustc_with_all_queries {
503 (
504 $macro:ident!
506
507 $( [$($extra_fake_queries:tt)*] )?
510 ) => {
511 $macro! {
512 $( $($extra_fake_queries)* )?
513 #query_stream
514 }
515 }
516 }
517 macro_rules! rustc_feedable_queries {
518 ( $macro:ident! ) => {
519 $macro!(#feedable_queries);
520 }
521 }
522
523 mod _analyzer_hints {
525 use super::*;
526 #analyzer_stream
527 }
528
529 pub mod _description_fns {
536 use super::*;
537 #description_fns_stream
538 }
539
540 pub mod _cache_on_disk_if_fns {
544 use super::*;
545 #cache_on_disk_if_fns_stream
546 }
547
548 #errors
549 })
550}