Description
I'm not entirely sure whether this is a bug or not, but the current behavior seems super finicky and unintuitive at worst, and I think it's likely a bug. I think this warrants some context on the use case, so I'd like to preface with that. There's a repro script at the bottom if you don't care about the context.
Custom derives have to work around the fact that they don't have access to $crate
for the crate they're associated with. Typically the way this is worked around is by doing const UNIQUE_NAME: () = { extern crate your_crate; /*code*/ };
. Diesel provides several derives which we want to allow third party crates to use, but also use them within Diesel itself. This means that the extern crate
workaround won't work for us. Instead we have this macro in Diesel:
macro_rules! __diesel_use_everything {
() => { pub use $crate::*; }
}
and then the generated code looks like this:
mod unique_name {
mod diesel {
__diesel_use_everything!();
}
/*code*/
}
However, this gets super finicky with hygiene. If we try to do that with nightly, using the derives within Diesel itself will complain that __diesel_use_everything!
can't be found. The fix for this is to give __diesel_use_everything!()
a call_site
span. Interestingly, the semicolon after it must have a def_site
span, or nothing it imported will be visible. The semicolon being significant is particularly weird to me, because it's basically enforcing that ()
or []
be used as the delimiters. If I wanted to invoke the macro with {}
, it would be impossible for me to make it work I have to ensure the braces have a def_site
span.
Anyway it's possible to work around this in the most basic cases by giving __diesel_use_everything!()
a call_site
span. However, we run into additional trouble when the use of the derive
originates inside a macro from Diesel (the actual macro is sql_function!
if you want a real use case). It'll still find __diesel_use_everything!()
but we get the same problem that we had if the ;
has a call_site
span. Nothing in this diesel
module is visible. use self::diesel::anything
will fail.
With all that said, here's a minimum repro script:
foo/lib.rs
#[macro_use]
extern crate bar;
macro_rules! __foo_use_everything {
() => {
pub use $crate::*;
};
}
pub struct Foo;
macro_rules! make_a_struct {
() => {
#[derive(Thingy)]
pub struct Bar;
};
}
make_a_struct!();
bar/lib.rs
#![feature(proc_macro)]
#[macro_use]
extern crate quote;
extern crate proc_macro2;
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
#[proc_macro_derive(Thingy)]
pub fn derive(_: TokenStream) -> TokenStream {
let call_site = Span::call_site();
let use_everything = quote_spanned!(call_site=> __foo_use_everything!());
quote!(
mod a_unique_name {
mod foo {
#use_everything;
}
use self::foo::Foo;
}
).into()
}
The workaround here is to call source
on the call_site
span (in proc_macro2
that looks like Span::call_site().unstable().source().into()
), but this requirement seems really weird to me. For that matter, the need to give the macro invocation any particular span at all is really surprising to me. This is a macro_rules!
macro, which by its very nature is non-hygienic and global. I think this invocation should work regardless of the span the macro name has. Even putting that aside though, it seems to me that a derive used inside a macro_rules!
macro should behave basically the same as one without (e.g. __diesel_use_everything!
should certainly resolve with call_site
regardless of where it's used)