Skip to content

Commit f916b04

Browse files
committed
Implement span quoting for proc-macros
This PR implements span quoting, allowing proc-macros to produce spans pointing *into their own crate*. This is used by the unstable `proc_macro::quote!` macro, allowing us to get error messages like this: ``` error[E0412]: cannot find type `MissingType` in this scope --> $DIR/auxiliary/span-from-proc-macro.rs:37:20 | LL | pub fn error_from_attribute(_args: TokenStream, _input: TokenStream) -> TokenStream { | ----------------------------------------------------------------------------------- in this expansion of procedural macro `#[error_from_attribute]` ... LL | field: MissingType | ^^^^^^^^^^^ not found in this scope | ::: $DIR/span-from-proc-macro.rs:8:1 | LL | #[error_from_attribute] | ----------------------- in this macro invocation ``` Here, `MissingType` occurs inside the implementation of the proc-macro `#[error_from_attribute]`. Previosuly, this would always result in a span pointing at `#[error_from_attribute]` This will make many proc-macro-related error message much more useful - when a proc-macro generates code containing an error, users will get an error message pointing directly at that code (within the macro definition), instead of always getting a span pointing at the macro invocation site. This is implemented as follows: * When a proc-macro crate is being *compiled*, it causes the `quote!` macro to get run. This saves all of the sapns in the input to `quote!` into the metadata of *the proc-macro-crate* (which we are currently compiling). The `quote!` macro then expands to a call to `proc_macro::Span::recover_proc_macro_span(id)`, where `id` is an opaque identifier for the span in the crate metadata. * When the same proc-macro crate is *run* (e.g. it is loaded from disk and invoked by some consumer crate), the call to `proc_macro::Span::recover_proc_macro_span` causes us to load the span from the proc-macro crate's metadata. The proc-macro then produces a `TokenStream` containing a `Span` pointing into the proc-macro crate itself. The recursive nature of 'quote!' can be difficult to understand at first. The file `src/test/ui/proc-macro/quote-debug.stdout` shows the output of the `quote!` macro, which should make this eaier to understand. This PR also supports custom quoting spans in custom quote macros (e.g. the `quote` crate). All span quoting goes through the `proc_macro::quote_span` method, which can be called by a custom quote macro to perform span quoting. An example of this usage is provided in `src/test/ui/proc-macro/auxiliary/custom-quote.rs` Custom quoting currently has a few limitations: In order to quote a span, we need to generate a call to `proc_macro::Span::recover_proc_macro_span`. However, proc-macros support renaming the `proc_macro` crate, so we can't simply hardcode this path. Previously, the `quote_span` method used the path `crate::Span` - however, this only works when it is called by the builtin `quote!` macro in the same crate. To support being called from arbitrary crates, we need access to the name of the `proc_macro` crate to generate a path. This PR adds an additional argument to `quote_span` to specify the name of the `proc_macro` crate. Howver, this feels kind of hacky, and we may want to change this before stabilizing anything quote-related. Additionally, using `quote_span` currently requires enabling the `proc_macro_internals` feature. The builtin `quote!` macro has an `#[allow_internal_unstable]` attribute, but this won't work for custom quote implementations. This will likely require some additional tricks to apply `allow_internal_unstable` to the span of `proc_macro::Span::recover_proc_macro_span`.
1 parent ea3068e commit f916b04

File tree

34 files changed

+494
-69
lines changed

34 files changed

+494
-69
lines changed

compiler/rustc_builtin_macros/src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::deriving::*;
2020

2121
use rustc_expand::base::{MacroExpanderFn, ResolverExpand, SyntaxExtensionKind};
2222
use rustc_expand::proc_macro::BangProcMacro;
23+
use rustc_span::def_id::LOCAL_CRATE;
2324
use rustc_span::symbol::sym;
2425

2526
mod asm;
@@ -114,5 +115,8 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
114115
}
115116

116117
let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
117-
register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })));
118+
register(
119+
sym::quote,
120+
SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client, krate: LOCAL_CRATE })),
121+
);
118122
}

compiler/rustc_errors/src/emitter.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,9 @@ pub trait Emitter {
309309
// are some which do actually involve macros.
310310
ExpnKind::Inlined | ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
311311

312-
ExpnKind::Macro(macro_kind, _) => Some(macro_kind),
312+
ExpnKind::Macro { kind: macro_kind, name: _, proc_macro: _ } => {
313+
Some(macro_kind)
314+
}
313315
}
314316
});
315317

@@ -371,10 +373,19 @@ pub trait Emitter {
371373
new_labels
372374
.push((trace.call_site, "in the inlined copy of this code".to_string()));
373375
} else if always_backtrace {
376+
let proc_macro = if let ExpnKind::Macro { kind: _, name: _, proc_macro: true } =
377+
trace.kind
378+
{
379+
"procedural macro "
380+
} else {
381+
""
382+
};
383+
374384
new_labels.push((
375385
trace.def_site,
376386
format!(
377-
"in this expansion of `{}`{}",
387+
"in this expansion of {}`{}`{}",
388+
proc_macro,
378389
trace.kind.descr(),
379390
if macro_backtrace.len() > 1 {
380391
// if macro_backtrace.len() == 1 it'll be
@@ -400,7 +411,11 @@ pub trait Emitter {
400411
// and it needs an "in this macro invocation" label to match that.
401412
let redundant_span = trace.call_site.contains(sp);
402413

403-
if !redundant_span && matches!(trace.kind, ExpnKind::Macro(MacroKind::Bang, _))
414+
if !redundant_span
415+
&& matches!(
416+
trace.kind,
417+
ExpnKind::Macro { kind: MacroKind::Bang, name: _, proc_macro: _ }
418+
)
404419
|| always_backtrace
405420
{
406421
new_labels.push((

compiler/rustc_expand/src/base.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT;
1414
use rustc_lint_defs::BuiltinLintDiagnostics;
1515
use rustc_parse::{self, nt_to_tokenstream, parser, MACRO_ARGUMENTS};
1616
use rustc_session::{parse::ParseSess, Limit, Session};
17-
use rustc_span::def_id::DefId;
17+
use rustc_span::def_id::{CrateNum, DefId};
1818
use rustc_span::edition::Edition;
1919
use rustc_span::hygiene::{AstPass, ExpnData, ExpnId, ExpnKind};
2020
use rustc_span::source_map::SourceMap;
@@ -810,8 +810,16 @@ impl SyntaxExtension {
810810
descr: Symbol,
811811
macro_def_id: Option<DefId>,
812812
) -> ExpnData {
813+
use SyntaxExtensionKind::*;
814+
let proc_macro = match self.kind {
815+
// User-defined proc macro
816+
Bang(..) | Attr(..) | Derive(..) => true,
817+
// Consider everthing else to be not a proc
818+
// macro for diagnostic purposes
819+
LegacyBang(..) | LegacyAttr(..) | NonMacroAttr { .. } | LegacyDerive(..) => false,
820+
};
813821
ExpnData::new(
814-
ExpnKind::Macro(self.macro_kind(), descr),
822+
ExpnKind::Macro { kind: self.macro_kind(), name: descr, proc_macro },
815823
parent,
816824
call_site,
817825
self.span,
@@ -873,6 +881,10 @@ pub trait ResolverExpand {
873881
fn take_derive_resolutions(&mut self, expn_id: ExpnId) -> Option<DeriveResolutions>;
874882
/// Path resolution logic for `#[cfg_accessible(path)]`.
875883
fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result<bool, Indeterminate>;
884+
885+
/// Decodes the proc-macro quoted span in the specified crate, with the specified id.
886+
/// No caching is performed.
887+
fn get_proc_macro_quoted_span(&self, krate: CrateNum, id: usize) -> Span;
876888
}
877889

878890
#[derive(Clone, Default)]

compiler/rustc_expand/src/proc_macro.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ use rustc_data_structures::sync::Lrc;
99
use rustc_errors::ErrorReported;
1010
use rustc_parse::nt_to_tokenstream;
1111
use rustc_parse::parser::ForceCollect;
12+
use rustc_span::def_id::CrateNum;
1213
use rustc_span::{Span, DUMMY_SP};
1314

1415
const EXEC_STRATEGY: pm::bridge::server::SameThread = pm::bridge::server::SameThread;
1516

1617
pub struct BangProcMacro {
1718
pub client: pm::bridge::client::Client<fn(pm::TokenStream) -> pm::TokenStream>,
19+
pub krate: CrateNum,
1820
}
1921

2022
impl base::ProcMacro for BangProcMacro {
@@ -24,7 +26,7 @@ impl base::ProcMacro for BangProcMacro {
2426
span: Span,
2527
input: TokenStream,
2628
) -> Result<TokenStream, ErrorReported> {
27-
let server = proc_macro_server::Rustc::new(ecx);
29+
let server = proc_macro_server::Rustc::new(ecx, self.krate);
2830
self.client.run(&EXEC_STRATEGY, server, input, ecx.ecfg.proc_macro_backtrace).map_err(|e| {
2931
let mut err = ecx.struct_span_err(span, "proc macro panicked");
3032
if let Some(s) = e.as_str() {
@@ -38,6 +40,7 @@ impl base::ProcMacro for BangProcMacro {
3840

3941
pub struct AttrProcMacro {
4042
pub client: pm::bridge::client::Client<fn(pm::TokenStream, pm::TokenStream) -> pm::TokenStream>,
43+
pub krate: CrateNum,
4144
}
4245

4346
impl base::AttrProcMacro for AttrProcMacro {
@@ -48,7 +51,7 @@ impl base::AttrProcMacro for AttrProcMacro {
4851
annotation: TokenStream,
4952
annotated: TokenStream,
5053
) -> Result<TokenStream, ErrorReported> {
51-
let server = proc_macro_server::Rustc::new(ecx);
54+
let server = proc_macro_server::Rustc::new(ecx, self.krate);
5255
self.client
5356
.run(&EXEC_STRATEGY, server, annotation, annotated, ecx.ecfg.proc_macro_backtrace)
5457
.map_err(|e| {
@@ -64,6 +67,7 @@ impl base::AttrProcMacro for AttrProcMacro {
6467

6568
pub struct ProcMacroDerive {
6669
pub client: pm::bridge::client::Client<fn(pm::TokenStream) -> pm::TokenStream>,
70+
pub krate: CrateNum,
6771
}
6872

6973
impl MultiItemModifier for ProcMacroDerive {
@@ -97,7 +101,7 @@ impl MultiItemModifier for ProcMacroDerive {
97101
nt_to_tokenstream(&item, &ecx.sess.parse_sess, CanSynthesizeMissingTokens::No)
98102
};
99103

100-
let server = proc_macro_server::Rustc::new(ecx);
104+
let server = proc_macro_server::Rustc::new(ecx, self.krate);
101105
let stream =
102106
match self.client.run(&EXEC_STRATEGY, server, input, ecx.ecfg.proc_macro_backtrace) {
103107
Ok(stream) => stream,

compiler/rustc_expand/src/proc_macro_server.rs

+67-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::base::ExtCtxt;
1+
use crate::base::{ExtCtxt, ResolverExpand};
22

33
use rustc_ast as ast;
44
use rustc_ast::token;
@@ -7,13 +7,16 @@ use rustc_ast::token::NtIdent;
77
use rustc_ast::tokenstream::{self, CanSynthesizeMissingTokens};
88
use rustc_ast::tokenstream::{DelimSpan, Spacing::*, TokenStream, TreeAndSpacing};
99
use rustc_ast_pretty::pprust;
10+
use rustc_data_structures::fx::FxHashMap;
1011
use rustc_data_structures::sync::Lrc;
1112
use rustc_errors::Diagnostic;
1213
use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT;
1314
use rustc_lint_defs::BuiltinLintDiagnostics;
1415
use rustc_parse::lexer::nfc_normalize;
1516
use rustc_parse::{nt_to_tokenstream, parse_stream_from_source_str};
1617
use rustc_session::parse::ParseSess;
18+
use rustc_span::def_id::CrateNum;
19+
use rustc_span::hygiene::ExpnId;
1720
use rustc_span::hygiene::ExpnKind;
1821
use rustc_span::symbol::{self, kw, sym, Symbol};
1922
use rustc_span::{BytePos, FileName, MultiSpan, Pos, RealFileName, SourceFile, Span};
@@ -355,22 +358,34 @@ pub struct Literal {
355358
}
356359

357360
pub(crate) struct Rustc<'a> {
361+
resolver: &'a dyn ResolverExpand,
358362
sess: &'a ParseSess,
359363
def_site: Span,
360364
call_site: Span,
361365
mixed_site: Span,
362366
span_debug: bool,
367+
krate: CrateNum,
368+
expn_id: ExpnId,
369+
rebased_spans: FxHashMap<usize, Span>,
363370
}
364371

365372
impl<'a> Rustc<'a> {
366-
pub fn new(cx: &'a ExtCtxt<'_>) -> Self {
373+
pub fn new(cx: &'a ExtCtxt<'_>, krate: CrateNum) -> Self {
367374
let expn_data = cx.current_expansion.id.expn_data();
375+
let def_site = cx.with_def_site_ctxt(expn_data.def_site);
376+
let call_site = cx.with_call_site_ctxt(expn_data.call_site);
377+
let mixed_site = cx.with_mixed_site_ctxt(expn_data.call_site);
378+
let sess = cx.parse_sess();
368379
Rustc {
369-
sess: &cx.sess.parse_sess,
370-
def_site: cx.with_def_site_ctxt(expn_data.def_site),
371-
call_site: cx.with_call_site_ctxt(expn_data.call_site),
372-
mixed_site: cx.with_mixed_site_ctxt(expn_data.call_site),
380+
resolver: cx.resolver,
381+
sess,
382+
def_site,
383+
call_site,
384+
mixed_site,
373385
span_debug: cx.ecfg.span_debug,
386+
krate,
387+
expn_id: cx.current_expansion.id,
388+
rebased_spans: FxHashMap::default(),
374389
}
375390
}
376391

@@ -713,6 +728,51 @@ impl server::Span for Rustc<'_> {
713728
fn source_text(&mut self, span: Self::Span) -> Option<String> {
714729
self.sess.source_map().span_to_snippet(span).ok()
715730
}
731+
/// Saves the provided span into the metadata of
732+
/// *the crate we are currently compiling*, which must
733+
/// be a proc-macro crate. This id can be passed to
734+
/// `recover_proc_macro_span` when our current crate
735+
/// is *run* as a proc-macro.
736+
///
737+
/// Let's suppose that we have two crates - `my_client`
738+
/// and `my_proc_macro`. The `my_proc_macro` crate
739+
/// contains a procedural macro `my_macro`, which
740+
/// is implemented as: `quote! { "hello" }`
741+
///
742+
/// When we *compile* `my_proc_macro`, we will execute
743+
/// the `quote` proc-macro. This will save the span of
744+
/// "hello" into the metadata of `my_proc_macro`. As a result,
745+
/// the body of `my_proc_macro` (after expansion) will end
746+
/// up containg a call that looks like this:
747+
/// `proc_macro::Ident::new("hello", proc_macro::Span::recover_proc_macro_span(0))`
748+
///
749+
/// where `0` is the id returned by this function.
750+
/// When `my_proc_macro` *executes* (during the compilation of `my_client`),
751+
/// the call to `recover_proc_macro_span` will load the corresponding
752+
/// span from the metadata of `my_proc_macro` (which we have access to,
753+
/// since we've loaded `my_proc_macro` from disk in order to execute it).
754+
/// In this way, we have obtained a span pointing into `my_proc_macro`
755+
fn save_span(&mut self, mut span: Self::Span) -> usize {
756+
// Throw away the `SyntaxContext`, since we currently
757+
// skip serializing `SyntaxContext`s for proc-macro crates
758+
span = span.with_ctxt(rustc_span::SyntaxContext::root());
759+
self.sess.save_proc_macro_span(span)
760+
}
761+
fn recover_proc_macro_span(&mut self, id: usize) -> Self::Span {
762+
let resolver = self.resolver;
763+
let krate = self.krate;
764+
let expn_id = self.expn_id;
765+
*self.rebased_spans.entry(id).or_insert_with(|| {
766+
let raw_span = resolver.get_proc_macro_quoted_span(krate, id);
767+
// Ignore the deserialized `SyntaxContext` entirely.
768+
// FIXME: Preserve the macro backtrace from the serialized span
769+
// For example, if a proc-macro crate has code like
770+
// `macro_one!() -> macro_two!() -> quote!()`, we might
771+
// want to 'concatenate' this backtrace with the backtrace from
772+
// our current call site.
773+
raw_span.with_def_site_ctxt(expn_id)
774+
})
775+
}
716776
}
717777

718778
// See issue #74616 for details
@@ -722,7 +782,7 @@ fn ident_name_compatibility_hack(
722782
rustc: &mut Rustc<'_>,
723783
) -> Option<(rustc_span::symbol::Ident, bool)> {
724784
if let NtIdent(ident, is_raw) = nt {
725-
if let ExpnKind::Macro(_, macro_name) = orig_span.ctxt().outer_expn_data().kind {
785+
if let ExpnKind::Macro { name: macro_name, .. } = orig_span.ctxt().outer_expn_data().kind {
726786
let source_map = rustc.sess.source_map();
727787
let filename = source_map.span_to_filename(orig_span);
728788
if let FileName::Real(RealFileName::Named(path)) = filename {

compiler/rustc_lint/src/internal.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,21 @@ impl EarlyLintPass for LintPassImpl {
248248
if last.ident.name == sym::LintPass {
249249
let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
250250
let call_site = expn_data.call_site;
251-
if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
252-
&& call_site.ctxt().outer_expn_data().kind
253-
!= ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
254-
{
251+
if !matches!(
252+
expn_data.kind,
253+
ExpnKind::Macro {
254+
kind: MacroKind::Bang,
255+
name: sym::impl_lint_pass,
256+
proc_macro: _
257+
}
258+
) && !matches!(
259+
call_site.ctxt().outer_expn_data().kind,
260+
ExpnKind::Macro {
261+
kind: MacroKind::Bang,
262+
name: sym::declare_lint_pass,
263+
proc_macro: _
264+
}
265+
) {
255266
cx.struct_span_lint(
256267
LINT_PASS_IMPL_WITHOUT_MACRO,
257268
lint_pass.path.span,

compiler/rustc_lint/src/non_fmt_panic.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,11 @@ fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span,
248248
}
249249
}
250250

251-
let macro_symbol = if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind {
252-
symbol
253-
} else {
254-
Symbol::intern("panic")
255-
};
251+
let macro_symbol =
252+
if let hygiene::ExpnKind::Macro { kind: _, name: symbol, proc_macro: _ } = expn.kind {
253+
symbol
254+
} else {
255+
Symbol::intern("panic")
256+
};
256257
(expn.call_site, panic_macro, macro_symbol.as_str())
257258
}

compiler/rustc_metadata/src/rmeta/decoder.rs

+27-11
Original file line numberDiff line numberDiff line change
@@ -716,30 +716,37 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
716716
.decode((self, sess))
717717
}
718718

719-
fn load_proc_macro(&self, id: DefIndex, sess: &Session) -> SyntaxExtension {
720-
let (name, kind, helper_attrs) = match *self.raw_proc_macro(id) {
719+
fn load_proc_macro(&self, def_id: DefId, sess: &Session) -> SyntaxExtension {
720+
let (name, kind, helper_attrs) = match *self.raw_proc_macro(def_id.index) {
721721
ProcMacro::CustomDerive { trait_name, attributes, client } => {
722722
let helper_attrs =
723723
attributes.iter().cloned().map(Symbol::intern).collect::<Vec<_>>();
724724
(
725725
trait_name,
726-
SyntaxExtensionKind::Derive(Box::new(ProcMacroDerive { client })),
726+
SyntaxExtensionKind::Derive(Box::new(ProcMacroDerive {
727+
client,
728+
krate: def_id.krate,
729+
})),
727730
helper_attrs,
728731
)
729732
}
730-
ProcMacro::Attr { name, client } => {
731-
(name, SyntaxExtensionKind::Attr(Box::new(AttrProcMacro { client })), Vec::new())
732-
}
733-
ProcMacro::Bang { name, client } => {
734-
(name, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })), Vec::new())
735-
}
733+
ProcMacro::Attr { name, client } => (
734+
name,
735+
SyntaxExtensionKind::Attr(Box::new(AttrProcMacro { client, krate: def_id.krate })),
736+
Vec::new(),
737+
),
738+
ProcMacro::Bang { name, client } => (
739+
name,
740+
SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client, krate: def_id.krate })),
741+
Vec::new(),
742+
),
736743
};
737744

738-
let attrs: Vec<_> = self.get_item_attrs(id, sess).collect();
745+
let attrs: Vec<_> = self.get_item_attrs(def_id.index, sess).collect();
739746
SyntaxExtension::new(
740747
sess,
741748
kind,
742-
self.get_span(id, sess),
749+
self.get_span(def_id.index, sess),
743750
helper_attrs,
744751
self.root.edition,
745752
Symbol::intern(name),
@@ -1379,6 +1386,15 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
13791386
}
13801387
}
13811388

1389+
fn get_proc_macro_quoted_span(&self, index: usize, sess: &Session) -> Span {
1390+
self.root
1391+
.tables
1392+
.proc_macro_quoted_spans
1393+
.get(self, index)
1394+
.unwrap_or_else(|| panic!("Missing proc macro quoted span: {:?}", index))
1395+
.decode((self, sess))
1396+
}
1397+
13821398
fn get_foreign_modules(&self, tcx: TyCtxt<'tcx>) -> Lrc<FxHashMap<DefId, ForeignModule>> {
13831399
if self.root.is_proc_macro_crate() {
13841400
// Proc macro crates do not have any *target* foreign modules.

0 commit comments

Comments
 (0)