Skip to content

Add #[rustc_edition_redirect] - #160227

Open
Amanieu wants to merge 4 commits into
rust-lang:mainfrom
Amanieu:edition-redirects
Open

Add #[rustc_edition_redirect]#160227
Amanieu wants to merge 4 commits into
rust-lang:mainfrom
Amanieu:edition-redirects

Conversation

@Amanieu

@Amanieu Amanieu commented Jul 30, 2026

Copy link
Copy Markdown
Member

This implements the compiler portion of the library API evolution project goal by adding the #[rustc_edition_redirect] attribute. This attribute allows an item path in the standard library to be redirected to a different item when used from a crate with an older edition.

This PR only implements the compiler portion and doesn't make any use of this in the standard library. However I do have a POC branch which replaces the edition-specific panic! dispatching with this.

Example

// std
pub struct Old;
#[rustc_edition_redirect(before = "2024", target(Old))]
pub struct Current;

// 2024 edition crate
use std::Current; // resolves to Current

// 2021 edition crate
use std::Current; // resolves to Old

Semantics

  • This attribute is allowed on items which define a name (e.g. const, static, fn, struct, enum, union, mod, type, etc) and on single-item use re-exports.
  • The path in the target field of the attribute is resolved in the local scope of the item it is on. Crate metadata directly encodes a resolved target rather than a path.
  • Multiple attributes are allowed with different before keys. The oldest one that applies is selected.
  • The edition comes from the span of the path used to name the item. This works through macro expansion.
  • Redirects do not affect the current crate, only downstream crates.
  • When active, a redirect effectively has the same behavior as a use re-export. It has the same rules regarding visibility, stability, etc. Notably this means that the target of the redirect should also be marked as stable.
  • Re-exports of items with a #[rustc_edition_redirect] marker within the standard library retain the edition redirect (e.g. re-exports of core from std). Re-exports of such items outside the standard library are resolved using the edition of the re-exporting crate. This also applies to glob re-exports.

Implementation

  1. Attribute parsing produces an EditionRedirect which records the edition and target path.
  2. Edition redirect targets are not resolved during reduced-graph construction. This is instead done during finalization after the import fixed point. This is fine because edition redirects only apply to downstream crates.
  3. Resolved redirects are stored as part of ModChild in crate metadata.
  4. When resolving a Decl outside the standard library, edition_adjusted_decl is used to apply the appropriate edition redirect depending on the span of the identifier being resolved.
  5. When constructing a re-export Decl:
    • In the standard library, edition redirects attached to the original item are preserved.
    • In other crates, edition redirects are resolved at the point of the re-export, and the resulting Decl has no edition redirects.

Open questions

  • This is currently gated under edition_redirect without a tracking issue. Does this need a separate tracking issue?
  • Is the re-export behavior the one we want? Do we ever want to preserve such redirect through re-exports in third-party crate.
  • Redirects currently have the same requirements as re-exports. This means that the target must be at least as public as the marked item. Additionally the target must itself be marked with #[stable]. This can be awkward if we don't want to expose a redirect target in the latest edition, we would have to wrap it in a private module to prevent it from being accessible.
  • This redirect is currently not shown in rustdoc at all. Ideally we would want a note that links to what this resolves to in older editions.

r? petrochenkov

@rustbot

rustbot commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred in compiler/rustc_passes/src/check_attr.rs

cc @jdonszelmann, @JonathanBrouwer

Some changes occurred in compiler/rustc_attr_parsing

cc @jdonszelmann, @JonathanBrouwer

Some changes occurred in compiler/rustc_hir/src/attrs

cc @jdonszelmann, @JonathanBrouwer

@rustbot rustbot added A-attributes Area: Attributes (`#[…]`, `#![…]`) 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 Jul 30, 2026
@Amanieu Amanieu added the A-resolve Area: Name/path resolution done by `rustc_resolve` specifically label Jul 30, 2026

@mejrs mejrs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I can review the attribute portion of this.

  • Multiple attributes are allowed with different before keys. The oldest one that applies is selected.

Given that it's an internal attribute it's not so important, but another option is a range syntax to force unambiguity (error on overlapping ranges):

#[rustc_edition_redirect(during = "..=2018", target(oldest_module))]
#[rustc_edition_redirect(during = "2021..=2024", target(middle_module))]
pub mod redirected_module { }

View changes since this review

Comment on lines +1437 to +1438
if ast::attr::contains_name(&item.attrs, sym::rustc_edition_redirect)
&& let Some(Attribute::Parsed(AttributeKind::RustcEditionRedirect(mut redirects))) =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if ast::attr::contains_name(&item.attrs, sym::rustc_edition_redirect)
&& let Some(Attribute::Parsed(AttributeKind::RustcEditionRedirect(mut redirects))) =
if let Some(Attribute::Parsed(AttributeKind::RustcEditionRedirect(mut redirects))) =

No need for this check, the attribute parser already does it.

Comment on lines +1448 to +1457
if edition_redirects
.last()
.is_some_and(|existing| existing.before == redirect.before)
{
self.r.dcx().span_err(
redirect.span,
format!("multiple edition redirects before edition {}", redirect.before),
);
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This check should be in attribute parsing, not here. CombineParser::finalize_check can be overridden to do this. If you implement AttributeParser you can store state in your attribute parser instead, if that makes things easier.

Comment on lines +417 to +423
let before = match before.as_str().parse::<Edition>() {
Ok(before) => before,
Err(()) => {
cx.dcx().span_err(cx.attr_span, "invalid edition in edition redirect");
return None;
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is the behavior of before = "future"? Should it be supported or be an error? Either way it looks like there are no tests for it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

before = "future" should be supported just like any other edition.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 30, 2026
@mejrs mejrs added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jul 31, 2026
@petrochenkov

Copy link
Copy Markdown
Contributor

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 31, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Jul 31, 2026
@petrochenkov

petrochenkov commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Given that this is a major expansion to name resolution, it's not surprising that I don't like it :)
My goal here will be making it less major while trying to still satisfy the library goals, perhaps at cost of some reduced ergonomics.

Some initial ideas:

  • #[rustc_edition_redirect] is effectively a second reexport system parallel to the regular reexports, it even has to duplicate some reexport checks like "too private to reexport".
    I would prefer the "redirects" to reuse actual reexports, like

    #[rustc_edition_redirect = <edition_info>]
    use RedirectTarget as Name;

    This would be resolved and checked exactly like a regular reexport, except it wouldn't plant Name into the current module to avoid conflicts.
    This will also allow to avoid resolving paths inside built-in attributes, something that was deliberately avoided so far.

  • Second, #[rustc_edition_redirect = ...] attributes on a single item should be able to give a precise list of editions they target, in isolation.
    So we don't need to find the full set of items with redirects and the same name and check how their edition ranges overlap to get the edition list.
    Right now name resolution works the same in the local crate and in other crates, this feature as implemented breaks that invariant, but it would be good to keep it, or produce errors in cases where the resolution would works differently.
    #[rustc_edition_redirect = ...] use RedirectTarget as Name; could actually plant the Name into the current module if the rustc_edition_redirect matches the current local edition.

  • Third, is it possible to get rid of the "some crates propagate redirects, and some do not" feature, and always either propagate or not? Perhaps at cost of some mild inconveniences in the standard library?
    In any case, right now the choice shouldn't be made by looking at whether feature(edition_redirect) is enabled or not, a missing feature is not supposed to change behavior, only to report a non-fatal error and then continue as usual. Another crate-level attribute gated by rustc_attrs would work instead (#[rustc_preserve_edition_redirects] or something).

I'll need to think about this more in the background for some time.

@petrochenkov petrochenkov added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 31, 2026
@rust-bors

rust-bors Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 1b93257 (1b9325785afb0e4413270e6bc497cf74b4bddc70)
Base parent: 922325b (922325bb13bfea5b41454318563f2a65e83c2336)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (1b93257): comparison URL.

Overall result: ❌✅ regressions and improvements - please read:

Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf.

Next, please: If you can, justify the regressions found in this try perf run in writing along with @rustbot label: +perf-regression-triaged. If not, fix the regressions and do another perf run. Neutral or positive results will clear the label automatically.

@bors rollup=never rustc-perf
@rustbot label: -S-waiting-on-perf +perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
0.4% [0.2%, 0.7%] 49
Regressions ❌
(secondary)
0.4% [0.1%, 1.0%] 50
Improvements ✅
(primary)
-0.4% [-0.4%, -0.4%] 3
Improvements ✅
(secondary)
-0.1% [-0.1%, -0.1%] 2
All ❌✅ (primary) 0.4% [-0.4%, 0.7%] 52

Max RSS (memory usage)

Results (primary 0.8%, secondary 1.5%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
0.8% [0.5%, 1.3%] 18
Regressions ❌
(secondary)
2.0% [0.4%, 10.6%] 39
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.1% [-4.5%, -0.6%] 4
All ❌✅ (primary) 0.8% [0.5%, 1.3%] 18

Cycles

Results (primary 0.1%, secondary -0.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
1.0% [0.5%, 2.4%] 5
Regressions ❌
(secondary)
1.7% [0.4%, 6.1%] 23
Improvements ✅
(primary)
-1.4% [-2.3%, -0.7%] 3
Improvements ✅
(secondary)
-4.7% [-9.2%, -2.0%] 9
All ❌✅ (primary) 0.1% [-2.3%, 2.4%] 8

Binary size

Results (primary 0.3%, secondary 0.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
0.3% [0.0%, 0.9%] 64
Regressions ❌
(secondary)
0.1% [0.0%, 0.5%] 30
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.3% [0.0%, 0.9%] 64

Bootstrap: 490.333s -> 491.013s (0.14%)
Artifact size: 392.58 MiB -> 390.64 MiB (-0.50%)

@rustbot rustbot added perf-regression Performance regression. and removed S-waiting-on-perf Status: Waiting on a perf run to be completed. labels Jul 31, 2026
@Amanieu

Amanieu commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

@petrochenkov Can you give more concrete examples of what you are proposing? You seem to be saying that we should just have multiple items with the same name in the same namespace with a different edition filter on them. I think that would be more complex and intrusive than the current solution.

#[rustc_edition_redirect = "2024"]
use RedirectTarget2024 as Name;
#[rustc_edition_redirect = "2021"]
use RedirectTarget2021 as Name;
#[rustc_edition_redirect = "2018"]
use RedirectTarget2018 as Name;

The current system is much simpler: there is only one name per namespace and you can attach any number of edition redirects on that name:

#[rustc_edition_redirect(before = "2024", target(RedirectTarget2024)]
#[rustc_edition_redirect(before = "2021", target(RedirectTarget2021)]
#[rustc_edition_redirect(before = "2018", target(RedirectTarget2018)]
struct Name;

That way all the redirection metadata for one name is available on the single ModChild for that name.

Third, is it possible to get rid of the "some crates propagate redirects, and some do not" feature, and always either propagate or not? Perhaps at cost of some mild inconveniences in the standard library?

In theory yes, we could manually add edition redirect attributes on every re-export of an item that has redirects. But that would just end up being completely equivalent to what the current implementation is doing, while being more error-prone since we may accidentally forget redirects in some places.

  • In any case, right now the choice shouldn't be made by looking at whether feature(edition_redirect) is enabled or not, a missing feature is not supposed to change behavior, only to report a non-fatal error and then continue as usual.

There's quite a few places in the compiler where we change global behavior depending on whether a feature is enabled. For example stage_api forces all public items to have stability attributes. I feel that what I am doing is not too different, and in any case this feature is only intended for use in the standard library and tests.

@rust-bors

This comment has been minimized.

@petrochenkov petrochenkov added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Aug 2, 2026
@Amanieu
Amanieu force-pushed the edition-redirects branch from ba14d93 to 457c443 Compare August 2, 2026 23:42
@rustbot

rustbot commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-attributes Area: Attributes (`#[…]`, `#![…]`) A-resolve Area: Name/path resolution done by `rustc_resolve` specifically perf-regression Performance regression. 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants