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`.
This commit is contained in:
parent
ea3068efe4
commit
f916b0474a
34 changed files with 494 additions and 69 deletions
31
src/test/ui/proc-macro/auxiliary/custom-quote.rs
Normal file
31
src/test/ui/proc-macro/auxiliary/custom-quote.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// force-host
|
||||
// no-prefer-dynamic
|
||||
// ignore-tidy-linelength
|
||||
|
||||
#![feature(proc_macro_quote)]
|
||||
#![crate_type = "proc-macro"]
|
||||
|
||||
extern crate proc_macro;
|
||||
use std::str::FromStr;
|
||||
use proc_macro::*;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn custom_quote(input: TokenStream) -> TokenStream {
|
||||
let mut tokens: Vec<_> = input.into_iter().collect();
|
||||
assert_eq!(tokens.len(), 1, "Unexpected input: {:?}", tokens);
|
||||
match tokens.pop() {
|
||||
Some(TokenTree::Ident(ident)) => {
|
||||
assert_eq!(ident.to_string(), "my_ident");
|
||||
|
||||
let proc_macro_crate = TokenStream::from_str("::proc_macro").unwrap();
|
||||
let quoted_span = proc_macro::quote_span(proc_macro_crate, ident.span());
|
||||
let prefix = TokenStream::from_str(r#"let mut ident = proc_macro::Ident::new("my_ident", proc_macro::Span::call_site());"#).unwrap();
|
||||
let set_span_method = TokenStream::from_str("ident.set_span").unwrap();
|
||||
let set_span_arg = TokenStream::from(TokenTree::Group(Group::new(Delimiter::Parenthesis, quoted_span)));
|
||||
let suffix = TokenStream::from_str(";proc_macro::TokenStream::from(proc_macro::TokenTree::Ident(ident))").unwrap();
|
||||
let full_stream: TokenStream = std::array::IntoIter::new([prefix, set_span_method, set_span_arg, suffix]).collect();
|
||||
full_stream
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
49
src/test/ui/proc-macro/auxiliary/span-from-proc-macro.rs
Normal file
49
src/test/ui/proc-macro/auxiliary/span-from-proc-macro.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// force-host
|
||||
// no-prefer-dynamic
|
||||
|
||||
#![feature(proc_macro_quote)]
|
||||
#![feature(proc_macro_internals)] // FIXME - this shouldn't be necessary
|
||||
#![crate_type = "proc-macro"]
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate custom_quote;
|
||||
|
||||
use proc_macro::{quote, TokenStream};
|
||||
|
||||
macro_rules! expand_to_quote {
|
||||
() => {
|
||||
quote! {
|
||||
let bang_error: bool = 25;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn error_from_bang(_input: TokenStream) -> TokenStream {
|
||||
expand_to_quote!()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn other_error_from_bang(_input: TokenStream) -> TokenStream {
|
||||
custom_quote::custom_quote! {
|
||||
my_ident
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn error_from_attribute(_args: TokenStream, _input: TokenStream) -> TokenStream {
|
||||
quote! {
|
||||
struct AttributeError {
|
||||
field: MissingType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ErrorFromDerive)]
|
||||
pub fn error_from_derive(_input: TokenStream) -> TokenStream {
|
||||
quote! {
|
||||
enum DeriveError {
|
||||
Variant(OtherMissingType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,10 +45,10 @@ fn main /* 0#0 */() { ; }
|
|||
Expansions:
|
||||
0: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Root
|
||||
1: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
||||
2: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "produce_it")
|
||||
2: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "produce_it", proc_macro: false }
|
||||
3: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
||||
4: parent: ExpnId(2), call_site_ctxt: #4, def_site_ctxt: #0, kind: Macro(Bang, "meta_macro::print_def_site")
|
||||
5: parent: ExpnId(4), call_site_ctxt: #5, def_site_ctxt: #0, kind: Macro(Bang, "$crate::dummy")
|
||||
4: parent: ExpnId(2), call_site_ctxt: #4, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "meta_macro::print_def_site", proc_macro: true }
|
||||
5: parent: ExpnId(4), call_site_ctxt: #5, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "$crate::dummy", proc_macro: true }
|
||||
|
||||
SyntaxContexts:
|
||||
#0: parent: #0, outer_mark: (ExpnId(0), Opaque)
|
||||
|
|
|
|||
|
|
@ -69,10 +69,10 @@ fn main /* 0#0 */() { }
|
|||
Expansions:
|
||||
0: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Root
|
||||
1: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
||||
2: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "outer")
|
||||
2: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "outer", proc_macro: false }
|
||||
3: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
||||
4: parent: ExpnId(2), call_site_ctxt: #4, def_site_ctxt: #4, kind: Macro(Bang, "inner")
|
||||
5: parent: ExpnId(4), call_site_ctxt: #6, def_site_ctxt: #0, kind: Macro(Bang, "print_bang")
|
||||
4: parent: ExpnId(2), call_site_ctxt: #4, def_site_ctxt: #4, kind: Macro { kind: Bang, name: "inner", proc_macro: false }
|
||||
5: parent: ExpnId(4), call_site_ctxt: #6, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "print_bang", proc_macro: true }
|
||||
|
||||
SyntaxContexts:
|
||||
#0: parent: #0, outer_mark: (ExpnId(0), Opaque)
|
||||
|
|
|
|||
18
src/test/ui/proc-macro/quote-debug.rs
Normal file
18
src/test/ui/proc-macro/quote-debug.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// check-pass
|
||||
// force-host
|
||||
// no-prefer-dynamic
|
||||
// compile-flags: -Z unpretty=expanded
|
||||
//
|
||||
// This file is not actually used as a proc-macro - instead,
|
||||
// it's just used to show the output of the `quote!` macro
|
||||
|
||||
#![feature(proc_macro_quote)]
|
||||
#![crate_type = "proc-macro"]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
fn main() {
|
||||
proc_macro::quote! {
|
||||
let hello = "world";
|
||||
}
|
||||
}
|
||||
52
src/test/ui/proc-macro/quote-debug.stdout
Normal file
52
src/test/ui/proc-macro/quote-debug.stdout
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#![feature(prelude_import)]
|
||||
#![no_std]
|
||||
// check-pass
|
||||
// force-host
|
||||
// no-prefer-dynamic
|
||||
// compile-flags: -Z unpretty=expanded
|
||||
//
|
||||
// This file is not actually used as a proc-macro - instead,
|
||||
// it's just used to show the output of the `quote!` macro
|
||||
|
||||
#![feature(proc_macro_quote)]
|
||||
#![crate_type = "proc-macro"]
|
||||
#[prelude_import]
|
||||
use ::std::prelude::rust_2015::*;
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
fn main() {
|
||||
[crate::TokenStream::from(crate::TokenTree::Ident(crate::Ident::new("let",
|
||||
crate::Span::recover_proc_macro_span(0)))),
|
||||
crate::TokenStream::from(crate::TokenTree::Ident(crate::Ident::new("hello",
|
||||
crate::Span::recover_proc_macro_span(1)))),
|
||||
crate::TokenStream::from(crate::TokenTree::Punct(crate::Punct::new('\u{3d}',
|
||||
crate::Spacing::Alone))),
|
||||
crate::TokenStream::from(crate::TokenTree::Literal({
|
||||
let mut iter =
|
||||
"\"world\"".parse::<crate::TokenStream>().unwrap().into_iter();
|
||||
if let (Some(crate::TokenTree::Literal(mut lit)),
|
||||
None) =
|
||||
(iter.next(),
|
||||
iter.next())
|
||||
{
|
||||
lit.set_span(crate::Span::recover_proc_macro_span(2));
|
||||
lit
|
||||
} else {
|
||||
{
|
||||
::core::panicking::panic("internal error: entered unreachable code")
|
||||
}
|
||||
}
|
||||
})),
|
||||
crate::TokenStream::from(crate::TokenTree::Punct(crate::Punct::new('\u{3b}',
|
||||
crate::Spacing::Alone)))].iter().cloned().collect::<crate::TokenStream>()
|
||||
}
|
||||
const _: () =
|
||||
{
|
||||
extern crate proc_macro;
|
||||
#[rustc_proc_macro_decls]
|
||||
#[allow(deprecated)]
|
||||
static _DECLS: &[proc_macro::bridge::client::ProcMacro] = &[];
|
||||
};
|
||||
17
src/test/ui/proc-macro/span-from-proc-macro.rs
Normal file
17
src/test/ui/proc-macro/span-from-proc-macro.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// aux-build:custom-quote.rs
|
||||
// aux-build:span-from-proc-macro.rs
|
||||
// compile-flags: -Z macro-backtrace
|
||||
|
||||
#[macro_use]
|
||||
extern crate span_from_proc_macro;
|
||||
|
||||
#[error_from_attribute] //~ ERROR cannot find type `MissingType`
|
||||
struct ShouldBeRemoved;
|
||||
|
||||
#[derive(ErrorFromDerive)] //~ ERROR cannot find type `OtherMissingType`
|
||||
struct Kept;
|
||||
|
||||
fn main() {
|
||||
error_from_bang!(); //~ ERROR mismatched types
|
||||
other_error_from_bang!(); //~ ERROR cannot find value `my_ident`
|
||||
}
|
||||
62
src/test/ui/proc-macro/span-from-proc-macro.stderr
Normal file
62
src/test/ui/proc-macro/span-from-proc-macro.stderr
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
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
|
||||
|
||||
error[E0412]: cannot find type `OtherMissingType` in this scope
|
||||
--> $DIR/auxiliary/span-from-proc-macro.rs:46:21
|
||||
|
|
||||
LL | pub fn error_from_derive(_input: TokenStream) -> TokenStream {
|
||||
| ------------------------------------------------------------ in this expansion of procedural macro `#[derive(ErrorFromDerive)]`
|
||||
...
|
||||
LL | Variant(OtherMissingType)
|
||||
| ^^^^^^^^^^^^^^^^ not found in this scope
|
||||
|
|
||||
::: $DIR/span-from-proc-macro.rs:11:10
|
||||
|
|
||||
LL | #[derive(ErrorFromDerive)]
|
||||
| --------------- in this macro invocation
|
||||
|
||||
error[E0425]: cannot find value `my_ident` in this scope
|
||||
--> $DIR/auxiliary/span-from-proc-macro.rs:29:9
|
||||
|
|
||||
LL | pub fn other_error_from_bang(_input: TokenStream) -> TokenStream {
|
||||
| ---------------------------------------------------------------- in this expansion of procedural macro `other_error_from_bang!`
|
||||
LL | custom_quote::custom_quote! {
|
||||
LL | my_ident
|
||||
| ^^^^^^^^ not found in this scope
|
||||
|
|
||||
::: $DIR/span-from-proc-macro.rs:16:5
|
||||
|
|
||||
LL | other_error_from_bang!();
|
||||
| ------------------------- in this macro invocation
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/auxiliary/span-from-proc-macro.rs:16:36
|
||||
|
|
||||
LL | let bang_error: bool = 25;
|
||||
| ---- ^^ expected `bool`, found integer
|
||||
| |
|
||||
| expected due to this
|
||||
...
|
||||
LL | pub fn error_from_bang(_input: TokenStream) -> TokenStream {
|
||||
| ---------------------------------------------------------- in this expansion of procedural macro `error_from_bang!`
|
||||
|
|
||||
::: $DIR/span-from-proc-macro.rs:15:5
|
||||
|
|
||||
LL | error_from_bang!();
|
||||
| ------------------- in this macro invocation
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
Some errors have detailed explanations: E0308, E0412, E0425.
|
||||
For more information about an error, try `rustc --explain E0308`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue