Skip to content

Add #[rustc_edition_redirect] - #160227

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

Add #[rustc_edition_redirect]#160227
Amanieu wants to merge 2 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.

@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
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 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.

4 participants