diff --git a/compiler/rustc_macros/src/query.rs b/compiler/rustc_macros/src/query.rs index 6b5c427c8bc1b..5b869dc3409a7 100644 --- a/compiler/rustc_macros/src/query.rs +++ b/compiler/rustc_macros/src/query.rs @@ -1,4 +1,5 @@ use proc_macro::TokenStream; +use proc_macro2::Span; use quote::{quote, quote_spanned}; use syn::parse::{Parse, ParseStream, Result}; use syn::punctuated::Punctuated; @@ -62,7 +63,7 @@ impl Parse for Query { // If there are no doc-comments, give at least some idea of what // it does by showing the query description. if doc_comments.is_empty() { - doc_comments.push(doc_comment_from_desc(&modifiers.desc.1)?); + doc_comments.push(doc_comment_from_desc(&modifiers.desc.expr_list)?); } Ok(Query { doc_comments, modifiers, name, key, arg, result }) @@ -82,15 +83,27 @@ impl Parse for List { } } +struct Desc { + modifier: Ident, + tcx_binding: Option, + expr_list: Punctuated, +} + +struct CacheOnDiskIf { + modifier: Ident, + tcx_binding: Option, + block: Block, +} + struct QueryModifiers { /// The description of the query. - desc: (Option, Punctuated), + desc: Desc, /// Use this type for the in-memory cache. arena_cache: Option, /// Cache the query to disk if the `Block` returns true. - cache_on_disk_if: Option<(Option, Block)>, + cache_on_disk_if: Option, /// A cycle error for this query aborting the compilation with a fatal error. cycle_fatal: Option, @@ -164,7 +177,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result { // `desc { |tcx| "foo {}", tcx.item_path(key) }` let attr_content; braced!(attr_content in input); - let tcx = if attr_content.peek(Token![|]) { + let tcx_binding = if attr_content.peek(Token![|]) { attr_content.parse::()?; let tcx = attr_content.parse()?; attr_content.parse::()?; @@ -172,15 +185,15 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result { } else { None }; - let list = attr_content.parse_terminated(Expr::parse, Token![,])?; - try_insert!(desc = (tcx, list)); + let expr_list = attr_content.parse_terminated(Expr::parse, Token![,])?; + try_insert!(desc = Desc { modifier, tcx_binding, expr_list }); } else if modifier == "cache_on_disk_if" { // Parse a cache-on-disk modifier like: // // `cache_on_disk_if { true }` // `cache_on_disk_if { key.is_local() }` // `cache_on_disk_if(tcx) { tcx.is_typeck_child(key.to_def_id()) }` - let args = if input.peek(token::Paren) { + let tcx_binding = if input.peek(token::Paren) { let args; parenthesized!(args in input); let tcx = Pat::parse_single(&args)?; @@ -189,7 +202,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result { None }; let block = input.parse()?; - try_insert!(cache_on_disk_if = (args, block)); + try_insert!(cache_on_disk_if = CacheOnDiskIf { modifier, tcx_binding, block }); } else if modifier == "arena_cache" { try_insert!(arena_cache = modifier); } else if modifier == "cycle_fatal" { @@ -275,44 +288,32 @@ struct HelperTokenStreams { } fn make_helpers_for_query(query: &Query, streams: &mut HelperTokenStreams) { - let Query { name, key, modifiers, .. } = &query; + let Query { name, key, modifiers, arg, .. } = &query; - // This dead code exists to instruct rust-analyzer about the link between the `rustc_queries` - // query names and the corresponding produced provider. The issue is that by nature of this - // macro producing a higher order macro that has all its token in the macro declaration we lose - // any meaningful spans, resulting in rust-analyzer being unable to make the connection between - // the query name and the corresponding providers field. The trick to fix this is to have - // `rustc_queries` emit a field access with the given name's span which allows it to successfully - // show references / go to definition to the corresponding provider assignment which is usually - // the more interesting place. - let ra_hint = quote! { - let crate::query::Providers { #name: _, .. }; - }; + // Replace span for `name` to make rust-analyzer ignore it. + let mut erased_name = name.clone(); + erased_name.set_span(Span::call_site()); // Generate a function to check whether we should cache the query to disk, for some key. - if let Some((args, expr)) = modifiers.cache_on_disk_if.as_ref() { - let tcx = args.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ }); - // expr is a `Block`, meaning that `{ #expr }` gets expanded - // to `{ { stmts... } }`, which triggers the `unused_braces` lint. + if let Some(CacheOnDiskIf { tcx_binding, block, .. }) = modifiers.cache_on_disk_if.as_ref() { + let tcx = tcx_binding.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ }); // we're taking `key` by reference, but some rustc types usually prefer being passed by value streams.cache_on_disk_if_fns_stream.extend(quote! { - #[allow(unused_variables, unused_braces, rustc::pass_by_value)] + #[allow(unused_variables, rustc::pass_by_value)] #[inline] - pub fn #name<'tcx>(#tcx: TyCtxt<'tcx>, #key: &crate::queries::#name::Key<'tcx>) -> bool { - #ra_hint - #expr - } + pub fn #erased_name<'tcx>(#tcx: TyCtxt<'tcx>, #key: &crate::queries::#name::Key<'tcx>) -> bool + #block }); } - let (tcx, desc) = &modifiers.desc; - let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t }); + let Desc { tcx_binding, expr_list, .. } = &modifiers.desc; + let tcx = tcx_binding.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t }); let desc = quote! { #[allow(unused_variables)] - pub fn #name<'tcx>(tcx: TyCtxt<'tcx>, key: crate::queries::#name::Key<'tcx>) -> String { + pub fn #erased_name<'tcx>(tcx: TyCtxt<'tcx>, key: #arg) -> String { let (#tcx, #key) = (tcx, key); - format!(#desc) + format!(#expr_list) } }; @@ -321,12 +322,88 @@ fn make_helpers_for_query(query: &Query, streams: &mut HelperTokenStreams) { }); } +/// Add hints for rust-analyzer +fn add_to_analyzer_stream(query: &Query, analyzer_stream: &mut proc_macro2::TokenStream) { + // Add links to relevant modifiers + + let modifiers = &query.modifiers; + + let mut modifiers_stream = quote! {}; + + let name = &modifiers.desc.modifier; + modifiers_stream.extend(quote! { + crate::query::modifiers::#name; + }); + + if let Some(CacheOnDiskIf { modifier, .. }) = &modifiers.cache_on_disk_if { + modifiers_stream.extend(quote! { + crate::query::modifiers::#modifier; + }); + } + + macro_rules! doc_link { + ( $( $modifier:ident ),+ $(,)? ) => { + $( + if let Some(name) = &modifiers.$modifier { + modifiers_stream.extend(quote! { + crate::query::modifiers::#name; + }); + } + )+ + } + } + + doc_link!( + arena_cache, + cycle_fatal, + cycle_delay_bug, + cycle_stash, + no_hash, + anon, + eval_always, + depth_limit, + separate_provide_extern, + feedable, + return_result_from_ensure_ok, + ); + + let name = &query.name; + + // Replace span for `name` to make rust-analyzer ignore it. + let mut erased_name = name.clone(); + erased_name.set_span(Span::call_site()); + + let result = &query.result; + + // This dead code exists to instruct rust-analyzer about the link between the `rustc_queries` + // query names and the corresponding produced provider. The issue is that by nature of this + // macro producing a higher order macro that has all its token in the macro declaration we lose + // any meaningful spans, resulting in rust-analyzer being unable to make the connection between + // the query name and the corresponding providers field. The trick to fix this is to have + // `rustc_queries` emit a field access with the given name's span which allows it to successfully + // show references / go to definition to the corresponding provider assignment which is usually + // the more interesting place. + let ra_hint = quote! { + let crate::query::Providers { #name: _, .. }; + }; + + analyzer_stream.extend(quote! { + #[inline(always)] + fn #erased_name<'tcx>() #result { + #ra_hint + #modifiers_stream + loop {} + } + }); +} + pub(super) fn rustc_queries(input: TokenStream) -> TokenStream { let queries = parse_macro_input!(input as List); let mut query_stream = quote! {}; let mut helpers = HelperTokenStreams::default(); let mut feedable_queries = quote! {}; + let mut analyzer_stream = quote! {}; let mut errors = quote! {}; macro_rules! assert { @@ -409,6 +486,7 @@ pub(super) fn rustc_queries(input: TokenStream) -> TokenStream { }); } + add_to_analyzer_stream(&query, &mut analyzer_stream); make_helpers_for_query(&query, &mut helpers); } @@ -442,6 +520,12 @@ pub(super) fn rustc_queries(input: TokenStream) -> TokenStream { } } + // Add hints for rust-analyzer + mod _analyzer_hints { + use super::*; + #analyzer_stream + } + /// Functions that format a human-readable description of each query /// and its key, as specified by the `desc` query modifier. /// diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 24a38e70ff6f2..62d5c1f9dd208 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -13,6 +13,7 @@ mod keys; pub mod on_disk_cache; #[macro_use] pub mod plumbing; +pub(crate) mod modifiers; pub fn describe_as_module(def_id: impl Into, tcx: TyCtxt<'_>) -> String { let def_id = def_id.into(); diff --git a/compiler/rustc_middle/src/query/modifiers.rs b/compiler/rustc_middle/src/query/modifiers.rs new file mode 100644 index 0000000000000..81b9f0da6446c --- /dev/null +++ b/compiler/rustc_middle/src/query/modifiers.rs @@ -0,0 +1,77 @@ +//! This contains documentation which is linked from query modifiers used in the `rustc_queries!` proc macro. +#![allow(unused, non_camel_case_types)] +// FIXME: Update and clarify documentation for these modifiers. + +/// # `desc` query modifier +/// +/// The description of the query. This modifier is required on every query. +pub struct desc; + +/// # `arena_cache` query modifier +/// +/// Use this type for the in-memory cache. +pub struct arena_cache; + +/// # `cache_on_disk_if` query modifier +/// +/// Cache the query to disk if the `Block` returns true. +pub struct cache_on_disk_if; + +/// # `cycle_fatal` query modifier +/// +/// A cycle error for this query aborting the compilation with a fatal error. +pub struct cycle_fatal; + +/// # `cycle_delay_bug` query modifier +/// +/// A cycle error results in a delay_bug call +pub struct cycle_delay_bug; + +/// # `cycle_stash` query modifier +/// +/// A cycle error results in a stashed cycle error that can be unstashed and canceled later +pub struct cycle_stash; + +/// # `no_hash` query modifier +/// +/// Don't hash the result, instead just mark a query red if it runs +pub struct no_hash; + +/// # `anon` query modifier +/// +/// Generate a dep node based on the dependencies of the query +pub struct anon; + +/// # `eval_always` query modifier +/// +/// Always evaluate the query, ignoring its dependencies +pub struct eval_always; + +/// # `depth_limit` query modifier +/// +/// Whether the query has a call depth limit +pub struct depth_limit; + +/// # `separate_provide_extern` query modifier +/// +/// Use a separate query provider for local and extern crates +pub struct separate_provide_extern; + +/// # `feedable` query modifier +/// +/// Generate a `feed` method to set the query's value from another query. +pub struct feedable; + +/// # `return_result_from_ensure_ok` query modifier +/// +/// When this query is called via `tcx.ensure_ok()`, it returns +/// `Result<(), ErrorGuaranteed>` instead of `()`. If the query needs to +/// be executed, and that execution returns an error, the error result is +/// returned to the caller. +/// +/// If execution is skipped, a synthetic `Ok(())` is returned, on the +/// assumption that a query with all-green inputs must have succeeded. +/// +/// Can only be applied to queries with a return value of +/// `Result<_, ErrorGuaranteed>`. +pub struct return_result_from_ensure_ok; diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index 3c844eac1fca0..9a709d2c43c80 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -462,10 +462,14 @@ macro_rules! define_callbacks { } pub struct Providers { - $(pub $name: for<'tcx> fn( - TyCtxt<'tcx>, - $name::LocalKey<'tcx>, - ) -> $name::ProvidedValue<'tcx>,)* + $( + /// This is the provider for the query. Use `Find references` on this to + /// navigate between the provider assignment and the query definition. + pub $name: for<'tcx> fn( + TyCtxt<'tcx>, + $name::LocalKey<'tcx>, + ) -> $name::ProvidedValue<'tcx>, + )* } pub struct ExternProviders {