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!(non_query);
14 syn::custom_keyword!(query);
15}
16
17fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
19 let inner = |attr: Attribute| {
20 if !attr.path().is_ident("doc") {
21 Err(Error::new(attr.span(), "attributes not supported on queries"))
22 } else if attr.style != AttrStyle::Outer {
23 Err(Error::new(
24 attr.span(),
25 "attributes must be outer attributes (`///`), not inner attributes",
26 ))
27 } else {
28 Ok(attr)
29 }
30 };
31 attrs.into_iter().map(inner).collect()
32}
33
34struct Query {
47 doc_comments: Vec<Attribute>,
48 name: Ident,
49
50 key_pat: Pat,
52 key_ty: Type,
53 return_ty: ReturnType,
54
55 modifiers: QueryModifiers,
56}
57
58struct NonQuery {
66 doc_comments: Vec<Attribute>,
67 name: Ident,
68}
69
70enum QueryEntry {
71 Query(Query),
72 NonQuery(NonQuery),
73}
74
75impl Parse for QueryEntry {
76 fn parse(input: ParseStream<'_>) -> Result<Self> {
77 let mut doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
78
79 if input.parse::<kw::non_query>().is_ok() {
81 let name: Ident = input.parse()?;
82 return Ok(QueryEntry::NonQuery(NonQuery { doc_comments, name }));
83 }
84
85 if input.parse::<kw::query>().is_err() {
87 return Err(input.error("expected `query` or `non_query`"));
88 }
89 let name: Ident = input.parse()?;
90
91 let parens_content;
93 parenthesized!(parens_content in input);
94 let key_pat = Pat::parse_single(&parens_content)?;
95 parens_content.parse::<Token![:]>()?;
96 let key_ty = parens_content.parse::<Type>()?;
97 let _trailing_comma = parens_content.parse::<Option<Token![,]>>()?;
98
99 let return_ty = input.parse::<ReturnType>()?;
101
102 let braces_content;
104 braced!(braces_content in input);
105 let modifiers = parse_query_modifiers(&braces_content)?;
106
107 if doc_comments.is_empty() {
110 doc_comments.push(doc_comment_from_desc(&modifiers.desc.expr_list)?);
111 }
112
113 Ok(QueryEntry::Query(Query { doc_comments, modifiers, name, key_pat, key_ty, return_ty }))
114 }
115}
116
117struct List<T>(Vec<T>);
119
120impl<T: Parse> Parse for List<T> {
121 fn parse(input: ParseStream<'_>) -> Result<Self> {
122 let mut list = Vec::new();
123 while !input.is_empty() {
124 list.push(input.parse()?);
125 }
126 Ok(List(list))
127 }
128}
129
130struct Desc {
131 modifier: Ident,
132 expr_list: Punctuated<Expr, Token![,]>,
133}
134
135struct CacheOnDiskIf {
136 modifier: Ident,
137 block: Block,
138}
139
140struct QueryModifiers {
142 anon: Option<Ident>,
144 arena_cache: Option<Ident>,
145 cache_on_disk_if: Option<CacheOnDiskIf>,
146 cycle_delay_bug: Option<Ident>,
147 cycle_fatal: Option<Ident>,
148 cycle_stash: Option<Ident>,
149 depth_limit: Option<Ident>,
150 desc: Desc,
151 eval_always: Option<Ident>,
152 feedable: Option<Ident>,
153 no_hash: Option<Ident>,
154 return_result_from_ensure_ok: Option<Ident>,
155 separate_provide_extern: Option<Ident>,
156 }
158
159fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
160 let mut arena_cache = None;
161 let mut cache_on_disk_if = None;
162 let mut desc = None;
163 let mut cycle_fatal = None;
164 let mut cycle_delay_bug = None;
165 let mut cycle_stash = None;
166 let mut no_hash = None;
167 let mut anon = None;
168 let mut eval_always = None;
169 let mut depth_limit = None;
170 let mut separate_provide_extern = None;
171 let mut feedable = None;
172 let mut return_result_from_ensure_ok = None;
173
174 while !input.is_empty() {
175 let modifier: Ident = input.parse()?;
176
177 macro_rules! try_insert {
178 ($name:ident = $expr:expr) => {
179 if $name.is_some() {
180 return Err(Error::new(modifier.span(), "duplicate modifier"));
181 }
182 $name = Some($expr);
183 };
184 }
185
186 if modifier == "desc" {
187 let attr_content;
190 braced!(attr_content in input);
191 let expr_list = attr_content.parse_terminated(Expr::parse, Token![,])?;
192 try_insert!(desc = Desc { modifier, expr_list });
193 } else if modifier == "cache_on_disk_if" {
194 let block = input.parse()?;
197 try_insert!(cache_on_disk_if = CacheOnDiskIf { modifier, block });
198 } else if modifier == "arena_cache" {
199 try_insert!(arena_cache = modifier);
200 } else if modifier == "cycle_fatal" {
201 try_insert!(cycle_fatal = modifier);
202 } else if modifier == "cycle_delay_bug" {
203 try_insert!(cycle_delay_bug = modifier);
204 } else if modifier == "cycle_stash" {
205 try_insert!(cycle_stash = modifier);
206 } else if modifier == "no_hash" {
207 try_insert!(no_hash = modifier);
208 } else if modifier == "anon" {
209 try_insert!(anon = modifier);
210 } else if modifier == "eval_always" {
211 try_insert!(eval_always = modifier);
212 } else if modifier == "depth_limit" {
213 try_insert!(depth_limit = modifier);
214 } else if modifier == "separate_provide_extern" {
215 try_insert!(separate_provide_extern = modifier);
216 } else if modifier == "feedable" {
217 try_insert!(feedable = modifier);
218 } else if modifier == "return_result_from_ensure_ok" {
219 try_insert!(return_result_from_ensure_ok = modifier);
220 } else {
221 return Err(Error::new(modifier.span(), "unknown query modifier"));
222 }
223 }
224 let Some(desc) = desc else {
225 return Err(input.error("no description provided"));
226 };
227 Ok(QueryModifiers {
228 arena_cache,
229 cache_on_disk_if,
230 desc,
231 cycle_fatal,
232 cycle_delay_bug,
233 cycle_stash,
234 no_hash,
235 anon,
236 eval_always,
237 depth_limit,
238 separate_provide_extern,
239 feedable,
240 return_result_from_ensure_ok,
241 })
242}
243
244fn make_modifiers_stream(query: &Query, modifiers: &QueryModifiers) -> proc_macro2::TokenStream {
245 let QueryModifiers {
246 anon,
248 arena_cache,
249 cache_on_disk_if,
250 cycle_delay_bug,
251 cycle_fatal,
252 cycle_stash,
253 depth_limit,
254 desc: _,
255 eval_always,
256 feedable,
257 no_hash,
258 return_result_from_ensure_ok,
259 separate_provide_extern,
260 } = modifiers;
262
263 let anon = anon.is_some();
264 let arena_cache = arena_cache.is_some();
265 let cache_on_disk = cache_on_disk_if.is_some();
266
267 let cycle_error_handling = if cycle_delay_bug.is_some() {
268 quote! { DelayBug }
269 } else if cycle_fatal.is_some() {
270 quote! { Fatal }
271 } else if cycle_stash.is_some() {
272 quote! { Stash }
273 } else {
274 quote! { Error }
275 };
276
277 let depth_limit = depth_limit.is_some();
278 let eval_always = eval_always.is_some();
279 let feedable = feedable.is_some();
280 let no_hash = no_hash.is_some();
281 let return_result_from_ensure_ok = return_result_from_ensure_ok.is_some();
282 let separate_provide_extern = separate_provide_extern.is_some();
283
284 let query_name_span = query.name.span();
288 quote_spanned! {
289 query_name_span =>
290 anon: #anon,
293 arena_cache: #arena_cache,
294 cache_on_disk: #cache_on_disk,
295 cycle_error_handling: #cycle_error_handling,
296 depth_limit: #depth_limit,
297 eval_always: #eval_always,
298 feedable: #feedable,
299 no_hash: #no_hash,
300 return_result_from_ensure_ok: #return_result_from_ensure_ok,
301 separate_provide_extern: #separate_provide_extern,
302 }
304}
305
306fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
307 use ::syn::*;
308 let mut iter = list.iter();
309 let format_str: String = match iter.next() {
310 Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
311 lit_str.value().replace("`{}`", "{}") }
313 _ => return Err(Error::new(list.span(), "Expected a string literal")),
314 };
315 let mut fmt_fragments = format_str.split("{}");
316 let mut doc_string = fmt_fragments.next().unwrap().to_string();
317 iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
318 |(tts, next_fmt_fragment)| {
319 use ::core::fmt::Write;
320 write!(
321 &mut doc_string,
322 " `{}` {}",
323 tts.to_string().replace(" . ", "."),
324 next_fmt_fragment,
325 )
326 .unwrap();
327 },
328 );
329 let doc_string = format!("[query description - consider adding a doc-comment!] {doc_string}");
330 Ok(parse_quote! { #[doc = #doc_string] })
331}
332
333#[derive(Default)]
339struct HelperTokenStreams {
340 description_fns_stream: proc_macro2::TokenStream,
341 cache_on_disk_if_fns_stream: proc_macro2::TokenStream,
342}
343
344fn make_helpers_for_query(query: &Query, streams: &mut HelperTokenStreams) {
345 let Query { name, key_pat, key_ty, modifiers, .. } = &query;
346
347 let mut erased_name = name.clone();
349 erased_name.set_span(Span::call_site());
350
351 if let Some(CacheOnDiskIf { block, .. }) = modifiers.cache_on_disk_if.as_ref() {
353 streams.cache_on_disk_if_fns_stream.extend(quote! {
355 #[cfg_attr(not(bootstrap), allow(unused_variables, rustc::disallowed_pass_by_ref))]
356 #[cfg_attr(bootstrap, allow(unused_variables, rustc::pass_by_value))]
357 #[inline]
358 pub fn #erased_name<'tcx>(tcx: TyCtxt<'tcx>, #key_pat: &#key_ty) -> bool
359 #block
360 });
361 }
362
363 let Desc { expr_list, .. } = &modifiers.desc;
364
365 let desc = quote! {
366 #[allow(unused_variables)]
367 pub fn #erased_name<'tcx>(tcx: TyCtxt<'tcx>, #key_pat: #key_ty) -> String {
368 format!(#expr_list)
369 }
370 };
371
372 streams.description_fns_stream.extend(quote! {
373 #desc
374 });
375}
376
377fn add_to_analyzer_stream(query: &Query, analyzer_stream: &mut proc_macro2::TokenStream) {
379 let modifiers = &query.modifiers;
382
383 let mut modifiers_stream = quote! {};
384
385 let name = &modifiers.desc.modifier;
386 modifiers_stream.extend(quote! {
387 crate::query::modifiers::#name;
388 });
389
390 if let Some(CacheOnDiskIf { modifier, .. }) = &modifiers.cache_on_disk_if {
391 modifiers_stream.extend(quote! {
392 crate::query::modifiers::#modifier;
393 });
394 }
395
396 macro_rules! doc_link {
397 ( $( $modifier:ident ),+ $(,)? ) => {
398 $(
399 if let Some(name) = &modifiers.$modifier {
400 modifiers_stream.extend(quote! {
401 crate::query::modifiers::#name;
402 });
403 }
404 )+
405 }
406 }
407
408 doc_link!(
409 arena_cache,
410 cycle_fatal,
411 cycle_delay_bug,
412 cycle_stash,
413 no_hash,
414 anon,
415 eval_always,
416 depth_limit,
417 separate_provide_extern,
418 feedable,
419 return_result_from_ensure_ok,
420 );
421
422 let name = &query.name;
423
424 let mut erased_name = name.clone();
426 erased_name.set_span(Span::call_site());
427
428 let result = &query.return_ty;
429
430 let ra_hint = quote! {
439 let crate::query::Providers { #name: _, .. };
440 };
441
442 analyzer_stream.extend(quote! {
443 #[inline(always)]
444 fn #erased_name<'tcx>() #result {
445 #ra_hint
446 #modifiers_stream
447 loop {}
448 }
449 });
450}
451
452pub(super) fn rustc_queries(input: TokenStream) -> TokenStream {
453 let queries = parse_macro_input!(input as List<QueryEntry>);
454
455 let mut query_stream = quote! {};
456 let mut non_query_stream = quote! {};
457 let mut helpers = HelperTokenStreams::default();
458 let mut analyzer_stream = quote! {};
459 let mut errors = quote! {};
460
461 macro_rules! assert {
462 ( $cond:expr, $span:expr, $( $tt:tt )+ ) => {
463 if !$cond {
464 errors.extend(
465 Error::new($span, format!($($tt)+)).into_compile_error(),
466 );
467 }
468 }
469 }
470
471 for query in queries.0 {
472 let query = match query {
473 QueryEntry::Query(query) => query,
474 QueryEntry::NonQuery(NonQuery { doc_comments, name }) => {
475 non_query_stream.extend(quote! {
477 #(#doc_comments)*
478 #name,
479 });
480 continue;
481 }
482 };
483
484 let Query { doc_comments, name, key_ty, return_ty, modifiers, .. } = &query;
485
486 let return_ty = match return_ty {
488 ReturnType::Default => quote! { -> () },
489 ReturnType::Type(..) => quote! { #return_ty },
490 };
491
492 let modifiers_stream = make_modifiers_stream(&query, modifiers);
493
494 query_stream.extend(quote! {
496 #(#doc_comments)*
497 fn #name(#key_ty) #return_ty
498 { #modifiers_stream }
499 });
500
501 if let Some(feedable) = &modifiers.feedable {
502 assert!(
503 modifiers.anon.is_none(),
504 feedable.span(),
505 "Query {name} cannot be both `feedable` and `anon`."
506 );
507 assert!(
508 modifiers.eval_always.is_none(),
509 feedable.span(),
510 "Query {name} cannot be both `feedable` and `eval_always`."
511 );
512 }
513
514 add_to_analyzer_stream(&query, &mut analyzer_stream);
515 make_helpers_for_query(&query, &mut helpers);
516 }
517
518 let HelperTokenStreams { description_fns_stream, cache_on_disk_if_fns_stream } = helpers;
519
520 TokenStream::from(quote! {
521 #[macro_export]
525 macro_rules! rustc_with_all_queries {
526 (
527 $macro:ident!
529 ) => {
530 $macro! {
531 queries { #query_stream }
532 non_queries { #non_query_stream }
533 }
534 }
535 }
536
537 mod _analyzer_hints {
539 use super::*;
540 #analyzer_stream
541 }
542
543 pub mod _description_fns {
550 use super::*;
551 #description_fns_stream
552 }
553
554 pub mod _cache_on_disk_if_fns {
558 use super::*;
559 #cache_on_disk_if_fns_stream
560 }
561
562 #errors
563 })
564}