Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 117 additions & 33 deletions compiler/rustc_macros/src/query.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 })
Expand All @@ -82,15 +83,27 @@ impl<T: Parse> Parse for List<T> {
}
}

struct Desc {
modifier: Ident,
tcx_binding: Option<Ident>,
expr_list: Punctuated<Expr, Token![,]>,
}

struct CacheOnDiskIf {
modifier: Ident,
tcx_binding: Option<Pat>,
block: Block,
}

struct QueryModifiers {
/// The description of the query.
desc: (Option<Ident>, Punctuated<Expr, Token![,]>),
desc: Desc,

/// Use this type for the in-memory cache.
arena_cache: Option<Ident>,

/// Cache the query to disk if the `Block` returns true.
cache_on_disk_if: Option<(Option<Pat>, Block)>,
cache_on_disk_if: Option<CacheOnDiskIf>,

/// A cycle error for this query aborting the compilation with a fatal error.
cycle_fatal: Option<Ident>,
Expand Down Expand Up @@ -164,23 +177,23 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
// `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::<Token![|]>()?;
let tcx = attr_content.parse()?;
attr_content.parse::<Token![|]>()?;
Some(tcx)
} 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)?;
Expand All @@ -189,7 +202,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
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" {
Expand Down Expand Up @@ -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)
}
};

Expand All @@ -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<Query>);

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 {
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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.
///
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<LocalDefId>, tcx: TyCtxt<'_>) -> String {
let def_id = def_id.into();
Expand Down
77 changes: 77 additions & 0 deletions compiler/rustc_middle/src/query/modifiers.rs
Original file line number Diff line number Diff line change
@@ -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;
12 changes: 8 additions & 4 deletions compiler/rustc_middle/src/query/plumbing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading