Skip to content

Conversation

@jdonszelmann
Copy link
Contributor

@jdonszelmann jdonszelmann commented Jan 8, 2026

Closes #149981

This used to ICE:

macro_rules! foo_impl {}
#[eii]
fn foo_impl() {}

#[eii] generates a macro (called foo_impl) and a default impl. So the partial expansion used to roughly look like the following:

macro_rules! foo_impl {} // actually resolves here

extern "Rust" {
    fn foo_impl();
}

#[eii_extern_target(foo_impl)]
macro foo_impl {
    () => {};
}

const _: () = {
    #[implements_eii(foo_impl)] // assumed to name resolve to the macro v2 above
    fn foo_impl() {}
};

Now, shadowing rules for macrov2 and macrov1 are super weird! Take a look at this: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=23f21421921360478b0ec0276711ad36

So instead of resolving to the macrov2, we resolve the macrov1 named the same thing.

A regression test was added to this, and some span_delayed_bugs were added to make sure we catch this in the right places. But that didn't fix the root cause.

To make sure this simply cannot happen again, I made it so that we don't even need to do a name resolution for the default. In other words, the new partial expansion looks more like:

macro_rules! foo_impl {}

extern "Rust" {
    fn foo_impl(); // resolves to here now!!!
}

#[eii_extern_target(foo_impl)]
macro foo_impl {
    () => {};
}

const _: () = {
    #[implements_eii(known_extern_target=foo_impl)] // still name resolved, but directly to the foreign function. 
    fn foo_impl() {}
};

The reason this helps is that name resolution for non-macros is much more predictable. It's not possible to have two functions like that with the same name in scope.

We used to key externally implementable items off of the defid of the macro, but now the unique identifier is the foreign function's defid which seems much more sane.

Finally, I lied a tiny bit because the above partial expansion doesn't actually work.

extern "Rust" {
    fn foo_impl(); // not to here
}

const _: () = {
    #[implements_eii(known_extern_target=foo_impl)] // actually resolves to this function itself
    fn foo_impl() {} // <--- so to here
};

So the last few commits change the expansion to actually be this:

macro_rules! foo_impl {}

extern "Rust" {
    fn foo_impl(); // resolves to here now!!!
}

#[eii_extern_target(foo_impl)]
macro foo_impl {
    () => {};
}

const _: () = {
    mod dflt { // necessary, otherwise `super` doesn't work
        use super::*;
        #[implements_eii(known_extern_target=super::foo_impl)] // now resolves to outside the `dflt` module, so the foreign item.
        fn foo_impl() {}
    }
};

I apologize to whoever needs to review this, this is very subtle and I hope this makes it clear enough 😭.

@rustbot rustbot added A-attributes Area: Attributes (`#[…]`, `#![…]`) A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jan 8, 2026
@rustbot
Copy link
Collaborator

rustbot commented Jan 8, 2026

r? @chenyukang

rustbot has assigned @chenyukang.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added the S-blocked Status: Blocked on something else such as an RFC or other implementation work. label Jan 8, 2026
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

extern_item
}
EiiImplResolution::Known(decl, _) => decl.eii_extern_target,
EiiImplResolution::Error(_eg) => continue,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this _eg a leftover from some logic here? i see the same pattern in many places (where you match for error), could you explain this a little, cuz it's not really obvious to me why we want to skip errors

also it would be nice to clean up this unusable variables a bit

Copy link
Contributor Author

@jdonszelmann jdonszelmann Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it's just a good reminder that if we get there, we're allowed to skip it because an error is already emitted.
I tried to separate out the logic of this, but I don't think I prefer it. A method on EiiImplResolution would make sense, but we need the tcx which we don't have in rustc_hir, so the signature would be super awkward, requiring an FnOnce to be passed in or something to get the attributes. I really did consider it but I personally believe this is nicest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yea, the eg is technically never used. I just wanted to make sure for myself I wouldn't accidentally skip EIIs without having a very good reason for it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that makes sense, will continue looking into this pr, not sure how much time it will take, i hopefully might finish it later today (it's a jan 9th and it's a bit late at my place)

i dit a quick glance a first 4-5 commits, looks fine overall

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks kivoo! You're amazing

@Kivooeo
Copy link
Member

Kivooeo commented Jan 8, 2026

r? me

@rustbot
Copy link
Collaborator

rustbot commented Jan 9, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@jdonszelmann
Copy link
Contributor Author

@rustbot review (rebased on the other PR)

@Kivooeo
Copy link
Member

Kivooeo commented Jan 9, 2026

this all looks good to me, i'm in pretty much favour of how the eii is now resolving with this changes

let's wait on what ci thinks on this matter

@jdonszelmann
Copy link
Contributor Author

@bors r=@Kivooeo

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-blocked Status: Blocked on something else such as an RFC or other implementation work. labels Jan 9, 2026
@rust-bors rust-bors bot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 9, 2026
@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 9, 2026

📌 Commit 7791bc2 has been approved by @Kivooeo

It is now in the queue for this repository.

@Kivooeo
Copy link
Member

Kivooeo commented Jan 9, 2026

thanks for working on it :)

rust-bors bot added a commit that referenced this pull request Jan 10, 2026
Rollup of 9 pull requests

Successful merges:

 - #149318 (Implement partial_sort_unstable for slice)
 - #150805 (Fix ICE in inline always warning emission.)
 - #150822 (Fix for ICE: eii: fn / macro rules None in find_attr())
 - #150853 (std: sys: fs: uefi: Implement File::read)
 - #150855 (std: sys: fs: uefi: Implement File::tell)
 - #150881 (Fix std::fs::copy on WASI by setting proper OpenOptions flags)
 - #150891 (Fix a trivial typo in def_id.rs)
 - #150892 (Don't check `[mentions]` paths in submodules from tidy)
 - #150894 (cg_llvm: add a pause to make comment less confusing)

r? @ghost
@rust-bors rust-bors bot merged commit a8028ab into rust-lang:main Jan 10, 2026
11 checks passed
@rustbot rustbot added this to the 1.94.0 milestone Jan 10, 2026
rust-timer added a commit that referenced this pull request Jan 10, 2026
Rollup merge of #150822 - fix-149981, r=@Kivooeo

Fix for ICE: eii: fn / macro rules None in find_attr()

Closes #149981

This used to ICE:
```rust
macro_rules! foo_impl {}
#[eii]
fn foo_impl() {}
```

`#[eii]` generates a macro (called `foo_impl`) and a default impl. So the partial expansion used to roughly look like the following:

```rust
macro_rules! foo_impl {} // actually resolves here

extern "Rust" {
    fn foo_impl();
}

#[eii_extern_target(foo_impl)]
macro foo_impl {
    () => {};
}

const _: () = {
    #[implements_eii(foo_impl)] // assumed to name resolve to the macro v2 above
    fn foo_impl() {}
};
```

Now, shadowing rules for macrov2 and macrov1 are super weird! Take a look at this: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=23f21421921360478b0ec0276711ad36

So instead of resolving to the macrov2, we resolve the macrov1 named the same thing.

A regression test was added to this, and some span_delayed_bugs were added to make sure we catch this in the right places. But that didn't fix the root cause.

To make sure this simply cannot happen again, I made it so that we don't even need to do a name resolution for the default. In other words, the new partial expansion looks more like:

```rust
macro_rules! foo_impl {}

extern "Rust" {
    fn foo_impl(); // resolves to here now!!!
}

#[eii_extern_target(foo_impl)]
macro foo_impl {
    () => {};
}

const _: () = {
    #[implements_eii(known_extern_target=foo_impl)] // still name resolved, but directly to the foreign function.
    fn foo_impl() {}
};
```

The reason this helps is that name resolution for non-macros is much more predictable. It's not possible to have two functions like that with the same name in scope.

We used to key externally implementable items off of the defid of the macro, but now the unique identifier is the foreign function's defid which seems much more sane.

Finally, I lied a tiny bit because the above partial expansion doesn't actually work.
```rust
extern "Rust" {
    fn foo_impl(); // not to here
}

const _: () = {
    #[implements_eii(known_extern_target=foo_impl)] // actually resolves to this function itself
    fn foo_impl() {} // <--- so to here
};
```

So the last few commits change the expansion to actually be this:

```rust
macro_rules! foo_impl {}

extern "Rust" {
    fn foo_impl(); // resolves to here now!!!
}

#[eii_extern_target(foo_impl)]
macro foo_impl {
    () => {};
}

const _: () = {
    mod dflt { // necessary, otherwise `super` doesn't work
        use super::*;
        #[implements_eii(known_extern_target=super::foo_impl)] // now resolves to outside the `dflt` module, so the foreign item.
        fn foo_impl() {}
    }
};
```

I apologize to whoever needs to review this, this is very subtle and I hope this makes it clear enough 😭.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-attributes Area: Attributes (`#[…]`, `#![…]`) A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. 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.

ICE: eii: fn / macro rules None in find_attr()

5 participants