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, Error, Expr, Ident, Pat, ReturnType, Token, Type, braced, parenthesized,
9 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,
133 expr_list: Punctuated<Expr, Token![,]>,
134}
135
136struct QueryModifiers {
138 arena_cache: Option<Ident>,
140 cache_on_disk: Option<Ident>,
141 depth_limit: Option<Ident>,
142 desc: Desc,
143 eval_always: Option<Ident>,
144 feedable: Option<Ident>,
145 handle_cycle_error: Option<Ident>,
146 no_force: Option<Ident>,
147 no_hash: Option<Ident>,
148 separate_provide_extern: Option<Ident>,
149 }
151
152fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
153 let mut arena_cache = None;
155 let mut cache_on_disk = None;
156 let mut depth_limit = None;
157 let mut desc = None;
158 let mut eval_always = None;
159 let mut feedable = None;
160 let mut handle_cycle_error = None;
161 let mut no_force = None;
162 let mut no_hash = None;
163 let mut separate_provide_extern = None;
164 while !input.is_empty() {
167 let modifier: Ident = input.parse()?;
168
169 macro_rules! try_insert {
170 ($name:ident = $expr:expr) => {
171 if $name.is_some() {
172 return Err(Error::new(modifier.span(), "duplicate modifier"));
173 }
174 $name = Some($expr);
175 };
176 }
177
178 if modifier == "arena_cache" {
179 try_insert!(arena_cache = modifier);
180 } else if modifier == "cache_on_disk" {
181 try_insert!(cache_on_disk = modifier);
182 } else if modifier == "depth_limit" {
183 try_insert!(depth_limit = modifier);
184 } else if modifier == "desc" {
185 let attr_content;
188 braced!(attr_content in input);
189 let expr_list = attr_content.parse_terminated(Expr::parse, Token![,])?;
190 try_insert!(desc = Desc { modifier, expr_list });
191 } else if modifier == "eval_always" {
192 try_insert!(eval_always = modifier);
193 } else if modifier == "feedable" {
194 try_insert!(feedable = modifier);
195 } else if modifier == "handle_cycle_error" {
196 try_insert!(handle_cycle_error = modifier);
197 } else if modifier == "no_force" {
198 try_insert!(no_force = modifier);
199 } else if modifier == "no_hash" {
200 try_insert!(no_hash = modifier);
201 } else if modifier == "separate_provide_extern" {
202 try_insert!(separate_provide_extern = modifier);
203 } else {
204 return Err(Error::new(modifier.span(), "unknown query modifier"));
205 }
206 }
207 let Some(desc) = desc else {
208 return Err(input.error("no description provided"));
209 };
210 Ok(QueryModifiers {
211 arena_cache,
213 cache_on_disk,
214 depth_limit,
215 desc,
216 eval_always,
217 feedable,
218 handle_cycle_error,
219 no_force,
220 no_hash,
221 separate_provide_extern,
222 })
224}
225
226fn returns_error_guaranteed(ret_ty: &ReturnType) -> bool {
228 use ::syn::*;
229 if let ReturnType::Type(_, ret_ty) = ret_ty
230 && let Type::Path(type_path) = ret_ty.as_ref()
231 && let Some(PathSegment { ident, arguments }) = type_path.path.segments.last()
232 && ident == "Result"
233 && let PathArguments::AngleBracketed(args) = arguments
234 && args.args.len() == 2
235 && let GenericArgument::Type(ty) = &args.args[1]
236 && let Type::Path(type_path) = ty
237 && type_path.path.is_ident("ErrorGuaranteed")
238 {
239 true
240 } else {
241 false
242 }
243}
244
245fn make_modifiers_stream(query: &Query) -> proc_macro2::TokenStream {
246 let QueryModifiers {
247 arena_cache,
249 cache_on_disk,
250 depth_limit,
251 desc,
252 eval_always,
253 feedable,
254 handle_cycle_error,
255 no_force,
256 no_hash,
257 separate_provide_extern,
258 } = &query.modifiers;
260
261 let arena_cache = arena_cache.is_some();
263 let cache_on_disk = cache_on_disk.is_some();
264 let depth_limit = depth_limit.is_some();
265 let desc = {
266 let key_pat = &query.key_pat;
268 let key_ty = &query.key_ty;
269 let desc_expr_list = &desc.expr_list;
270 quote! {
271 {
272 #[allow(unused_variables)]
273 |tcx: TyCtxt<'tcx>, #key_pat: #key_ty| format!(#desc_expr_list)
274 }
275 }
276 };
277 let eval_always = eval_always.is_some();
278 let feedable = feedable.is_some();
279 let handle_cycle_error = handle_cycle_error.is_some();
280 let no_force = no_force.is_some();
281 let no_hash = no_hash.is_some();
282 let returns_error_guaranteed = returns_error_guaranteed(&query.return_ty);
283 let separate_provide_extern = separate_provide_extern.is_some();
284 let query_name_span = query.name.span();
290 quote_spanned! {
291 query_name_span =>
292 arena_cache: #arena_cache,
295 cache_on_disk: #cache_on_disk,
296 depth_limit: #depth_limit,
297 desc: #desc,
298 eval_always: #eval_always,
299 feedable: #feedable,
300 handle_cycle_error: #handle_cycle_error,
301 no_force: #no_force,
302 no_hash: #no_hash,
303 returns_error_guaranteed: #returns_error_guaranteed,
304 separate_provide_extern: #separate_provide_extern,
305 }
307}
308
309fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> {
310 use ::syn::*;
311 let mut iter = list.iter();
312 let format_str: String = match iter.next() {
313 Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
314 lit_str.value().replace("`{}`", "{}") }
316 _ => return Err(Error::new(list.span(), "Expected a string literal")),
317 };
318 let mut fmt_fragments = format_str.split("{}");
319 let mut doc_string = fmt_fragments.next().unwrap().to_string();
320 iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
321 |(tts, next_fmt_fragment)| {
322 use ::core::fmt::Write;
323 write!(
324 &mut doc_string,
325 " `{}` {}",
326 tts.to_string().replace(" . ", "."),
327 next_fmt_fragment,
328 )
329 .unwrap();
330 },
331 );
332 let doc_string = format!("[query description - consider adding a doc-comment!] {doc_string}");
333 Ok(parse_quote! { #[doc = #doc_string] })
334}
335
336fn add_to_analyzer_stream(query: &Query, analyzer_stream: &mut proc_macro2::TokenStream) {
338 let modifiers = &query.modifiers;
341
342 let mut modifiers_stream = quote! {};
343
344 let name = &modifiers.desc.modifier;
345 modifiers_stream.extend(quote! {
346 crate::query::modifiers::#name;
347 });
348
349 macro_rules! doc_link {
350 ( $( $modifier:ident ),+ $(,)? ) => {
351 $(
352 if let Some(name) = &modifiers.$modifier {
353 modifiers_stream.extend(quote! {
354 crate::query::modifiers::#name;
355 });
356 }
357 )+
358 }
359 }
360
361 doc_link!(
362 arena_cache,
364 cache_on_disk,
365 depth_limit,
366 eval_always,
368 feedable,
369 handle_cycle_error,
370 no_force,
371 no_hash,
372 separate_provide_extern,
373 );
375
376 let name = &query.name;
377
378 let mut erased_name = name.clone();
380 erased_name.set_span(Span::call_site());
381
382 let result = &query.return_ty;
383
384 let ra_hint = quote! {
393 let crate::query::Providers { #name: _, .. };
394 };
395
396 analyzer_stream.extend(quote! {
397 #[inline(always)]
398 fn #erased_name<'tcx>() #result {
399 #ra_hint
400 #modifiers_stream
401 loop {}
402 }
403 });
404}
405
406pub(super) fn rustc_queries(input: TokenStream) -> TokenStream {
407 let queries = parse_macro_input!(input as List<QueryEntry>);
408
409 let mut query_stream = quote! {};
410 let mut non_query_stream = quote! {};
411 let mut analyzer_stream = quote! {};
412 let mut errors = quote! {};
413
414 macro_rules! assert {
415 ( $cond:expr, $span:expr, $( $tt:tt )+ ) => {
416 if !$cond {
417 errors.extend(
418 Error::new($span, format!($($tt)+)).into_compile_error(),
419 );
420 }
421 }
422 }
423
424 for query in queries.0 {
425 let query = match query {
426 QueryEntry::Query(query) => query,
427 QueryEntry::NonQuery(NonQuery { doc_comments, name }) => {
428 non_query_stream.extend(quote! {
430 #(#doc_comments)*
431 #name,
432 });
433 continue;
434 }
435 };
436
437 let Query { doc_comments, name, key_ty, return_ty, modifiers, .. } = &query;
438
439 let return_ty = match return_ty {
441 ReturnType::Default => quote! { -> () },
442 ReturnType::Type(..) => quote! { #return_ty },
443 };
444
445 let modifiers_stream = make_modifiers_stream(&query);
446
447 query_stream.extend(quote! {
449 #(#doc_comments)*
450 fn #name(#key_ty) #return_ty
451 { #modifiers_stream }
452 });
453
454 if let Some(feedable) = &modifiers.feedable {
455 assert!(
456 modifiers.eval_always.is_none(),
457 feedable.span(),
458 "Query {name} cannot be both `feedable` and `eval_always`."
459 );
460 }
461
462 add_to_analyzer_stream(&query, &mut analyzer_stream);
463 }
464
465 TokenStream::from(quote! {
466 #[rustc_macro_transparency = "semiopaque"] pub macro rustc_with_all_queries {
471 (
472 $macro:ident!
474 ) => {
475 $macro! {
476 queries { #query_stream }
477 non_queries { #non_query_stream }
478 }
479 }
480 }
481
482 mod _analyzer_hints {
484 use super::*;
485 #analyzer_stream
486 }
487
488 #errors
489 })
490}