Skip to content

Resolve inner attributes from inside the module#155623

Draft
nik-rev wants to merge 1 commit intorust-lang:mainfrom
nik-contrib:inner-attribute-resolution
Draft

Resolve inner attributes from inside the module#155623
nik-rev wants to merge 1 commit intorust-lang:mainfrom
nik-contrib:inner-attribute-resolution

Conversation

@nik-rev
Copy link
Copy Markdown
Contributor

@nik-rev nik-rev commented Apr 21, 2026

Note: Draft PR, will likely be ready tomorrow

This PR changes the resolution of inner attributes to resolve inside the module, rather than outside.

Example:

macro x() {} // 1
    
mod a {
    macro x() {} // 2
    
    mod b {
        #![super::x]
    }
}

The "target module" is the module that the attribute applies to. In this case, it is b.

Before:

  • Inner attributes are resolved in the module containing the target item. super::x is resolved inside of a, and resolves to // 1.

After:

  • Inner attributes are resolved in target item. super::x is resolved inside of b, and resolves to // 2.

Motivation

@petrochenkov said

There's a quite strong argument for resolving the inner attributes from inside the module, and not from the outside - consistency with key-value attribute expansion and documentation links.

We have two kinds of paths being resolved in inner attributes:

mod m {
    //! 1) Documentation links [mac]
    #![doc = mac!()] // and 2) values in key-value attributes
    
    macro_rules! mac { () => ("my documentation") }
    use mac;
}

Both paths resolve to m::mac inside the module, and if mac is put outside of the module, then it won't resolve.
So I'd expect the inner attributes to behave the same way.

Avoiding problems with new names

If new names are introduced inside of the target module in a way that would change the resolution of the inner attribute, the compilation fails.

macro x() {} // 1

mod b {
    #![x]
    
    macro x() {} // 2
}

Before this PR, the x would resolve to // 1.

Now, a compilation error is raised:

  • Without // 2, the resolution would fail.
  • With // 2, the resolution succeeds.
  • The resolutions differ, so the program fails to compile.

This avoids issues where macros expanded inside of the module would create ambiguities in name resolution.

Implementation

Consider:

macro x() {} // 1

mod target {
    #![x]
    
    macro x() {} // 2
}
  • When resolving x, we haven't resolved target yet (we don't know what target will contain -- it does not even have a DefId yet)

  • We create an empty module mod _ {} as a sibling of target, and resolve x inside of it. The resolution (Res) is recorded.

  • When finalizing macro resolutions, we resolve x again, this time inside of the target module, with all items populated.

  • We compare the old resolution (// 1) (obtained during initial stage) with the new resolution (// 2) (obtained during finalization), if the 2 resolutions differ (which they do), we error.

Keeping track of the target module

When resolving x, target is not yet available - so its NodeId is just a placeholder that we can't actually keep.

We need to somehow hold on to the target module so we can resolve inside of it at a later point, in finalize_macro_resolutions.

The way this is done in this PR is hacky, so if there's a better way, let me know:

  • The entire Span of the target module is recorded
  • The expansion ID (ExpnId) of the inner attribute is recorded
  • In finalize_macro_resolutions we get the Module corresponding to the recorded ExpnId
  • We iterate over all children of the Module, and we find a child who's Span is inside of the original target module's span.
  • This child is the target module, yet this time we have the DefId for it, so we can resolve inside of it.
  • If there is no such child, then the target module is the crate root.

Related

r? petrochenkov

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants