Description
Up until recently I've considered these two function calls in proc_macro
, Span::default()
and Span::call_site()
relatively different. I'm not realizing, however, that they're actually quite significantly different depending on what you're doing in a procedural macro!
In working with the gnome-class macro we've ended up getting a good deal more experience with the procedural macro system. This macro is using dependencies like quote
, syn
, and proc-macro2
to parse and generate code. The code itself contains a mixture of modules and macro_rules-like macro expansions.
When we tried to enable the unstable
feature in proc-macro2
, which switches it to use the "real" proc_macro
APIs and preserve span information, it turned out everything broke! When digging into this I found that everything we were experiencing was related to the distinction between the default
and call_site
functions.
So as a bit of background, the gnome_class!
macro uses a few methods to manufacture a TokenStream
. Primarily it uses the quote!
macro from the quote
crate, which primarily at this time uses parse
for most tokens to generate a TokenTree
. Namely part of the quote!
macro will call stringify!
on each token to get reparsed at runtime and turned into a list of tokens. For delimiters and such the quote
crate currently creates a default
span.
Additionally both @federicomenaquintero and I were novices at the procedural macro/hygiene/span systems, so we didn't have a lot of info coming in! Now though we think we're a bit more up to speed :). The rest of this issue will be focused on "weird errors" that we had to work backwards to figure out how to solve. This all, to me at least, seems like a blocker for stabilization in the sense that I wouldn't wish this experience on others.
I'm not really sure what the conclusions from this would be though. The behavior below may be bugs in the compiler or bugs in the libraries we're using, I'm not sure! I'll write up some thoughts at the end though. In general though I wanted to just detail all that we went through in discovering this and showing how the current behavior that we have ends up being quite confusing.
Examples of odd errors
In any case, I've created an example project showcasing a number of the "surprises" (some bugs?) that we ran into. I'll try to recatalog them here:
Using parse
breaks super
The first bug that was found was related to generating a program that looked like:
mod foo {
use super::*;
}
It turns out that if you use parse
to generate the token super
it doesn't work! If you set the span of super
to default
, however, it does indeed work.
I was pretty surprised by this (and the odd error messages). I'm not really sure why the parse
span was not allowing it to resolve, but I imagine it was related to hygiene? I submitted dtolnay/quote#51 which I think might fix this but I wasn't sure if that was the right fix...
Is that the right fix for quote
? Should it be using default
wherever it can? I originally though that but then ran into...
Using Span::default
means you can't import from yourself
This second bug was found relating to the program that looks like:
struct A;
mod foo {
use super::A;
}
Here we have a failing procedural macro despite the usage of Span::default
on all tokens. This means that by default all modules generated via quote!
, if we were to switch spans to Span::default
, would not be able to import from one another it looks like? But maybe this is only related to super
? I'm not quite sure..
It also turns out that this does indeed work if we use Span::call_site
by default everywhere. I'm not really sure why, but it apparently works!
Using Span::default
means you can't import generated structs
Next up we had a bug related to:
pub struct A;
It turns out that if these tokens are using Span::default
this can't actually be used! In this test case you get an error about an unresolved import.
Like with before though if we use call_site
as a span everywhere this case does indeed work.
Is this expected? This means, I think, that all tokens with a Default
span can't be improted outside of the procedural macro.
Using Span::default
means you can't use external crates
Next we took a look at a program like:
use std::mem;
When generating these tokens with Span::default
it turns out that this becomes an unresolved import! That is, the usage of Span::default
seems like it's putting it in an entirely new namespace without access to even std
at the root. Coming from the perspective of not knowing a lot about spans/hygiene I found this a little confusing :)
As with the previous and remaining cases using call_site
as a span does indeed get this working.
Naturally the error message was pretty confusing here, but I guess this is expected? Hygiene I think necessitates this? Unsure...
Using Span::default
means you can't import from yourself
Next up we have a program like
use foo::*;
mod foo {
}
Here if we use Span::default
everywhere this program will not compile with the import becoming unresolved. For us this seemed to imply that if we generated new modules in a macro we basically can't use imports!
As per usual respanning with call_site
everywhere fixes this but I'd imagine has different hygiene implications. I'm not sure if this behavior was intended, although it seemed like it may be a bug in rustc?
Using Span::default
precludes working with "non hygienic macros"
This is a particularly interesting use case. The gnome_class!
procedural macro internally delegates to the glib_wrapper!
macro_rules macro in the expanded tokens. The glib_wrapper!
macro, however, in its current state does not work in an empty module but rather requires imports like std::ptr
in the environment. With Span::default
, however, the generated tokens in glib_wrapper!
couldn't see the tokens we generated with gnome_class!
.
For example if in one crate we have a macro like
#[macro_export]
macro_rules! a {
($a:ident) => (
fn _bar() {
mem::drop(3);
}
)
}
(note that this requires std::mem
to be imported to work)
and then we're generating a token stream that looks like:
mod foo {
extern crate std;
use self::std::mem;
a! {}
Note that the extern crate
is necessary due to one of the above situations (we can't import from the top-level extern crate
). Here though if we generated tokens with Span::default
as with many other cases this doesn't work! As usual if we respan with call_site
spans then this does indeed work.
Is this a bug? Or maybe more hygiene?
Conclusions
Overall for our use case we found that 100% of the time we should be using Span::call_site
instead of Span::default
to get things working. Whether or not that's what we wanted hygienically we're not sure! I couldn't really understand the hygiene implications here because tokens using Span::default
couldn't import from other modules defined next to it with the default span as well.
Should quote
and syn
move to using Span::call_site
by default instead of Span::default
? Or maybe Span::default
should be renamed to sound "less default" if it appears to not work most of the time? Or maybe Span::default
has bugs that need fixing?
I'm quite curious to hear what others think! Especially those that are particularly familiar with hygiene/macros, I'd love to hear ideas about whether this is expected behavior (and if so if we could maybe improve the error messages) or if we should perhaps be structuring the macro expansion differently. Similarly what would recommendations be for spanning tokens returned by quote!
in an external crate? Or syn
? (for example if I maufacture an Ident
, is there a "good default" for that?)
In any case, curious to hear others' thoughts!
cc @jseyfried
cc @nrc
cc @nikomatsakis
cc @federicomenaquintero
cc @dtolnay
cc @mystor