If we define the macro:
// crate upstream
#[macro_export]
macro_rules! outer {
() => {
#[macro_rules]
macro_rules! inner {
() => {
$$crate::ITEM
};
}
};
}
and then call it:
// crate downstream
outer!();
dbg!( inner!() );
in what crate namespace is ITEM looked up? Or in more precise implementation terms, when the kw::DollarCrate token is created, what is its inherited span for the purpose of symbol resolution?
The current behavior is that kw::DollarCrate inherits the exact span of the crate token of the {$, crate} token pair. For this example, it means that ITEM is looked up in the upstream crate that defined outer!. However, I claim that this behavior is not useful and even counterintuitive; if upstream wanted to say $crate::ITEM, it would have written $crate::ITEM. By writing $$crate::ITEM instead, upstream communicates a different intent: that ITEM should be looked up in the crate which invokes outer! to define item! (in our example, downstream).
My proposal is that instead, the behavior should be instead that kw::DollarCrate use the resolution span of the Ident symbol name of the macro which is expanding/gluing the $crate. Thus in this example, expanding outer! defines inner! with mixed-site resolution span, which sees the namespace its been expanded into, meaning that when inner! glues the $crate tokens together, the kw::DollarCrate will resolve to the downstream crate.
This is, unfortunately, a breaking change to existing stable semantics in an edge case: macros which use the workaround to define a $binding that acts like $$, but I posit that any case that uses $_ crate to emulate writing $$crate on stable wants the downstream-resolved version of the semantics, not the current upstream-resolved version, for the same reason: they could have just written $crate.
There's also the case of constructing $crate piecewise, e.g.
// crate upstream
#[macro_export]
macro_rules! outer {( $dollar:tt $krate:tt ) => {
macro_rules! define_middler {( $dollar_late:tt $krate_late:tt ) => {
#[macro_export]
macro_rules! middler {() => {
macro_rules! inner { () => { $dollar_late $krate_late :: ITEM }}
}}
}
define_middler!( $dollar $krate );
}}
// crate midstream
outer!($crate);
// crate downstream
middler!();
dbg!( inner!() );
The current behavior is to find midstream::ITEM. Under the proposed behavior, it would find downstream::ITEM. The repeated indirection between macros generating macros is required in order to defer expansion/gluing $crate until inner!, otherwise the $crate would be glued when expanding middler!, which has a resolution span in midstream.
@rustbot labels +F-macro_metavar_expr
(sort of—$$ makes this more relevant, but isn't required)
cc #83527
If we define the macro:
and then call it:
in what crate namespace is
ITEMlooked up? Or in more precise implementation terms, when thekw::DollarCratetoken is created, what is its inherited span for the purpose of symbol resolution?The current behavior is that
kw::DollarCrateinherits the exact span of thecratetoken of the {$,crate} token pair. For this example, it means thatITEMis looked up in theupstreamcrate that definedouter!. However, I claim that this behavior is not useful and even counterintuitive; ifupstreamwanted to say$crate::ITEM, it would have written$crate::ITEM. By writing$$crate::ITEMinstead,upstreamcommunicates a different intent: thatITEMshould be looked up in the crate which invokesouter!to defineitem!(in our example,downstream).My proposal is that instead, the behavior should be instead that
kw::DollarCrateuse the resolution span of the Ident symbol name of the macro which is expanding/gluing the$crate. Thus in this example, expandingouter!definesinner!with mixed-site resolution span, which sees the namespace its been expanded into, meaning that wheninner!glues the$cratetokens together, thekw::DollarCratewill resolve to thedownstreamcrate.This is, unfortunately, a breaking change to existing stable semantics in an edge case: macros which use the workaround to define a $binding that acts like
$$, but I posit that any case that uses$_ crateto emulate writing$$crateon stable wants thedownstream-resolved version of the semantics, not the currentupstream-resolved version, for the same reason: they could have just written$crate.There's also the case of constructing
$cratepiecewise, e.g.The current behavior is to find
midstream::ITEM. Under the proposed behavior, it would finddownstream::ITEM. The repeated indirection between macros generating macros is required in order to defer expansion/gluing$crateuntilinner!, otherwise the$cratewould be glued when expandingmiddler!, which has a resolution span inmidstream.@rustbot labels +F-macro_metavar_expr
(sort of—
$$makes this more relevant, but isn't required)cc #83527