What exactly is $$crate's resolution span/crate?
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.2k
- PR merge metrics
- PR metrics pending
Description
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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing creation of the kw::DollarCrate token and how its inherited or resolution span is used for symbol lookup. Compare the documented upstream, downstream, and multi-level macro examples; done means reaching agreement on the intended span semantics and covering the relevant cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100