Skip to content

What exactly is $$crate's resolution span/crate? #151175

@CAD97

Description

@CAD97

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)C-bugCategory: This is a bug.F-macro_metavar_expr`#![feature(macro_metavar_expr)]`T-langRelevant to the language teamneeds-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions