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:
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:
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
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
Up until recently I've considered these two function calls in
proc_macro,Span::default()andSpan::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, andproc-macro2to parse and generate code. The code itself contains a mixture of modules and macro_rules-like macro expansions.When we tried to enable the
unstablefeature inproc-macro2, which switches it to use the "real"proc_macroAPIs 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 thedefaultandcall_sitefunctions.So as a bit of background, the
gnome_class!macro uses a few methods to manufacture aTokenStream. Primarily it uses thequote!macro from thequotecrate, which primarily at this time usesparsefor most tokens to generate aTokenTree. Namely part of thequote!macro will callstringify!on each token to get reparsed at runtime and turned into a list of tokens. For delimiters and such thequotecrate currently creates adefaultspan.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
parsebreakssuperThe first bug that was found was related to generating a program that looked like:
It turns out that if you use
parseto generate the tokensuperit doesn't work! If you set the span ofsupertodefault, however, it does indeed work.I was pretty surprised by this (and the odd error messages). I'm not really sure why the
parsespan 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 usingdefaultwherever it can? I originally though that but then ran into...Using
Span::defaultmeans you can't import from yourselfThis second bug was found relating to the program that looks like:
Here we have a failing procedural macro despite the usage of
Span::defaulton all tokens. This means that by default all modules generated viaquote!, if we were to switch spans toSpan::default, would not be able to import from one another it looks like? But maybe this is only related tosuper? I'm not quite sure..It also turns out that this does indeed work if we use
Span::call_siteby default everywhere. I'm not really sure why, but it apparently works!Using
Span::defaultmeans you can't import generated structsNext up we had a bug related to:
It turns out that if these tokens are using
Span::defaultthis 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_siteas a span everywhere this case does indeed work.Is this expected? This means, I think, that all tokens with a
Defaultspan can't be improted outside of the procedural macro.Using
Span::defaultmeans you can't use external cratesNext we took a look at a program like:
When generating these tokens with
Span::defaultit turns out that this becomes an unresolved import! That is, the usage ofSpan::defaultseems like it's putting it in an entirely new namespace without access to evenstdat 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_siteas 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::defaultmeans you can't import from yourselfNext up we have a program like
Here if we use
Span::defaulteverywhere 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_siteeverywhere 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::defaultprecludes working with "non hygienic macros"This is a particularly interesting use case. The
gnome_class!procedural macro internally delegates to theglib_wrapper!macro_rules macro in the expanded tokens. Theglib_wrapper!macro, however, in its current state does not work in an empty module but rather requires imports likestd::ptrin the environment. WithSpan::default, however, the generated tokens inglib_wrapper!couldn't see the tokens we generated withgnome_class!.For example if in one crate we have a macro like
(note that this requires
std::memto be imported to work)and then we're generating a token stream that looks like:
Note that the
extern crateis necessary due to one of the above situations (we can't import from the top-levelextern crate). Here though if we generated tokens withSpan::defaultas with many other cases this doesn't work! As usual if we respan withcall_sitespans 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_siteinstead ofSpan::defaultto 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 usingSpan::defaultcouldn't import from other modules defined next to it with the default span as well.Should
quoteandsynmove to usingSpan::call_siteby default instead ofSpan::default? Or maybeSpan::defaultshould be renamed to sound "less default" if it appears to not work most of the time? Or maybeSpan::defaulthas 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? Orsyn? (for example if I maufacture anIdent, 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