1use proc_macro::TokenStream;
2use quote::{quote, quote_spanned};
3use syn::parse::{Parse, ParseStream, Result};
4use syn::punctuated::Punctuated;
5use syn::spanned::Spanned;
6use syn::{
7 AttrStyle, Attribute, Block, Error, Expr, Ident, Pat, ReturnType, Token, Type, braced,
8 parenthesized, parse_macro_input, parse_quote, token,
9};
10
11mod kw {
12 syn::custom_keyword!(query);
13}
14
15fn 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 {
21 Err(Error::new(
22 attr.span(),
23 "attributes must be outer attributes (`///`), not inner attributes",
24 ))
25 } else {
26 Ok(attr)
27 }
28 };
29 attrs.into_iter().map(inner).collect()
30}
31
32struct Query {
34 doc_comments: Vec<Attribute>,
35 modifiers: QueryModifiers,
36 name: Ident,
37 key: Pat,
38 arg: Type,
39 result: ReturnType,
40}
41
42impl Parse for Query {
43 fn parse(input: ParseStream<'_>) -> Result<Self> {
44 let mut doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
45
46 input.parse::<kw::query>()?;
48 let name: Ident = input.parse()?;
49 let arg_content;
50 parenthesized!(arg_content in input);
51 let key = Pat::parse_single(&arg_content)?;
52 arg_content.parse::<Token![:]>()?;
53 let arg = arg_content.parse()?;
54 let result = input.parse()?;
55
56 let content;
58 braced!(content in input);
59 let modifiers = parse_query_modifiers(&content)?;
60
61 if doc_comments.is_empty() {
64 doc_comments.push(doc_comment_from_desc(&modifiers.desc.1)?);
65 }
66
67 Ok(Query { doc_comments, modifiers, name, key, arg, result })
68 }
69}
70
71struct List<T>(Vec<T>);
73
74impl<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()?);
79 }
80 Ok(List(list))
81 }
82}
83
84struct QueryModifiers {
85 desc: (Option<Ident>, Punctuated<Expr, Token![,]>),
87
88 arena_cache: Option<Ident>,
90
91 cache: Option<(Option<Pat>, Block)>,
93
94 fatal_cycle: Option<Ident>,
96
97 cycle_delay_bug: Option<Ident>,
99
100 cycle_stash: Option<Ident>,
102
103 no_hash: Option<Ident>,
105
106 anon: Option<Ident>,
108
109 eval_always: Option<Ident>,
111
112 depth_limit: Option<Ident>,
114
115 separate_provide_extern: Option<Ident>,
117
118 feedable: Option<Ident>,
120
121 return_result_from_ensure_ok: Option<Ident>,
132}
133
134fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
135 let mut arena_cache = None;
136 let mut cache = None;
137 let mut desc = None;
138 let mut fatal_cycle = None;
139 let mut cycle_delay_bug = None;
140 let mut cycle_stash = None;
141 let mut no_hash = None;
142 let mut anon = None;
143 let mut eval_always = None;
144 let mut depth_limit = None;
145 let mut separate_provide_extern = None;
146 let mut feedable = None;
147 let mut return_result_from_ensure_ok = None;
148
149 while !input.is_empty() {
150 let modifier: Ident = input.parse()?;
151
152 macro_rules! try_insert {
153 ($name:ident = $expr:expr) => {
154 if $name.is_some() {
155 return Err(Error::new(modifier.span(), "duplicate modifier"));
156 }
157 $name = Some($expr);
158 };
159 }
160
161 if modifier == "desc" {
162 let attr_content;
165 braced!(attr_content in input);
166 let tcx = if attr_content.peek(Token![|]) {
167 attr_content.parse::<Token![|]>()?;
168 let tcx = attr_content.parse()?;
169 attr_content.parse::<Token![|]>()?;
170 Some(tcx)
171 } else {
172 None
173 };
174 let list = attr_content.parse_terminated(Expr::parse, Token![,])?;
175 try_insert!(desc = (tcx, list));
176 } else if modifier == "cache_on_disk_if" {
177 let args = if input.peek(token::Paren) {
180 let args;
181 parenthesized!(args in input);
182 let tcx = Pat::parse_single(&args)?;
183 Some(tcx)
184 } else {
185 None
186 };
187 let block = input.parse()?;
188 try_insert!(cache = (args, block));
189 } else if modifier == "arena_cache" {
190 try_insert!(arena_cache = modifier);
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 == "cycle_stash" {
196 try_insert!(cycle_stash = modifier);
197 } else if modifier == "no_hash" {
198 try_insert!(no_hash = modifier);
199 } else if modifier == "anon" {
200 try_insert!(anon = modifier);
201 } else if modifier == "eval_always" {
202 try_insert!(eval_always = modifier);
203 } else if modifier == "depth_limit" {
204 try_insert!(depth_limit = modifier);
205 } else if modifier == "separate_provide_extern" {
206 try_insert!(separate_provide_extern = modifier);
207 } else if modifier == "feedable" {
208 try_insert!(feedable = modifier);
209 } else if modifier == "return_result_from_ensure_ok" {
210 try_insert!(return_result_from_ensure_ok = modifier);
211 } else {
212 return Err(Error::new(modifier.span(), "unknown query modifier"));
213 }
214 }
215 let Some(desc) = desc else {
216 return Err(input.error("no description provided"));
217 };
218 Ok(QueryModifiers {
219 arena_cache,
220 cache,
221 desc,
222 fatal_cycle,
223 cycle_delay_bug,
224 cycle_stash,
225 no_hash,
226 anon,
227 eval_always,
228 depth_limit,
229 separate_provide_extern,
230 feedable,
231 return_result_from_ensure_ok,
232 })
233}
234
235fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
236 use ::syn::*;
237 let mut iter = list.iter();
238 let format_str: String = match iter.next() {
239 Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
240 lit_str.value().replace("`{}`", "{}") }
242 _ => return Err(Error::new(list.span(), "Expected a string literal")),
243 };
244 let mut fmt_fragments = format_str.split("{}");
245 let mut doc_string = fmt_fragments.next().unwrap().to_string();
246 iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
247 |(tts, next_fmt_fragment)| {
248 use ::core::fmt::Write;
249 write!(
250 &mut doc_string,
251 " `{}` {}",
252 tts.to_string().replace(" . ", "."),
253 next_fmt_fragment,
254 )
255 .unwrap();
256 },
257 );
258 let doc_string = format!("[query description - consider adding a doc-comment!] {doc_string}");
259 Ok(parse_quote! { #[doc = #doc_string] })
260}
261
262fn add_query_desc_cached_impl(
264 query: &Query,
265 descs: &mut proc_macro2::TokenStream,
266 cached: &mut proc_macro2::TokenStream,
267) {
268 let Query { name, key, modifiers, .. } = &query;
269
270 let cache = if let Some((args, expr)) = modifiers.cache.as_ref() {
272 let tcx = args.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ });
273 quote! {
277 #[allow(unused_variables, unused_braces, rustc::pass_by_value)]
278 #[inline]
279 pub fn #name<'tcx>(#tcx: TyCtxt<'tcx>, #key: &crate::query::queries::#name::Key<'tcx>) -> bool {
280 #expr
281 }
282 }
283 } else {
284 quote! {
285 #[allow(rustc::pass_by_value)]
287 #[inline]
288 pub fn #name<'tcx>(_: TyCtxt<'tcx>, _: &crate::query::queries::#name::Key<'tcx>) -> bool {
289 false
290 }
291 }
292 };
293
294 let (tcx, desc) = &modifiers.desc;
295 let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
296
297 let desc = quote! {
298 #[allow(unused_variables)]
299 pub fn #name<'tcx>(tcx: TyCtxt<'tcx>, key: crate::query::queries::#name::Key<'tcx>) -> String {
300 let (#tcx, #key) = (tcx, key);
301 ::rustc_middle::ty::print::with_no_trimmed_paths!(
302 format!(#desc)
303 )
304 }
305 };
306
307 descs.extend(quote! {
308 #desc
309 });
310
311 cached.extend(quote! {
312 #cache
313 });
314}
315
316pub(super) fn rustc_queries(input: TokenStream) -> TokenStream {
317 let queries = parse_macro_input!(input as List<Query>);
318
319 let mut query_stream = quote! {};
320 let mut query_description_stream = quote! {};
321 let mut query_cached_stream = quote! {};
322 let mut feedable_queries = quote! {};
323 let mut errors = quote! {};
324
325 macro_rules! assert {
326 ( $cond:expr, $span:expr, $( $tt:tt )+ ) => {
327 if !$cond {
328 errors.extend(
329 Error::new($span, format!($($tt)+)).into_compile_error(),
330 );
331 }
332 }
333 }
334
335 for query in queries.0 {
336 let Query { name, arg, modifiers, .. } = &query;
337 let result_full = &query.result;
338 let result = match query.result {
339 ReturnType::Default => quote! { -> () },
340 _ => quote! { #result_full },
341 };
342
343 let mut attributes = Vec::new();
344
345 macro_rules! passthrough {
346 ( $( $modifier:ident ),+ $(,)? ) => {
347 $( if let Some($modifier) = &modifiers.$modifier {
348 attributes.push(quote! { (#$modifier) });
349 }; )+
350 }
351 }
352
353 passthrough!(
354 fatal_cycle,
355 arena_cache,
356 cycle_delay_bug,
357 cycle_stash,
358 no_hash,
359 anon,
360 eval_always,
361 depth_limit,
362 separate_provide_extern,
363 return_result_from_ensure_ok,
364 );
365
366 if modifiers.cache.is_some() {
367 attributes.push(quote! { (cache) });
368 }
369 if modifiers.cache.is_some() {
371 attributes.push(quote! { (cache) });
372 }
373
374 let span = name.span();
381 let attribute_stream = quote_spanned! {span=> #(#attributes),*};
382 let doc_comments = &query.doc_comments;
383 query_stream.extend(quote! {
385 #(#doc_comments)*
386 [#attribute_stream] fn #name(#arg) #result,
387 });
388
389 if let Some(feedable) = &modifiers.feedable {
390 assert!(
391 modifiers.anon.is_none(),
392 feedable.span(),
393 "Query {name} cannot be both `feedable` and `anon`."
394 );
395 assert!(
396 modifiers.eval_always.is_none(),
397 feedable.span(),
398 "Query {name} cannot be both `feedable` and `eval_always`."
399 );
400 feedable_queries.extend(quote! {
401 #(#doc_comments)*
402 [#attribute_stream] fn #name(#arg) #result,
403 });
404 }
405
406 add_query_desc_cached_impl(&query, &mut query_description_stream, &mut query_cached_stream);
407 }
408
409 TokenStream::from(quote! {
410 #[macro_export]
411 macro_rules! rustc_query_append {
412 ($macro:ident! $( [$($other:tt)*] )?) => {
413 $macro! {
414 $( $($other)* )?
415 #query_stream
416 }
417 }
418 }
419 macro_rules! rustc_feedable_queries {
420 ( $macro:ident! ) => {
421 $macro!(#feedable_queries);
422 }
423 }
424 pub mod descs {
425 use super::*;
426 #query_description_stream
427 }
428 pub mod cached {
429 use super::*;
430 #query_cached_stream
431 }
432 #errors
433 })
434}