From 1f80e5f4b00126dfd8575a93fbdcb39a5113d10f Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 15 Jul 2026 01:03:03 +0100 Subject: [PATCH 1/4] Add #[rustc_edition_redirect] --- compiler/rustc_attr_ir/src/data_structures.rs | 10 + .../rustc_attr_ir/src/encode_cross_crate.rs | 1 + compiler/rustc_attr_ir/src/pretty_printing.rs | 3 +- .../src/attributes/rustc_internal.rs | 28 +- compiler/rustc_attr_parsing/src/context.rs | 1 + .../rustc_attr_parsing/src/diagnostics.rs | 7 + compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_metadata/src/rmeta/decoder.rs | 9 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 6 +- compiler/rustc_metadata/src/rmeta/mod.rs | 7 +- compiler/rustc_middle/src/middle/resolve.rs | 10 + compiler/rustc_passes/src/check_attr.rs | 18 +- compiler/rustc_passes/src/diagnostics.rs | 8 + .../rustc_resolve/src/build_reduced_graph.rs | 91 ++++- .../rustc_resolve/src/diagnostics/impls.rs | 324 +++++++++--------- compiler/rustc_resolve/src/ident.rs | 7 +- compiler/rustc_resolve/src/imports.rs | 286 +++++++++++++--- .../rustc_resolve/src/late/diagnostics.rs | 106 +++--- compiler/rustc_resolve/src/lib.rs | 88 ++++- compiler/rustc_span/src/edition.rs | 2 +- compiler/rustc_span/src/symbol.rs | 3 + tests/ui/README.md | 5 + .../ui/edition-redirect/ambiguity.old.stderr | 23 ++ tests/ui/edition-redirect/ambiguity.rs | 17 + tests/ui/edition-redirect/auxiliary/basic.rs | 109 ++++++ .../edition-redirect/auxiliary/macro-2018.rs | 37 ++ .../edition-redirect/auxiliary/macro-2024.rs | 37 ++ .../auxiliary/macro-source.rs | 96 ++++++ .../auxiliary/reexport-current.rs | 5 + .../auxiliary/reexport-old.rs | 5 + .../auxiliary/reexport-preserving.rs | 7 + .../auxiliary/reexport-source.rs | 35 ++ .../edition-redirect/auxiliary/stability.rs | 20 ++ tests/ui/edition-redirect/basic.rs | 65 ++++ tests/ui/edition-redirect/diagnostic.rs | 51 +++ tests/ui/edition-redirect/diagnostic.stderr | 78 +++++ tests/ui/edition-redirect/feature-gate.rs | 13 + tests/ui/edition-redirect/feature-gate.stderr | 12 + tests/ui/edition-redirect/invalid.rs | 68 ++++ tests/ui/edition-redirect/invalid.stderr | 78 +++++ tests/ui/edition-redirect/macro-glob.rs | 25 ++ tests/ui/edition-redirect/macro-use.rs | 32 ++ tests/ui/edition-redirect/prelude-import.rs | 31 ++ tests/ui/edition-redirect/reexport.rs | 24 ++ .../ui/edition-redirect/stability.old.stderr | 12 + tests/ui/edition-redirect/stability.rs | 17 + 47 files changed, 1636 insertions(+), 284 deletions(-) create mode 100644 tests/ui/edition-redirect/ambiguity.old.stderr create mode 100644 tests/ui/edition-redirect/ambiguity.rs create mode 100644 tests/ui/edition-redirect/auxiliary/basic.rs create mode 100644 tests/ui/edition-redirect/auxiliary/macro-2018.rs create mode 100644 tests/ui/edition-redirect/auxiliary/macro-2024.rs create mode 100644 tests/ui/edition-redirect/auxiliary/macro-source.rs create mode 100644 tests/ui/edition-redirect/auxiliary/reexport-current.rs create mode 100644 tests/ui/edition-redirect/auxiliary/reexport-old.rs create mode 100644 tests/ui/edition-redirect/auxiliary/reexport-preserving.rs create mode 100644 tests/ui/edition-redirect/auxiliary/reexport-source.rs create mode 100644 tests/ui/edition-redirect/auxiliary/stability.rs create mode 100644 tests/ui/edition-redirect/basic.rs create mode 100644 tests/ui/edition-redirect/diagnostic.rs create mode 100644 tests/ui/edition-redirect/diagnostic.stderr create mode 100644 tests/ui/edition-redirect/feature-gate.rs create mode 100644 tests/ui/edition-redirect/feature-gate.stderr create mode 100644 tests/ui/edition-redirect/invalid.rs create mode 100644 tests/ui/edition-redirect/invalid.stderr create mode 100644 tests/ui/edition-redirect/macro-glob.rs create mode 100644 tests/ui/edition-redirect/macro-use.rs create mode 100644 tests/ui/edition-redirect/prelude-import.rs create mode 100644 tests/ui/edition-redirect/reexport.rs create mode 100644 tests/ui/edition-redirect/stability.old.stderr create mode 100644 tests/ui/edition-redirect/stability.rs diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 54d083a094883..77823c9a453bd 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -16,6 +16,7 @@ use rustc_macros::{ Decodable, Decodable_NoContext, Encodable, Encodable_NoContext, PrintAttribute, StableHash, }; use rustc_span::def_id::DefId; +use rustc_span::edition::Edition; use rustc_span::hygiene::Transparency; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_structures::{CollapseMacroDebuginfo, CrateType, Limit, NativeLibKind, SanitizerSet}; @@ -119,6 +120,12 @@ pub enum InstrumentFnAttr { Off, } +#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)] +pub struct EditionRedirect { + pub edition: Edition, + pub span: Span, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Default, PrintAttribute)] #[derive(Encodable, Decodable, StableHash)] pub enum OptimizeAttr { @@ -1267,6 +1274,9 @@ pub enum AttributeKind { /// Represents `#[rustc_dyn_incompatible_trait]`. RustcDynIncompatibleTrait(Span), + /// Represents `#[rustc_edition_redirect = "..."]`. + RustcEditionRedirect(EditionRedirect), + /// Represents `#[rustc_effective_visibility]`. RustcEffectiveVisibility, diff --git a/compiler/rustc_attr_ir/src/encode_cross_crate.rs b/compiler/rustc_attr_ir/src/encode_cross_crate.rs index 270ec0399799e..b2e579ca0d587 100644 --- a/compiler/rustc_attr_ir/src/encode_cross_crate.rs +++ b/compiler/rustc_attr_ir/src/encode_cross_crate.rs @@ -145,6 +145,7 @@ impl AttributeKind { RustcDumpVariancesOfOpaques => No, RustcDumpVtable(..) => No, RustcDynIncompatibleTrait(..) => No, + RustcEditionRedirect(..) => No, RustcEffectiveVisibility => Yes, RustcEiiForeignItem => No, RustcEvaluateWhereClauses => Yes, diff --git a/compiler/rustc_attr_ir/src/pretty_printing.rs b/compiler/rustc_attr_ir/src/pretty_printing.rs index cd8a0c0e1e96f..8afb5aad76419 100644 --- a/compiler/rustc_attr_ir/src/pretty_printing.rs +++ b/compiler/rustc_attr_ir/src/pretty_printing.rs @@ -12,6 +12,7 @@ use rustc_ast::{AttrId, AttrStyle, IntTy, UintTy}; use rustc_ast_pretty::pp::Printer; use rustc_data_structures::fx::FxIndexMap; use rustc_span::def_id::DefId; +use rustc_span::edition::Edition; use rustc_span::hygiene::Transparency; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_structures::{CollapseMacroDebuginfo, CrateType, Limit, NativeLibKind, SanitizerSet}; @@ -190,7 +191,7 @@ macro_rules! print_tup { print_tup!(A B C D E F G H); print_skip!(Span, (), ErrorGuaranteed, AttrId); -print_disp!(u8, u16, u32, u128, usize, bool, NonZero, Limit); +print_disp!(u8, u16, u32, u128, usize, bool, NonZero, Edition, Limit); print_debug!( Symbol, Ident, diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 0df3d9a626bee..565c2c96b9ebc 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -4,12 +4,13 @@ use rustc_ast::{LitIntType, LitKind, MetaItemLit}; use rustc_attr_ir::lang_items::LangItem; use rustc_attr_ir::target::GenericParamKind; use rustc_attr_ir::{ - BorrowckGraphvizFormatKind, CguFields, CguKind, RustcCleanAttribute, RustcCleanQueries, - RustcMirKind, + BorrowckGraphvizFormatKind, CguFields, CguKind, EditionRedirect, RustcCleanAttribute, + RustcCleanQueries, RustcMirKind, }; use rustc_data_structures::fx::FxHashMap; use rustc_feature::AttributeStability; use rustc_span::Symbol; +use rustc_span::edition::Edition; use super::prelude::*; use super::util::parse_single_integer; @@ -342,6 +343,29 @@ impl AttributeParser for RustcCguTestAttributeParser { } } +pub(crate) struct RustcEditionRedirectParser; + +impl SingleAttributeParser for RustcEditionRedirectParser { + const PATH: &[Symbol] = &[sym::rustc_edition_redirect]; + const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Use)]); + const TEMPLATE: AttributeTemplate = template!(NameValueStr: "2024"); + const STABILITY: AttributeStability = unstable!(edition_redirect); + + fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { + let value = cx.expect_name_value(args, cx.attr_span, Some(sym::rustc_edition_redirect))?; + let value = cx.expect_string_literal(value)?; + let edition = match value.as_str().parse::() { + Ok(edition) => edition, + Err(()) => { + cx.emit_err(diagnostics::InvalidEditionRedirect { span: cx.attr_span }); + return None; + } + }; + + Some(AttributeKind::RustcEditionRedirect(EditionRedirect { edition, span: cx.attr_span })) + } +} + pub(crate) struct RustcDeprecatedSafe2024Parser; impl SingleAttributeParser for RustcDeprecatedSafe2024Parser { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index cf99311cc0cfc..497be1c627dae 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -240,6 +240,7 @@ attribute_parsers!( Single, Single, Single, + Single, Single, Single, Single, diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index 663915e39dccd..94052df963b59 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -37,6 +37,13 @@ pub(crate) struct ItemFollowingInnerAttr { pub span: Span, } +#[derive(Diagnostic)] +#[diag("invalid edition in edition redirect")] +pub(crate) struct InvalidEditionRedirect { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("unreachable configuration predicate")] pub(crate) struct UnreachableCfgSelectPredicate { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index f79a8e9ffc79c..20608137160eb 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -247,6 +247,7 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ sym::rustc_eii_foreign_item, sym::rustc_allowed_through_unstable_modules, sym::rustc_deprecated_safe_2024, + sym::rustc_edition_redirect, sym::rustc_pub_transparent, // ========================================================================== diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index ca70389815141..948214aaa48f1 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -235,6 +235,8 @@ declare_features! ( (internal, const_param_ty_unchecked, "1.97.0", None), /// Allows writing custom MIR (internal, custom_mir, "1.65.0", None), + /// Allows defining edition redirects and preserving redirects on re-exports. + (internal, edition_redirect, "CURRENT_RUSTC_VERSION", None), /// Implementation details of externally implementable items (internal, eii_internals, "1.94.0", None), /// Implementation details of field representing types. diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 8a565369d7610..4e4d272c04ffc 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1313,7 +1313,14 @@ impl CrateMetadata { let res = Res::Def(self.def_kind(id), self.local_def_id(id)); let vis = self.get_visibility(tcx, id); - ModChild { ident, res, vis, reexport_chain: Default::default() } + ModChild { + ident, + res, + vis, + reexport_chain: Default::default(), + // Children with redirects are encoded as full `ModChild`s. + edition_redirects: Default::default(), + } } /// Iterates over all named children of the given module, diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 713671c3a5b47..e69355bbe9650 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1737,11 +1737,13 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let module_children = tcx.module_children_local(local_def_id); record_array!(self.tables.module_children_non_reexports[def_id] <- - module_children.iter().filter(|child| child.reexport_chain.is_empty()) + module_children.iter().filter(|child| child.reexport_chain.is_empty() + && child.edition_redirects.is_empty()) .map(|child| child.res.def_id().index)); record_defaulted_array!(self.tables.module_children_reexports[def_id] <- - module_children.iter().filter(|child| !child.reexport_chain.is_empty())); + module_children.iter().filter(|child| !child.reexport_chain.is_empty() + || !child.edition_redirects.is_empty())); let ambig_module_children = tcx .resolutions(()) diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index d16839b910c4b..2d24b0a8ef514 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -402,10 +402,9 @@ define_tables! { explicit_implied_const_bounds: Table, Span)>>, inherent_impls: Table>, opt_rpitit_info: Table>>, - // Reexported names are not associated with individual `DefId`s, - // e.g. a glob import can introduce a lot of names, all with the same `DefId`. - // That's why the encoded list needs to contain `ModChild` structures describing all the names - // individually instead of `DefId`s. + // Names requiring data beyond the item's own `DefId` are encoded as full `ModChild`s. + // This includes reexports, where a glob can introduce many names with the same `DefId`, and + // proper items carrying edition redirects. module_children_reexports: Table>, ambig_module_children: Table>, cross_crate_inlinable: Table, diff --git a/compiler/rustc_middle/src/middle/resolve.rs b/compiler/rustc_middle/src/middle/resolve.rs index 2958048103320..5c1a98f925720 100644 --- a/compiler/rustc_middle/src/middle/resolve.rs +++ b/compiler/rustc_middle/src/middle/resolve.rs @@ -13,6 +13,7 @@ use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalDefIdMap, LocalModId, use rustc_hir::definitions::PerParentDisambiguatorState; use rustc_hir::{MissingLifetimeKind, TraitCandidate}; use rustc_macros::{StableHash, TyDecodable, TyEncodable}; +use rustc_span::edition::Edition; use rustc_span::{ExpnId, Ident, Span, Symbol}; use smallvec::SmallVec; @@ -132,6 +133,13 @@ impl Reexport { } } +/// A different item that a module child resolves to on or before a given edition. +#[derive(Clone, Copy, Debug, TyEncodable, TyDecodable, StableHash)] +pub struct EditionRedirect { + pub edition: Edition, + pub target: Res, +} + /// This structure is supposed to keep enough data to re-create `Decl`s for other crates /// during name resolution. Right now the bindings are not recreated entirely precisely so we may /// need to add more data in the future to correctly support macros 2.0, for example. @@ -149,6 +157,8 @@ pub struct ModChild { /// Reexport chain linking this module child to its original reexported item. /// Empty if the module child is a proper item. pub reexport_chain: SmallVec<[Reexport; 2]>, + /// Edition-dependent alternatives, sorted from the earliest boundary to the latest. + pub edition_redirects: SmallVec<[EditionRedirect; 1]>, } /// Same as `ModChild`, however, it includes ambiguity error. diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index cd7f422fb72f3..204f391344b9e 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -17,8 +17,8 @@ use rustc_feature::BUILTIN_ATTRIBUTE_SET; use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::lang_items::LangItem; use rustc_hir::attrs::{ - AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr, - OptimizeAttr, ReprAttr, + AttributeKind, DocAttribute, DocInline, EditionRedirect, EiiDecl, EiiImpl, EiiImplResolution, + InlineAttr, OptimizeAttr, ReprAttr, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModId; @@ -239,6 +239,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::Linkage(_linkage, span) => { self.check_linkage(*span, hir_id, target, item) } + AttributeKind::RustcEditionRedirect(redirect) => { + self.check_rustc_edition_redirect(item, redirect) + } // All of the following attributes have no specific checks. // tidy-alphabetical-start @@ -467,6 +470,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> { }) } + /// Rejects the use of edition redirect on non-single use statements. + fn check_rustc_edition_redirect(&self, item: Option<&Item<'_>>, redirect: &EditionRedirect) { + let Some(Item { kind: ItemKind::Use(_, use_kind), .. }) = item else { + return; + }; + if matches!(use_kind, hir::UseKind::Single(_)) { + return; + } + self.dcx().emit_err(diagnostics::EditionRedirectNonSingleUse { attr_span: redirect.span }); + } + fn check_rustc_must_implement_one_of( &self, attr_span: Span, diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 5f99c4b133597..2b5e63f02a2f7 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -13,6 +13,14 @@ use rustc_span::{DUMMY_SP, Ident, Span, Symbol}; use crate::check_attr::ProcMacroKind; use crate::lang_items::Duplicate; +#[derive(Diagnostic)] +#[diag("`#[rustc_edition_redirect]` can only be applied to a single import")] +#[help("use a separate, non-braced `use` item")] +pub(crate) struct EditionRedirectNonSingleUse { + #[primary_span] + pub attr_span: Span, +} + #[derive(Diagnostic)] #[diag("`{$no_mangle_attr}` attribute may not be used in combination with `{$export_name_attr}`")] pub(crate) struct MixedExportNameAndNoMangle { diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 88f057c3a6d6d..7fcac95b257fa 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -18,7 +18,7 @@ use rustc_attr_parsing::AttributeParser; use rustc_data_structures::fx::FxIndexMap; use rustc_expand::base::{ResolverExpand, SyntaxExtension, SyntaxExtensionKind}; use rustc_hir::Attribute; -use rustc_hir::attrs::{AttributeKind, MacroUseArgs}; +use rustc_hir::attrs::{AttributeKind, EditionRedirect, MacroUseArgs}; use rustc_hir::def::{self, *}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_index::bit_set::DenseBitSet; @@ -29,6 +29,7 @@ use rustc_middle::{bug, span_bug}; use rustc_span::def_id::{CRATE_MOD_ID, ModId}; use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind}; use rustc_span::{Ident, Span, Symbol, kw, sym}; +use smallvec::SmallVec; use thin_vec::ThinVec; use tracing::debug; @@ -382,13 +383,15 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .unwrap_or_else(|| res.def_id()), ) }; - let ModChild { ident: orig_ident, res, vis, ref reexport_chain } = *child; + let ModChild { ident: orig_ident, res, vis, ref reexport_chain, ref edition_redirects } = + *child; let ident = IdentKey::new(orig_ident); let span = child_span(self, reexport_chain, res); let res = res.expect_non_local(); let expansion = LocalExpnId::ROOT; let ambig = ambig_child.map(|ambig_child| { - let ModChild { ident: _, res, vis, ref reexport_chain } = *ambig_child; + let ModChild { ident: _, res, vis, ref reexport_chain, edition_redirects: _ } = + *ambig_child; let span = child_span(self, reexport_chain, res); let res = res.expect_non_local(); // External ambiguities always report the `AMBIGUOUS_GLOB_IMPORTS` lint at the moment. @@ -398,6 +401,32 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Record primary definitions. let mut define_extern = |ns| { let orig_ident_span = orig_ident.span; + let edition_redirects = if edition_redirects.is_empty() { + // Fast path when there are no edition redirects. + &[] + } else { + let edition_redirects = edition_redirects + .iter() + .map(|redirect| crate::EditionRedirectDecl { + edition: redirect.edition, + // Model this as a one-step reexport under the original + // child's name: the target supplies the resolution, while + // the child supplies its visibility and provenance. + target: self.arenas.alloc_decl(DeclData { + kind: DeclKind::Def(redirect.target.expect_non_local()), + ambiguity: CmCell::new(None), + initial_vis: vis, + ambiguity_vis_max: CmCell::new(None), + ambiguity_vis_min: CmCell::new(None), + span, + expansion, + parent_module: Some(parent.to_module()), + edition_redirects: &[], + }), + }) + .collect::>(); + self.arenas.alloc_edition_redirects(&edition_redirects) + }; let decl = self.arenas.alloc_decl(DeclData { kind: DeclKind::Def(res), ambiguity: CmCell::new(ambig), @@ -407,13 +436,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { span, expansion, parent_module: Some(parent.to_module()), + edition_redirects, }); - let resolution = self.arenas.alloc_name_resolution(NameResolution { - non_glob_decl: Some(decl), - orig_ident_span, - single_imports: Default::default(), - .. - }); + let resolution = + self.arenas.alloc_name_resolution(NameResolution::new(Some(decl), orig_ident_span)); let key = BindingKey::new_disambiguated(ident, ns, || (child_index + 1).try_into().unwrap()); @@ -545,10 +571,12 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { root_span: Span, root_id: NodeId, vis: Visibility, + edition_redirect: Option, ) { let current_module = self.parent_scope.module.expect_local(); let import = self.r.arenas.alloc_import(ImportData { kind, + edition_redirect, parent_scope: self.parent_scope, module_path, imported_module: CmCell::new(None), @@ -568,7 +596,10 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { ImportKind::Single { target, .. } => { // Don't add underscore imports to `single_imports` // because they cannot define any usable names. - if target.name != kw::Underscore { + // + // Same with edition redirects: these redirects are attached to + // an existing name and don't introduce one themselves. + if target.name != kw::Underscore && import.edition_redirect.is_none() { self.r.per_ns_mut(|this, ns| { let key = BindingKey::new(IdentKey::new(target), ns); this.resolution_or_default(current_module.to_module(), key, target.span) @@ -734,13 +765,44 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { def_id: feed.def_id(), }; - self.add_import(module_path, kind, use_tree.span(), item, root_span, item.id, vis); + let edition_redirect = if !nested + && ast::attr::contains_name(&item.attrs, sym::rustc_edition_redirect) + && let Some(Attribute::Parsed(AttributeKind::RustcEditionRedirect(redirect))) = + AttributeParser::parse_limited_sym( + self.r.tcx.sess, + &item.attrs, + &[sym::rustc_edition_redirect], + ) { + Some(redirect) + } else { + None + }; + + self.add_import( + module_path, + kind, + use_tree.span(), + item, + root_span, + item.id, + vis, + edition_redirect, + ); } ast::UseTreeKind::Glob(_) => { if !ast::attr::contains_name(&item.attrs, sym::prelude_import) { let kind = ImportKind::Glob { max_vis: CmCell::new(None), id, def_id: feed.def_id() }; - self.add_import(prefix, kind, use_tree.span(), item, root_span, item.id, vis); + self.add_import( + prefix, + kind, + use_tree.span(), + item, + root_span, + item.id, + vis, + None, + ); } else { // Resolve the prelude import early. let path_res = @@ -1042,6 +1104,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { id: item.id, def_id: local_def_id, }, + edition_redirect: None, root_id: item.id, parent_scope, imported_module: CmCell::new(module), @@ -1174,6 +1237,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { let macro_use_import = |this: &Self, span, warn_private| { this.r.arenas.alloc_import(ImportData { kind: ImportKind::MacroUse { warn_private }, + edition_redirect: None, root_id: item.id, parent_scope: this.parent_scope, imported_module: CmCell::new(Some(ModuleOrUniformRoot::Module(module))), @@ -1193,7 +1257,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { if let Some(span) = import_all { let import = macro_use_import(self, span, false); self.r.potentially_unused_imports.push(import); - module.for_each_child_mut(self, |this, ident, _, ns, binding| { + module.for_each_child_redir_mut(self, span, |this, ident, _, ns, binding| { if ns == MacroNS { let import = if this.r.is_accessible_from(binding.vis(), this.parent_scope.module) { @@ -1352,6 +1416,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { if is_macro_export { let import = self.r.arenas.alloc_import(ImportData { kind: ImportKind::MacroExport, + edition_redirect: None, root_id: item.id, parent_scope: ParentScope { module: self.r.graph_root.to_module(), diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index a005824e5dbfa..9cb3dea7a3c74 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -767,16 +767,21 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { pub(crate) fn add_module_candidates( &self, module: Module<'ra>, + redirect_span: Span, names: &mut Vec, filter_fn: &impl Fn(Res) -> bool, ctxt: Option, ) { - module.for_each_child(self, |_this, ident, orig_ident_span, _ns, binding| { - let res = binding.res(); - if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { - names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); - } - }); + module.for_each_child_redir( + self, + redirect_span, + |_this, ident, orig_ident_span, _ns, binding| { + let res = binding.res(); + if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { + names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); + } + }, + ); } /// Combines an error with provided span and emits it. @@ -1011,6 +1016,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut local_names = vec![]; self.add_module_candidates( parent_scope.module, + name.span, &mut local_names, &|res| matches!(res, Res::Def(_, _)), None, @@ -1534,7 +1540,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } Scope::ModuleNonGlobs(module, _) => { - this.add_module_candidates(module, suggestions, filter_fn, None); + this.add_module_candidates(module, sp, suggestions, filter_fn, None); } Scope::ModuleGlobs(..) => { // Already handled in `ModuleNonGlobs`. @@ -1576,7 +1582,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope::StdLibPrelude => { if let Some(prelude) = this.prelude { let mut tmp_suggestions = Vec::new(); - this.add_module_candidates(prelude, &mut tmp_suggestions, filter_fn, None); + this.add_module_candidates( + prelude, + sp, + &mut tmp_suggestions, + filter_fn, + None, + ); suggestions.extend( tmp_suggestions .into_iter() @@ -1660,174 +1672,180 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } { let in_module_is_extern = !in_module.def_id().is_local(); - in_module.for_each_child(self, |this, ident, orig_ident_span, ns, name_binding| { - // Avoid non-importable candidates. - if name_binding.is_assoc_item() - && !this.features.import_trait_associated_functions() - { - return; - } + in_module.for_each_child_redir( + self, + lookup_ident.span, + |this, ident, orig_ident_span, ns, name_binding| { + // Avoid non-importable candidates. + if name_binding.is_assoc_item() + && !this.features.import_trait_associated_functions() + { + return; + } - if ident.name == kw::Underscore { - return; - } + if ident.name == kw::Underscore { + return; + } - let child_accessible = - accessible && this.is_accessible_from(name_binding.vis(), parent_scope.module); + let child_accessible = accessible + && this.is_accessible_from(name_binding.vis(), parent_scope.module); - // do not venture inside inaccessible items of other crates - if in_module_is_extern && !child_accessible { - return; - } + // do not venture inside inaccessible items of other crates + if in_module_is_extern && !child_accessible { + return; + } - let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); + let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); - // There is an assumption elsewhere that paths of variants are in the enum's - // declaration and not imported. With this assumption, the variant component is - // chopped and the rest of the path is assumed to be the enum's own path. For - // errors where a variant is used as the type instead of the enum, this causes - // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. - if via_import && name_binding.is_possibly_imported_variant() { - return; - } + // There is an assumption elsewhere that paths of variants are in the enum's + // declaration and not imported. With this assumption, the variant component is + // chopped and the rest of the path is assumed to be the enum's own path. For + // errors where a variant is used as the type instead of the enum, this causes + // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. + if via_import && name_binding.is_possibly_imported_variant() { + return; + } - // #90113: Do not count an inaccessible reexported item as a candidate. - if let DeclKind::Import { source_decl, .. } = name_binding.kind - && this.is_accessible_from(source_decl.vis(), parent_scope.module) - && !this.is_accessible_from(name_binding.vis(), parent_scope.module) - { - return; - } + // #90113: Do not count an inaccessible reexported item as a candidate. + if let DeclKind::Import { source_decl, .. } = name_binding.kind + && this.is_accessible_from(source_decl.vis(), parent_scope.module) + && !this.is_accessible_from(name_binding.vis(), parent_scope.module) + { + return; + } - let res = name_binding.res(); - let did = match res { - Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), - _ => res.opt_def_id(), - }; - let child_doc_visible = doc_visible - && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); - - // collect results based on the filter function - // avoid suggesting anything from the same module in which we are resolving - // avoid suggesting anything with a hygienic name - if ident.name == lookup_ident.name - && ns == namespace - && in_module != parent_scope.module - && ident.ctxt.is_root() - && filter_fn(res) - { - // create the path - let mut segms = if lookup_ident.span.at_least_rust_2018() { - // crate-local absolute paths start with `crate::` in edition 2018 - // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) - crate_path.clone() - } else { - ThinVec::new() + let res = name_binding.res(); + let did = match res { + Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), + _ => res.opt_def_id(), }; - segms.append(&mut path_segments.clone()); + let child_doc_visible = doc_visible + && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); + + // collect results based on the filter function + // avoid suggesting anything from the same module in which we are resolving + // avoid suggesting anything with a hygienic name + if ident.name == lookup_ident.name + && ns == namespace + && in_module != parent_scope.module + && ident.ctxt.is_root() + && filter_fn(res) + { + // create the path + let mut segms = if lookup_ident.span.at_least_rust_2018() { + // crate-local absolute paths start with `crate::` in edition 2018 + // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) + crate_path.clone() + } else { + ThinVec::new() + }; + segms.append(&mut path_segments.clone()); - segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let path = Path { span: name_binding.span, segments: segms }; + segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let path = Path { span: name_binding.span, segments: segms }; - if child_accessible + if child_accessible // Remove invisible match if exists && let Some(idx) = candidates .iter() .position(|v: &ImportSuggestion| v.did == did && !v.accessible) - { - candidates.remove(idx); - } + { + candidates.remove(idx); + } - let is_stable = if is_stable - && let Some(did) = did - && this.is_stable(did, path.span) - { - true - } else { - false - }; + let is_stable = if is_stable + && let Some(did) = did + && this.is_stable(did, path.span) + { + true + } else { + false + }; - // Rreplace unstable suggestions if we meet a new stable one, - // and do nothing if any other situation. For example, if we - // meet `std::ops::Range` after `std::range::legacy::Range`, - // we will remove the latter and then insert the former. - if is_stable - && let Some(idx) = candidates - .iter() - .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) - { - candidates.remove(idx); - } + // Rreplace unstable suggestions if we meet a new stable one, + // and do nothing if any other situation. For example, if we + // meet `std::ops::Range` after `std::range::legacy::Range`, + // we will remove the latter and then insert the former. + if is_stable + && let Some(idx) = candidates + .iter() + .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) + { + candidates.remove(idx); + } - if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { - // See if we're recommending TryFrom, TryInto, or FromIterator and add - // a note about editions - let note = if let Some(did) = did { - let requires_note = !did.is_local() - && find_attr!( - this.tcx, - did, - RustcDiagnosticItem( - sym::TryInto | sym::TryFrom | sym::FromIterator + if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { + // See if we're recommending TryFrom, TryInto, or FromIterator and add + // a note about editions + let note = if let Some(did) = did { + let requires_note = !did.is_local() + && find_attr!( + this.tcx, + did, + RustcDiagnosticItem( + sym::TryInto | sym::TryFrom | sym::FromIterator + ) + ); + requires_note.then(|| { + format!( + "'{}' is included in the prelude starting in Edition 2021", + path_names_to_string(&path) ) - ); - requires_note.then(|| { - format!( - "'{}' is included in the prelude starting in Edition 2021", - path_names_to_string(&path) - ) - }) - } else { - None - }; + }) + } else { + None + }; - candidates.push(ImportSuggestion { - did, - descr: res.descr(), - path, - accessible: child_accessible, - doc_visible: child_doc_visible, - note, - via_import, - is_stable, - }); + candidates.push(ImportSuggestion { + did, + descr: res.descr(), + path, + accessible: child_accessible, + doc_visible: child_doc_visible, + note, + via_import, + is_stable, + }); + } } - } - // collect submodules to explore - if let Some(def_id) = name_binding.res().module_like_def_id() { - // form the path - let mut path_segments = path_segments.clone(); - path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - - let alias_import = if let DeclKind::Import { import, .. } = name_binding.kind - && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind - && import.parent_scope.expansion == parent_scope.expansion - { - true - } else { - false - }; + // collect submodules to explore + if let Some(def_id) = name_binding.res().module_like_def_id() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments + .push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + + let alias_import = if let DeclKind::Import { import, .. } = + name_binding.kind + && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind + && import.parent_scope.expansion == parent_scope.expansion + { + true + } else { + false + }; - let is_extern_crate_that_also_appears_in_prelude = - name_binding.is_extern_crate() && lookup_ident.span.at_least_rust_2018(); - - if !is_extern_crate_that_also_appears_in_prelude || alias_import { - // add the module to the lookup - if seen_modules.insert(def_id) { - if via_import { &mut worklist_via_import } else { &mut worklist }.push( - ( - this.expect_module(def_id), - path_segments, - child_accessible, - child_doc_visible, - is_stable && this.is_stable(def_id, name_binding.span), - ), - ); + let is_extern_crate_that_also_appears_in_prelude = name_binding + .is_extern_crate() + && lookup_ident.span.at_least_rust_2018(); + + if !is_extern_crate_that_also_appears_in_prelude || alias_import { + // add the module to the lookup + if seen_modules.insert(def_id) { + if via_import { &mut worklist_via_import } else { &mut worklist } + .push(( + this.expect_module(def_id), + path_segments, + child_accessible, + child_doc_visible, + is_stable && this.is_stable(def_id, name_binding.span), + )); + } } } - } - }) + }, + ); } candidates @@ -3542,7 +3560,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } let binding_key = BindingKey::new(IdentKey::new(ident), MacroNS); - let binding = self.resolution(crate_module, binding_key)?.best_decl()?; + let binding = self.resolution(crate_module, binding_key)?.best_decl_redir(ident.span)?; let Res::Def(DefKind::Macro(kinds), _) = binding.res() else { return None; }; diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index c1b3c6cd5eeba..afa2de12bd9b1 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1113,7 +1113,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let resolution = &*self.resolution(module.to_module(), key).ok_or(ControlFlow::Continue(Determined))?; - let binding = resolution.non_glob_decl.filter(|b| Some(*b) != ignore_decl); + let binding = + resolution.non_glob_decl_redir(orig_ident_span).filter(|b| Some(*b) != ignore_decl); if let Some(finalize) = finalize { return self.get_mut().finalize_module_binding( @@ -1153,7 +1154,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let resolution = self.resolution(module.to_module(), key); let binding = - resolution.as_ref().and_then(|r| r.non_glob_decl).filter(|b| Some(*b) != ignore_decl); + resolution.as_ref().and_then(|r| r.non_glob_decl()).filter(|b| Some(*b) != ignore_decl); if let Some(finalize) = finalize { // finalize implies that the module is fully expanded @@ -1398,7 +1399,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { parent_scope: &ParentScope<'ra>, ) -> bool { for single_import in &resolution.single_imports { - if let Some(decl) = resolution.non_glob_decl + if let Some(decl) = resolution.non_glob_decl() && let DeclKind::Import { import, .. } = decl.kind && import == *single_import { diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 1cda9b9139028..28a14ebc12dde 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -4,10 +4,11 @@ use std::cmp::Ordering; use std::mem; use rustc_ast::NodeId; -use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_errors::{Applicability, BufferedEarlyLint, Diagnostic}; use rustc_expand::base::SyntaxExtensionKind; +use rustc_hir::attrs::EditionRedirect; use rustc_hir::def::{self, DefKind}; use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap}; use rustc_lint_defs::LintId; @@ -15,13 +16,16 @@ use rustc_lint_defs::builtin::{ AMBIGUOUS_GLOB_REEXPORTS, EXPORTED_PRIVATE_DEPENDENCIES, HIDDEN_GLOB_REEXPORTS, PUB_USE_OF_PRIVATE_EXTERN_CRATE, REDUNDANT_IMPORTS, UNUSED_IMPORTS, }; -use rustc_middle::middle::resolve::{AmbigModChild, ModChild, PartialRes, Reexport}; +use rustc_middle::middle::resolve::{ + AmbigModChild, EditionRedirect as MetadataEditionRedirect, ModChild, PartialRes, Reexport, +}; use rustc_middle::span_bug; use rustc_middle::ty::Visibility; use rustc_session::diagnostics::feature_err; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::hygiene::LocalExpnId; use rustc_span::{Ident, Span, Symbol, kw, sym}; +use smallvec::SmallVec; use tracing::debug; use crate::Namespace::{self, *}; @@ -35,9 +39,9 @@ use crate::diagnostics::{ use crate::ref_mut::{CmCell, CmRefCell}; use crate::{ AmbiguityError, BindingKey, Decl, DeclData, DeclKind, Determinacy, Finalize, IdentKey, - ImportSuggestion, ImportSummary, LocalModule, ModuleOrUniformRoot, ParentScope, PathResult, - PerNS, Res, ResolutionError, Resolver, ScopeSet, Segment, Used, module_to_string, - names_to_string, + ImportSuggestion, ImportSummary, LocalEditionRedirect, LocalModule, ModuleOrUniformRoot, + ParentScope, PathResult, PerNS, Res, ResolutionError, Resolver, ScopeSet, Segment, Used, + module_to_string, names_to_string, }; /// A potential import declaration in the process of being planted into a module. @@ -209,6 +213,10 @@ pub(crate) struct ImportData<'ra> { /// /// This is `None` if the feature flag for `diagnostic::on_unknown` is disabled. pub on_unknown_attr: Option, + + /// If present, this import supplies one edition-specific alternative for its target name. + /// It is resolved and checked like an ordinary import, but is not visible in the current crate. + pub edition_redirect: Option, } /// `Interned` is used because values of this type have "identity" and compare as unequal even if @@ -266,28 +274,64 @@ impl<'ra> ImportData<'ra> { } } -/// Records information about the resolution of a name in a namespace of a module. -#[derive(Debug)] -pub(crate) struct NameResolution<'ra> { - /// Single imports that may define the name in the namespace. - /// Imports are arena-allocated, so it's ok to use pointers as keys. - pub single_imports: FxIndexSet>, - /// The non-glob declaration for this name, if it is known to exist. - pub non_glob_decl: Option> = None, - /// The glob declaration for this name, if it is known to exist. - pub glob_decl: Option> = None, - pub orig_ident_span: Span, +mod name_resolution { + use super::*; + + /// Records information about the resolution of a name in a namespace of a module. + #[derive(Debug)] + pub(crate) struct NameResolution<'ra> { + /// Single imports that may define the name in the namespace. + /// Imports are arena-allocated, so it's ok to use pointers as keys. + pub single_imports: FxIndexSet>, + /// The non-glob declaration for this name, if it is known to exist. + non_glob_decl: Option>, + /// The glob declaration for this name, if it is known to exist. + pub glob_decl: Option> = None, + pub orig_ident_span: Span, + } + + impl<'ra> NameResolution<'ra> { + pub(crate) fn new(non_glob_decl: Option>, orig_ident_span: Span) -> Self { + NameResolution { + single_imports: FxIndexSet::default(), + non_glob_decl, + orig_ident_span, + .. + } + } + + pub(crate) fn non_glob_decl(&self) -> Option> { + self.non_glob_decl.map(|decl| { + assert!(decl.edition_redirects.is_empty()); + decl + }) + } + + pub(crate) fn non_glob_decl_redir(&self, span: Span) -> Option> { + self.non_glob_decl.map(|decl| { + if decl.edition_redirects.is_empty() { + return decl; + } + let edition = span.edition(); + match decl.edition_redirects.iter().find(|redirect| edition <= redirect.edition) { + Some(redirect) => redirect.target, + None => decl, + } + }) + } + + pub(super) fn set_non_glob_decl(&mut self, decl: Decl<'ra>) { + self.non_glob_decl = Some(decl); + } + } } +pub(crate) use name_resolution::NameResolution; /// `Interned` is used because values of this type have "identity" and compare as unequal even if /// they have the same contents. pub(crate) type NameResolutionRef<'ra> = Interned<'ra, CmRefCell>>; impl<'ra> NameResolution<'ra> { - pub(crate) fn new(orig_ident_span: Span) -> Self { - NameResolution { single_imports: FxIndexSet::default(), orig_ident_span, .. } - } - /// Returns the best declaration if it is not going to change, and `None` if the best /// declaration may still change to something else. /// FIXME: this function considers `single_imports`, but not `unexpanded_invocations`, so @@ -297,8 +341,19 @@ impl<'ra> NameResolution<'ra> { /// code breakage in practice. /// FIXME: relationship between this function and similar `DeclData::determined` is unclear. pub(crate) fn determined_decl(&self) -> Option> { - if self.non_glob_decl.is_some() { - self.non_glob_decl + if self.non_glob_decl().is_some() { + self.non_glob_decl() + } else if self.glob_decl.is_some() && self.single_imports.is_empty() { + self.glob_decl + } else { + None + } + } + + pub(crate) fn determined_decl_redir(&self, span: Span) -> Option> { + let non_glob_decl = self.non_glob_decl_redir(span); + if non_glob_decl.is_some() { + non_glob_decl } else if self.glob_decl.is_some() && self.single_imports.is_empty() { self.glob_decl } else { @@ -307,7 +362,11 @@ impl<'ra> NameResolution<'ra> { } pub(crate) fn best_decl(&self) -> Option> { - self.non_glob_decl.or(self.glob_decl) + self.non_glob_decl().or(self.glob_decl) + } + + pub(crate) fn best_decl_redir(&self, span: Span) -> Option> { + self.non_glob_decl_redir(span).or(self.glob_decl) } } @@ -481,6 +540,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ambiguity_vis_min: CmCell::new(None), expansion: import.parent_scope.expansion, parent_module: Some(import.parent_scope.module), + edition_redirects: &[], }) } @@ -659,10 +719,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None => decl, }); } else { - resolution.non_glob_decl = Some(match resolution.non_glob_decl { + match resolution.non_glob_decl() { Some(old_decl) => return Err(old_decl), - None => decl, - }) + None => resolution.set_non_glob_decl(decl), + } } Ok(()) @@ -734,6 +794,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if !(is_indeterminate || decls.iter().all(|d| d.get().decl().is_none())) { return; // Has resolution, do not create the dummy binding } + if import.edition_redirect.is_some() { + let dummy_decl = self.new_import_decl(self.dummy_decl, import); + self.record_use(target, dummy_decl, Used::Other); + return; + } let dummy_decl = self.dummy_decl; let dummy_decl = self.new_import_decl(dummy_decl, import); self.per_ns_mut(|this, ns| { @@ -838,7 +903,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { match (&import.kind, resolution_kind) { ( - ImportKind::Single { target, decls, .. }, + ImportKind::Single { source, target, decls, .. }, ImportResolutionKind::Single(import_decls), ) => { self.per_ns_mut(|this, ns| { @@ -857,17 +922,37 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) .emit(); } - this.plant_decl_into_local_module( - IdentKey::new(*target), - target.span, - ns, - import_decl, - ); + let ident = IdentKey::new(*target); + if let Some(redirect) = import.edition_redirect { + // Redirect imports are checked like ordinary imports, but their + // aliases are not visible while compiling this crate. They are + // combined with the ordinary binding when metadata is produced. + this.local_edition_redirects.push(LocalEditionRedirect { + module: import.parent_scope.module.expect_local(), + key: BindingKey::new(ident, ns), + edition: redirect.edition, + import_decl, + default_decl: None, + span: redirect.span, + }); + this.record_use(*source, import_decl, Used::Other); + } else { + this.plant_decl_into_local_module( + ident, + target.span, + ns, + import_decl, + ); + } decls[ns].set(PendingDecl::Ready(Some(import_decl)), this); } PendingDecl::Ready(None) => { - // Don't remove underscores from `single_imports`, they were never added. - if target.name != kw::Underscore { + // Don't remove underscores and edition + // redirects from `single_imports`, they were + // never added. + if target.name != kw::Underscore + && import.edition_redirect.is_none() + { let key = BindingKey::new(IdentKey::new(*target), ns); this.update_local_resolution( import.parent_scope.module.expect_local(), @@ -922,10 +1007,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } pub(crate) fn finalize_imports(&mut self) { + self.finalize_local_edition_redirects(); + let mut module_children = Default::default(); let mut ambig_module_children = Default::default(); - for module in &self.local_modules { - self.finalize_resolutions_in(*module, &mut module_children, &mut ambig_module_children); + for index in 0..self.local_modules.len() { + let module = self.local_modules[index]; + self.finalize_resolutions_in(module, &mut module_children, &mut ambig_module_children); } self.module_children = module_children; self.ambig_module_children = ambig_module_children; @@ -1018,7 +1106,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Report "cannot reexport" errors for exotic cases involving macros 2.0 // privacy bending or invariant-breaking code under deprecation lints. - for decl in [resolution.non_glob_decl, resolution.glob_decl] { + for decl in [resolution.non_glob_decl(), resolution.glob_decl] { if let Some(decl) = decl && let DeclKind::Import { source_decl, import } = decl.kind // FIXME: Do not check visibility-ambiguous imports for now. To check them @@ -1062,7 +1150,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } if let Some(glob_decl) = resolution.glob_decl - && resolution.non_glob_decl.is_some() + && resolution.non_glob_decl().is_some() { if binding.res() != Res::Err && glob_decl.res() != Res::Err @@ -1507,7 +1595,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } // `use _` is never valid let resolution = resolution.borrow(self); - if let Some(name_binding) = resolution.best_decl() { + if let Some(name_binding) = resolution.best_decl_redir(ident.span) { match name_binding.kind { DeclKind::Import { source_decl, .. } => { match source_decl.kind { @@ -1818,7 +1906,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .filter_map(|(key, resolution)| { let res = resolution.borrow_checked(self); - let decl = res.determined_decl()?; + let decl = res.determined_decl_redir(import.span)?; let mut key = *key; let scope = match key.ident.ctxt.update_unchecked(|ctxt| { ctxt.reverse_glob_adjust(module.expansion, import.span) @@ -1874,6 +1962,104 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { false } + /// Connects each resolved redirect import to the ordinary binding used if no redirect applies. + /// + /// This also validates properties that concern the redirect group as a whole rather than one + /// import in isolation. + fn finalize_local_edition_redirects(&mut self) { + // A BindingKey includes a namespace, so redirects may be duplicated in + // multiple namespaces. We only want to emit diagnostics once in these + // cases. + let mut diagnosed_missing_default = FxHashSet::default(); + let mut diagnosed_visibility = FxHashSet::default(); + let mut diagnosed_duplicate = FxHashSet::default(); + + // Group redirects by the base decl that they are attached to. + let mut groups = FxIndexMap::<_, SmallVec<[usize; 2]>>::default(); + for (index, redirect) in self.local_edition_redirects.iter().enumerate() { + groups.entry((redirect.module, redirect.key)).or_default().push(index); + } + + for ((module, key), mut indices) in groups { + // Resolve the default decl that redirects are attached to. + let Some(default_decl) = self + .resolution(module.to_module(), key) + .and_then(|resolution| resolution.best_decl()) + else { + let redirect = &self.local_edition_redirects[indices[0]]; + if diagnosed_missing_default.insert(redirect.span) { + self.dcx().span_err( + redirect.span, + format!( + "edition redirect for `{}` has no default item", + redirect.key.ident.name + ), + ); + } + continue; + }; + + // Point each redirect to the default decl for later passes. + for &index in &indices { + self.local_edition_redirects[index].default_decl = Some(default_decl); + } + + // Check that there are no duplicate editions in the group. + indices.sort_by_key(|&index| self.local_edition_redirects[index].edition); + for &[previous, redirect] in indices.array_windows() { + let previous = &self.local_edition_redirects[previous]; + let redirect = &self.local_edition_redirects[redirect]; + if previous.edition == redirect.edition && diagnosed_duplicate.insert(redirect.span) + { + self.dcx().span_err( + redirect.span, + format!( + "multiple edition redirects with edition {} for `{}`", + redirect.edition, redirect.key.ident.name + ), + ); + } + } + + // Check that redirects have the same visibility as the default + // item. + for index in indices { + let redirect = &self.local_edition_redirects[index]; + if redirect.import_decl.vis() != default_decl.vis() + && diagnosed_visibility.insert(redirect.span) + { + self.dcx().span_err( + redirect.span, + format!( + "edition redirect for `{}` must have the same visibility as its default item", + redirect.key.ident.name + ), + ); + } + } + } + } + + /// Returns the redirects to encode for `decl`. + fn edition_redirects_for_decl( + &self, + decl: Decl<'ra>, + ) -> SmallVec<[MetadataEditionRedirect; 1]> { + let mut redirects = self + .local_edition_redirects + .iter() + .filter(|redirect| redirect.default_decl == Some(decl)) + .collect::>(); + redirects.sort_by_key(|redirect| redirect.edition); + redirects + .into_iter() + .map(|redirect| MetadataEditionRedirect { + edition: redirect.edition, + target: redirect.import_decl.res().expect_non_local(), + }) + .collect() + } + // Miscellaneous post-processing, including recording re-exports, // reporting conflicts, and reporting unresolved imports. fn finalize_resolutions_in( @@ -1899,18 +2085,30 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { decl.vis() }; let ident = ident.orig(orig_ident_span); - let child = |reexport_chain| ModChild { ident, res, vis, reexport_chain }; if let Some((ambig_binding1, ambig_binding2)) = decl.descent_to_ambiguity() { - let main = child(ambig_binding1.reexport_chain()); + let main = ModChild { + ident, + res, + vis, + reexport_chain: ambig_binding1.reexport_chain(), + edition_redirects: Default::default(), + }; let second = ModChild { ident, res: ambig_binding2.res().expect_non_local(), vis: ambig_binding2.vis(), reexport_chain: ambig_binding2.reexport_chain(), + edition_redirects: Default::default(), }; ambig_children.push(AmbigModChild { main, second }) } else { - children.push(child(decl.reexport_chain())); + children.push(ModChild { + ident, + res, + vis, + reexport_chain: decl.reexport_chain(), + edition_redirects: this.edition_redirects_for_decl(decl), + }); } } }); diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index f5f40a641b66e..125db53d90760 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1163,10 +1163,12 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } fn lookup_doc_alias_name(&mut self, path: &[Segment], ns: Namespace) -> Option<(DefId, Ident)> { - let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item_name: Symbol| { + let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item: Ident| { for resolution in r.resolutions(m).values() { - let Some(did) = - resolution.borrow(r).best_decl().and_then(|binding| binding.res().opt_def_id()) + let Some(did) = resolution + .borrow(r) + .best_decl_redir(item.span) + .and_then(|binding| binding.res().opt_def_id()) else { continue; }; @@ -1177,7 +1179,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { continue; } if let Some(d) = hir::find_attr!(r.tcx, did, Doc(d) => d) - && d.aliases.contains_key(&item_name) + && d.aliases.contains_key(&item.name) { return Some(did); } @@ -1189,7 +1191,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { for rib in self.ribs[ns].iter().rev() { let item = path[0].ident; if let RibKind::Module(module) | RibKind::Block(Some(module)) = rib.kind - && let Some(did) = find_doc_alias_name(self.r, module.to_module(), item.name) + && let Some(did) = find_doc_alias_name(self.r, module.to_module(), item) { return Some((did, item)); } @@ -1213,7 +1215,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let Res::Def(DefKind::Mod, module) = res.expect_full_res() && let module = self.r.expect_module(module) && let item = path[idx + 1].ident - && let Some(did) = find_doc_alias_name(self.r, module, item.name) + && let Some(did) = find_doc_alias_name(self.r, module, item) { return Some((did, item)); } @@ -2930,6 +2932,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let RibKind::Block(Some(module)) = rib.kind { self.r.add_module_candidates( module.to_module(), + segment.ident.span.with_ctxt(ctxt), &mut names, &filter_fn, Some(ctxt), @@ -2962,7 +2965,13 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = self.resolve_path(mod_path, Some(TypeNS), None, PathSource::Type) { - self.r.add_module_candidates(module, &mut names, &filter_fn, None); + self.r.add_module_candidates( + module, + path[path.len() - 1].ident.span, + &mut names, + &filter_fn, + None, + ); } } @@ -3074,7 +3083,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { false } - fn find_module(&self, def_id: DefId) -> Option<(Module<'ra>, ImportSuggestion)> { + fn find_module(&self, def_id: DefId, span: Span) -> Option<(Module<'ra>, ImportSuggestion)> { let mut result = None; let mut seen_modules = FxHashSet::default(); let mut worklist = vec![(self.r.graph_root.to_module(), ThinVec::new(), true)]; @@ -3085,48 +3094,57 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { break; } - in_module.for_each_child(self.r, |r, ident, orig_ident_span, _, name_binding| { - // abort if the module is already found or if name_binding is private external - if result.is_some() || !name_binding.vis().is_visible_locally() { - return; - } - if let Some(module_def_id) = name_binding.res().module_like_def_id() { - // form the path - let mut path_segments = path_segments.clone(); - path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let doc_visible = doc_visible - && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); - if module_def_id == def_id { - let path = Path { span: name_binding.span, segments: path_segments }; - result = Some(( - r.expect_module(module_def_id), - ImportSuggestion { - did: Some(def_id), - descr: "module", - path, - accessible: true, - doc_visible, - note: None, - via_import: false, - is_stable: true, - }, - )); - } else { - // add the module to the lookup - if seen_modules.insert(module_def_id) { - let module = r.expect_module(module_def_id); - worklist.push((module, path_segments, doc_visible)); + in_module.for_each_child_redir( + self.r, + span, + |r, ident, orig_ident_span, _, name_binding| { + // abort if the module is already found or if name_binding is private external + if result.is_some() || !name_binding.vis().is_visible_locally() { + return; + } + if let Some(module_def_id) = name_binding.res().module_like_def_id() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments + .push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let doc_visible = doc_visible + && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); + if module_def_id == def_id { + let path = Path { span: name_binding.span, segments: path_segments }; + result = Some(( + r.expect_module(module_def_id), + ImportSuggestion { + did: Some(def_id), + descr: "module", + path, + accessible: true, + doc_visible, + note: None, + via_import: false, + is_stable: true, + }, + )); + } else { + // add the module to the lookup + if seen_modules.insert(module_def_id) { + let module = r.expect_module(module_def_id); + worklist.push((module, path_segments, doc_visible)); + } } } - } - }); + }, + ); } result } - fn collect_enum_ctors(&self, def_id: DefId) -> Option> { - self.find_module(def_id).map(|(enum_module, enum_import_suggestion)| { + fn collect_enum_ctors( + &self, + def_id: DefId, + span: Span, + ) -> Option> { + self.find_module(def_id, span).map(|(enum_module, enum_import_suggestion)| { let mut variants = Vec::new(); enum_module.for_each_child(self.r, |_, ident, orig_ident_span, _, name_binding| { if let Res::Def(DefKind::Ctor(CtorOf::Variant, kind), def_id) = name_binding.res() { @@ -3148,7 +3166,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { def_id: DefId, span: Span, ) { - let Some(variant_ctors) = self.collect_enum_ctors(def_id) else { + let Some(variant_ctors) = self.collect_enum_ctors(def_id, span) else { err.note("you might have meant to use one of the enum's variants"); return; }; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index d5b1457865891..ab6e6cd2cc741 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -69,6 +69,7 @@ use rustc_middle::query::Providers; use rustc_middle::ty::{self, RegisteredTools, TyCtxt, TyCtxtFeed, Visibility}; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::{LocalModId, ModId}; +use rustc_span::edition::Edition; use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind, SyntaxContext, Transparency}; use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; use rustc_structures::CrateType; @@ -696,8 +697,14 @@ struct ModuleData<'ra> { globs: CmRefCell>>, /// Used to memoize the traits in this module for faster searches through all traits in scope. + /// + /// The cache is tagged with the edition it was generated with, since redirected trait + /// declarations can select different traits in each edition. traits: CmRefCell< - Option, Option>, bool /* lint ambiguous */)]>>, + Option<( + Edition, + Box<[(Symbol, Decl<'ra>, Option>, bool /* lint ambiguous */)]>, + )>, >, /// Span of the module itself. Used for error reporting. @@ -800,6 +807,7 @@ impl<'ra> ModuleData<'ra> { } impl<'ra> Module<'ra> { + /// Visits children without applying edition redirects. fn for_each_child<'tcx, R: AsRef>>( self, resolver: &R, @@ -813,25 +821,49 @@ impl<'ra> Module<'ra> { } } - fn for_each_child_mut<'tcx, R: AsMut>>( + /// Visits children after applying edition redirects for `redirect_span`. + fn for_each_child_redir<'tcx, R: AsRef>>( + self, + resolver: &R, + redirect_span: Span, + mut f: impl FnMut(&R, IdentKey, Span, Namespace, Decl<'ra>), + ) { + for (key, name_resolution) in resolver.as_ref().resolutions(self).iter() { + let name_resolution = name_resolution.borrow_checked(resolver.as_ref()); + if let Some(decl) = name_resolution.best_decl_redir(redirect_span) { + f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); + } + } + } + + /// Mutable variant of `for_each_child_redir`. + fn for_each_child_redir_mut<'tcx, R: AsMut>>( self, resolver: &mut R, + redirect_span: Span, mut f: impl FnMut(&mut R, IdentKey, Span, Namespace, Decl<'ra>), ) { for (key, name_resolution) in resolver.as_mut().resolutions(self).iter() { let name_resolution = name_resolution.borrow(resolver.as_mut()); - if let Some(decl) = name_resolution.best_decl() { + if let Some(decl) = name_resolution.best_decl_redir(redirect_span) { f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); } } } /// This modifies `self` in place. The traits will be stored in `self.traits`. - fn ensure_traits<'tcx>(self, resolver: &Resolver<'ra, 'tcx>) { + fn ensure_traits<'tcx>(self, resolver: &Resolver<'ra, 'tcx>, redirect_span: Span) { + let edition = redirect_span.edition(); let mut traits = self.traits.borrow_mut_checked(resolver); - if traits.is_none() { + // Macro expansion can cause the same module to be queried with spans from different + // editions. Cache the most recently requested edition and recompute when it changes. + let needs_update = match traits.as_ref() { + Some((cached_edition, _)) => *cached_edition != edition, + None => true, + }; + if needs_update { let mut collected_traits = Vec::new(); - self.for_each_child(resolver, |r, ident, _, ns, mut decl| { + self.for_each_child_redir(resolver, redirect_span, |r, ident, _, ns, mut decl| { if ns != TypeNS { return; } @@ -859,7 +891,7 @@ impl<'ra> Module<'ra> { decl = ambig_decl; } }); - *traits = Some(collected_traits.into_boxed_slice()); + *traits = Some((edition, collected_traits.into_boxed_slice())); } } @@ -1029,6 +1061,25 @@ struct DeclData<'ra> { /// declaration from the set, if its visibility is different from `initial_vis`. ambiguity_vis_min: CmCell>>, parent_module: Option>, + /// Fully resolved cross-crate redirects attached to this declaration. + edition_redirects: &'ra [EditionRedirectDecl<'ra>], +} + +#[derive(Clone, Copy, Debug)] +struct EditionRedirectDecl<'ra> { + edition: Edition, + target: Decl<'ra>, +} + +/// A resolved redirect import waiting to be attached to the default item with the same name. +#[derive(Clone)] +struct LocalEditionRedirect<'ra> { + module: LocalModule<'ra>, + key: BindingKey, + edition: Edition, + import_decl: Decl<'ra>, + default_decl: Option>, + span: Span, } /// `Interned` is used because values of this type have "identity" and compare as unequal even if @@ -1382,6 +1433,8 @@ pub struct Resolver<'ra, 'tcx> { extern_crate_map: UnordMap = Default::default(), module_children: LocalDefIdMap> = Default::default(), ambig_module_children: LocalDefIdMap> = Default::default(), + /// Resolved redirect imports waiting to be combined with their default module children. + local_edition_redirects: Vec> = Vec::new(), /// A map from nodes to anonymous modules. /// Anonymous modules are pseudo-modules that are implicitly created around items @@ -1592,6 +1645,7 @@ impl<'ra> ResolverArenas<'ra> { span, expansion, parent_module, + edition_redirects: &[], }) } @@ -1603,6 +1657,12 @@ impl<'ra> ResolverArenas<'ra> { // SAFETY: `Interned` is valid because values of this type have "identity". Interned::new_unchecked(self.dropless.alloc(data)) } + fn alloc_edition_redirects( + &'ra self, + redirects: &[EditionRedirectDecl<'ra>], + ) -> &'ra [EditionRedirectDecl<'ra>] { + if redirects.is_empty() { &[] } else { self.dropless.alloc_slice(redirects) } + } fn alloc_import(&'ra self, import: ImportData<'ra>) -> Import<'ra> { // SAFETY: `Interned` is valid because values of this type have "identity". Interned::new_unchecked(self.imports.alloc(import)) @@ -2116,14 +2176,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { cmr.visit_scopes(scope_set, parent_scope, ctxt, sp, None, |mut this, scope, _, _| { match scope { Scope::ModuleNonGlobs(module, _) => { - this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); + this.get_mut().traits_in_module(module, sp, assoc_item, &mut found_traits); } Scope::ModuleGlobs(..) => { // Already handled in `ModuleNonGlobs` (but see #144993). } Scope::StdLibPrelude => { if let Some(module) = this.prelude { - this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); + this.get_mut().traits_in_module(module, sp, assoc_item, &mut found_traits); } } Scope::ExternPreludeItems @@ -2141,14 +2201,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { fn traits_in_module( &mut self, module: Module<'ra>, + redirect_span: Span, assoc_item: Option<(Symbol, Namespace)>, found_traits: &mut Vec>, ) { - module.ensure_traits(self); + module.ensure_traits(self, redirect_span); let traits = module.traits.borrow(self); - for &(trait_name, trait_binding, trait_module, lint_ambiguous) in - traits.as_ref().unwrap().iter() - { + let (_, traits) = traits.as_ref().unwrap(); + for &(trait_name, trait_binding, trait_module, lint_ambiguous) in traits.iter() { if self.trait_may_have_item(trait_module, assoc_item) { let def_id = trait_binding.res().def_id(); let import_ids = self.find_transitive_imports(&trait_binding.kind, trait_name); @@ -2236,7 +2296,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { orig_ident_span: Span, ) -> NameResolutionRef<'ra> { *self.resolutions_mut(module).entry(key).or_insert_with(|| { - self.arenas.alloc_name_resolution(NameResolution::new(orig_ident_span)) + self.arenas.alloc_name_resolution(NameResolution::new(None, orig_ident_span)) }) } diff --git a/compiler/rustc_span/src/edition.rs b/compiler/rustc_span/src/edition.rs index e24e05df113b4..235a0bb97dc8b 100644 --- a/compiler/rustc_span/src/edition.rs +++ b/compiler/rustc_span/src/edition.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use rustc_macros::{BlobDecodable, Encodable, StableHash}; /// The edition of the compiler. (See [RFC 2052](https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md).) -#[derive(Clone, Copy, Hash, PartialEq, PartialOrd, Debug, Encodable, BlobDecodable, Eq)] +#[derive(Clone, Copy, Hash, PartialEq, PartialOrd, Debug, Encodable, BlobDecodable, Eq, Ord)] #[derive(StableHash)] pub enum Edition { // When adding new editions, be sure to do the following: diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 7665df4a4e5ae..795cad2b3a2e8 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -519,6 +519,7 @@ symbols! { await_macro, backchain, backend_repr, + before, begin_panic, bench, bevy_ecs, @@ -868,6 +869,7 @@ symbols! { dyn_trait, dynamic_no_pic: "dynamic-no-pic", edition_panic, + edition_redirect, effective_target_features, effects, eh_personality, @@ -1806,6 +1808,7 @@ symbols! { rustc_dump_variances_of_opaques, rustc_dump_vtable, rustc_dyn_incompatible_trait, + rustc_edition_redirect, rustc_effective_visibility, rustc_eii_foreign_item, rustc_evaluate_where_clauses, diff --git a/tests/ui/README.md b/tests/ui/README.md index b80c8215c1cf8..a07ddee601307 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -511,6 +511,11 @@ The `dyn` keyword is used to highlight that calls to methods on the associated T See [`dyn` keyword](https://doc.rust-lang.org/std/keyword.dyn.html). +## `tests/ui/edition-redirect/`: Edition-dependent item resolution + +Tests for resolving external items and associated items to different definitions +depending on the edition of the use site. + ## `tests/ui/editions/`: Rust edition-specific peculiarities These tests run in specific Rust editions, such as Rust 2015 or Rust 2018, and check errors and functionality related to specific now-deprecated idioms and features. diff --git a/tests/ui/edition-redirect/ambiguity.old.stderr b/tests/ui/edition-redirect/ambiguity.old.stderr new file mode 100644 index 0000000000000..5c2caa6704b51 --- /dev/null +++ b/tests/ui/edition-redirect/ambiguity.old.stderr @@ -0,0 +1,23 @@ +error[E0659]: `Item` is ambiguous + --> $DIR/ambiguity.rs:13:17 + | +LL | fn check(_: Item) {} + | ^^^^ ambiguous name + | + = note: ambiguous because of multiple glob imports of a name in the same module +note: `Item` could refer to the struct imported here + --> $DIR/ambiguity.rs:10:9 + | +LL | use edition_redirect::ambiguity::alias_a::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding an explicit import of `Item` to disambiguate +note: `Item` could also refer to the struct imported here + --> $DIR/ambiguity.rs:11:9 + | +LL | use edition_redirect::ambiguity::alias_b::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding an explicit import of `Item` to disambiguate + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0659`. diff --git a/tests/ui/edition-redirect/ambiguity.rs b/tests/ui/edition-redirect/ambiguity.rs new file mode 100644 index 0000000000000..47a6107fb1f55 --- /dev/null +++ b/tests/ui/edition-redirect/ambiguity.rs @@ -0,0 +1,17 @@ +//@ revisions: old current +//@[old] edition: 2021 +//@[current] edition: 2024 +//@ aux-build: basic.rs +//@[current] check-pass + +extern crate basic as edition_redirect; + +mod downstream_ambiguity { + use edition_redirect::ambiguity::alias_a::*; + use edition_redirect::ambiguity::alias_b::*; + + fn check(_: Item) {} + //[old]~^ ERROR `Item` is ambiguous +} + +fn main() {} diff --git a/tests/ui/edition-redirect/auxiliary/basic.rs b/tests/ui/edition-redirect/auxiliary/basic.rs new file mode 100644 index 0000000000000..efc4a13ffa266 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/basic.rs @@ -0,0 +1,109 @@ +#![feature(edition_redirect)] + +pub struct Oldest; +pub struct Middle; + +#[rustc_edition_redirect = "2021"] +pub use Middle as Redirected; +#[rustc_edition_redirect = "2018"] +pub use Oldest as Redirected; +pub struct Redirected; + +pub mod oldest_module { + pub const VALUE: usize = 1; +} + +pub mod middle_module { + pub const VALUE: usize = 2; +} + +#[rustc_edition_redirect = "2018"] +pub use oldest_module as redirected_module; +#[rustc_edition_redirect = "2021"] +pub use middle_module as redirected_module; +pub mod redirected_module { + pub const VALUE: usize = 3; +} + +pub mod use_targets { + pub struct OldestUse; + pub struct MiddleUse; + pub struct CurrentUse; +} + +#[rustc_edition_redirect = "2018"] +pub use use_targets::OldestUse as RedirectedUse; +#[rustc_edition_redirect = "2021"] +pub use use_targets::MiddleUse as RedirectedUse; +pub use use_targets::CurrentUse as RedirectedUse; + +pub mod same_redirect_a { + pub use crate::RedirectedUse as Item; +} + +pub mod same_redirect_b { + pub use crate::RedirectedUse as Item; +} + +pub mod same_redirects { + pub use crate::same_redirect_a::*; + pub use crate::same_redirect_b::*; +} + +pub mod reexport_scope { + pub struct Old; + pub struct Current; + + #[rustc_edition_redirect = "2021"] + pub use self::OldAlias as Redirected; + pub use self::Current as Redirected; + + pub use self::Old as OldAlias; +} + +pub use reexport_scope::Redirected as ScopedRedirected; + +#[macro_export] +macro_rules! oldest_macro { + () => { 1 }; +} + +#[macro_export] +macro_rules! middle_macro { + () => { 2 }; +} + +#[rustc_edition_redirect = "2018"] +pub use oldest_macro as redirected_macro; +#[rustc_edition_redirect = "2021"] +pub use middle_macro as redirected_macro; +#[macro_export] +macro_rules! redirected_macro { + () => { 3 }; +} + +pub mod ambiguity { + pub struct Shared; + pub struct OldA; + pub struct OldB; + + pub mod alias_a { + #[rustc_edition_redirect = "2021"] + pub use super::OldA as Item; + pub use super::Shared as Item; + } + + pub mod alias_b { + #[rustc_edition_redirect = "2021"] + pub use super::OldB as Item; + pub use super::Shared as Item; + } +} + +fn local_resolution_uses_default_items() { + let _: Redirected = Redirected; + let _: use_targets::CurrentUse = RedirectedUse; + let _: reexport_scope::Current = reexport_scope::Redirected; + const _: [(); 3] = [(); redirected_module::VALUE]; + const _: [(); 3] = [(); redirected_macro!()]; +} diff --git a/tests/ui/edition-redirect/auxiliary/macro-2018.rs b/tests/ui/edition-redirect/auxiliary/macro-2018.rs new file mode 100644 index 0000000000000..307240090c352 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-2018.rs @@ -0,0 +1,37 @@ +//@ edition: 2018 + +#[macro_export] +macro_rules! import_all { + () => { + use macro_source::*; + }; +} + +#[macro_export] +macro_rules! macro_use_source { + () => { + #[macro_use] + extern crate macro_source; + }; +} + +#[macro_export] +macro_rules! call_redirected_trait { + () => { + ().redirected_method() + }; +} + +#[macro_export] +macro_rules! redirected_type { + () => { + RedirectedItem + }; +} + +#[macro_export] +macro_rules! redirected_value { + () => { + RedirectedItem + }; +} diff --git a/tests/ui/edition-redirect/auxiliary/macro-2024.rs b/tests/ui/edition-redirect/auxiliary/macro-2024.rs new file mode 100644 index 0000000000000..8eebf7d35ac27 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-2024.rs @@ -0,0 +1,37 @@ +//@ edition: 2024 + +#[macro_export] +macro_rules! import_all { + () => { + use macro_source::*; + }; +} + +#[macro_export] +macro_rules! macro_use_source { + () => { + #[macro_use] + extern crate macro_source; + }; +} + +#[macro_export] +macro_rules! call_redirected_trait { + () => { + ().redirected_method() + }; +} + +#[macro_export] +macro_rules! redirected_type { + () => { + RedirectedItem + }; +} + +#[macro_export] +macro_rules! redirected_value { + () => { + RedirectedItem + }; +} diff --git a/tests/ui/edition-redirect/auxiliary/macro-source.rs b/tests/ui/edition-redirect/auxiliary/macro-source.rs new file mode 100644 index 0000000000000..2e510d249281f --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-source.rs @@ -0,0 +1,96 @@ +//@ edition: 2024 + +#![feature(edition_redirect)] +#![allow(internal_features)] + +pub struct Old; +pub struct Current; + +#[rustc_edition_redirect = "2018"] +pub use Old as Name; +pub use Current as Name; + +mod diagnostic_targets { + #[doc(alias = "OldAlias")] + pub struct AliasCarrier; + + pub trait Candidate {} + + pub mod diagnostic_module { + pub enum DiagnosticEnum { + Variant(u8), + } + } +} + +#[rustc_edition_redirect = "2018"] +pub use diagnostic_targets::AliasCarrier as AliasCarrier; +pub struct AliasCarrier; + +#[rustc_edition_redirect = "2018"] +pub use diagnostic_targets::Candidate as Candidate; +pub struct Candidate; + +#[rustc_edition_redirect = "2018"] +pub use diagnostic_targets::diagnostic_module as diagnostic_module; +pub mod diagnostic_module { + pub enum DiagnosticEnum { + CurrentVariant(u8), + } +} + +pub mod trait_prelude { + pub struct OldItem; + pub struct CurrentItem; + + #[rustc_edition_redirect = "2018"] + pub use OldItem as RedirectedItem; + pub use CurrentItem as RedirectedItem; + + pub struct OldMarker; + pub struct CurrentMarker; + + mod old { + pub trait RedirectedTrait { + fn redirected_method(&self) -> super::OldMarker; + } + + impl RedirectedTrait for () { + fn redirected_method(&self) -> super::OldMarker { + super::OldMarker + } + } + } + + #[rustc_edition_redirect = "2018"] + pub use old::RedirectedTrait as RedirectedTrait; + + pub trait RedirectedTrait { + fn redirected_method(&self) -> CurrentMarker; + } + + impl RedirectedTrait for () { + fn redirected_method(&self) -> CurrentMarker { + CurrentMarker + } + } +} + +#[macro_export] +macro_rules! old_macro { + () => { + pub type Selected = $crate::Old; + }; +} + +#[rustc_edition_redirect = "2018"] +pub use old_macro as redirected_macro; + +#[macro_export] +macro_rules! redirected_macro { + () => { + pub type Selected = $crate::Current; + }; +} + +pub mod nested {} diff --git a/tests/ui/edition-redirect/auxiliary/reexport-current.rs b/tests/ui/edition-redirect/auxiliary/reexport-current.rs new file mode 100644 index 0000000000000..c65a8196383ac --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-current.rs @@ -0,0 +1,5 @@ +//@ edition: 2024 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-old.rs b/tests/ui/edition-redirect/auxiliary/reexport-old.rs new file mode 100644 index 0000000000000..9df0165313ea0 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-old.rs @@ -0,0 +1,5 @@ +//@ edition: 2021 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs b/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs new file mode 100644 index 0000000000000..f27156a68a3be --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs @@ -0,0 +1,7 @@ +//@ edition: 2021 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +// Redirects are selected when this crate first imports the external name, so +// both re-exports are fixed according to this crate's edition. +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-source.rs b/tests/ui/edition-redirect/auxiliary/reexport-source.rs new file mode 100644 index 0000000000000..5a60c5d63f04b --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-source.rs @@ -0,0 +1,35 @@ +//@ edition: 2024 + +#![feature(edition_redirect)] + +pub struct Old; + +#[rustc_edition_redirect = "2021"] +pub use Old as Current; +pub struct Current; + +pub fn old() -> Old { + Old +} + +pub fn current() -> Current { + Current +} + +pub mod old_module { + pub struct Child; +} + +#[rustc_edition_redirect = "2021"] +pub use old_module as redirected_module; +pub mod redirected_module { + pub struct Child; +} + +pub fn old_child() -> old_module::Child { + old_module::Child +} + +pub fn current_child() -> redirected_module::Child { + redirected_module::Child +} diff --git a/tests/ui/edition-redirect/auxiliary/stability.rs b/tests/ui/edition-redirect/auxiliary/stability.rs new file mode 100644 index 0000000000000..ddb457430b4a5 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/stability.rs @@ -0,0 +1,20 @@ +#![feature(allow_internal_unstable, edition_redirect, staged_api)] +#![stable(feature = "edition_redirect_stability", since = "1.0.0")] + +#[doc(hidden)] +#[unstable(feature = "edition_redirect_old", issue = "none")] +#[macro_export] +macro_rules! old_macro { + () => { 1 }; +} + +#[rustc_edition_redirect = "2021"] +#[stable(feature = "edition_redirect_stability", since = "1.0.0")] +pub use old_macro as redirected_macro; + +#[stable(feature = "edition_redirect_stability", since = "1.0.0")] +#[allow_internal_unstable(edition_redirect_old)] +#[macro_export] +macro_rules! redirected_macro { + () => { 2 }; +} diff --git a/tests/ui/edition-redirect/basic.rs b/tests/ui/edition-redirect/basic.rs new file mode 100644 index 0000000000000..726332cd21350 --- /dev/null +++ b/tests/ui/edition-redirect/basic.rs @@ -0,0 +1,65 @@ +//@ revisions: edition2018 edition2021 edition2024 +//@[edition2018] edition: 2018 +//@[edition2021] edition: 2021 +//@[edition2024] edition: 2024 +//@ aux-build: basic.rs +//@ check-pass + +#[macro_use] +extern crate basic as edition_redirect; + +use edition_redirect::{ + Redirected as ImportedRedirected, redirected_macro as imported_redirected_macro, +}; +use edition_redirect::{ + reexport_scope::Current as ExpectedScopedRedirected, + use_targets::CurrentUse as ExpectedReexportedUse, +}; + +#[cfg(edition2018)] +use edition_redirect::{ + Oldest as ExpectedRedirected, use_targets::OldestUse as ExpectedRedirectedUse, +}; +#[cfg(edition2021)] +use edition_redirect::{ + Middle as ExpectedRedirected, use_targets::MiddleUse as ExpectedRedirectedUse, +}; +#[cfg(edition2024)] +use edition_redirect::{ + Redirected as ExpectedRedirected, use_targets::CurrentUse as ExpectedRedirectedUse, +}; + +#[cfg(edition2018)] +const EXPECTED_VALUE: usize = 1; +#[cfg(edition2021)] +const EXPECTED_VALUE: usize = 2; +#[cfg(edition2024)] +const EXPECTED_VALUE: usize = 3; + +fn explicit() { + let _: ExpectedRedirected = edition_redirect::Redirected; + let _: ExpectedRedirectedUse = edition_redirect::RedirectedUse; + let _: ExpectedScopedRedirected = edition_redirect::ScopedRedirected; + let _: edition_redirect::same_redirects::Item = ExpectedReexportedUse; + const _: [(); EXPECTED_VALUE] = [(); edition_redirect::redirected_module::VALUE]; + const _: [(); EXPECTED_VALUE] = [(); edition_redirect::redirected_macro!()]; + const _: [(); EXPECTED_VALUE] = [(); redirected_macro!()]; + let _: ImportedRedirected = ExpectedRedirected; + const _: [(); EXPECTED_VALUE] = [(); imported_redirected_macro!()]; +} + +mod glob { + use super::{EXPECTED_VALUE, ExpectedRedirected, ExpectedRedirectedUse}; + use edition_redirect::*; + + fn check() { + let _: Redirected = ExpectedRedirected; + let _: RedirectedUse = ExpectedRedirectedUse; + const _: [(); EXPECTED_VALUE] = [(); redirected_module::VALUE]; + const _: [(); EXPECTED_VALUE] = [(); redirected_macro!()]; + } +} + +fn main() { + explicit(); +} diff --git a/tests/ui/edition-redirect/diagnostic.rs b/tests/ui/edition-redirect/diagnostic.rs new file mode 100644 index 0000000000000..89254cf83c498 --- /dev/null +++ b/tests/ui/edition-redirect/diagnostic.rs @@ -0,0 +1,51 @@ +//@ edition: 2018 +//@ aux-build: macro-source.rs +//@ check-fail + +extern crate macro_source; + +// A macro exported at the crate root should still be suggested when it is +// incorrectly imported through a module, even when the root binding has an +// edition redirect. +use macro_source::nested::redirected_macro; +//~^ ERROR unresolved import `macro_source::nested::redirected_macro` +//~| HELP a macro with this name exists at the root of the crate +//~| SUGGESTION macro_source::redirected_macro +//~| HELP consider importing this trait +//~| SUGGESTION use macro_source::Candidate; + +// A missing import from a module that also contains redirected names should +// produce the usual unresolved-import diagnostic. +use macro_source::NoSuchImport; +//~^ ERROR unresolved import `macro_source::NoSuchImport` + +// In edition 2018, `Candidate` redirects to a trait, so it should be suggested +// as an import for a missing unqualified trait. The default `Candidate` is a +// struct. +fn import_candidate() {} +//~^ ERROR cannot find trait `Candidate` in this scope + +// A misspelled qualified trait name should likewise suggest the trait selected +// in edition 2018. +fn typo_candidate() {} +//~^ ERROR cannot find trait `Canddate` in crate `macro_source` +//~| HELP a trait with a similar name exists +//~| SUGGESTION Candidate + +// Doc aliases from the edition-selected target should be available in typo +// suggestions. The default `AliasCarrier` does not have this alias. +fn doc_alias(_: macro_source::OldAlias) {} +//~^ ERROR cannot find type `OldAlias` in crate `macro_source` +//~| HELP has a name defined in the doc alias attribute as `OldAlias` +//~| SUGGESTION AliasCarrier + +// An enum reached through a redirected module should still produce a suggestion +// using a variant from the selected module. +fn enum_variant() -> macro_source::diagnostic_module::DiagnosticEnum { + macro_source::diagnostic_module::DiagnosticEnum(0) + //~^ ERROR cannot find function, tuple struct or tuple variant `DiagnosticEnum` + //~| HELP try to construct the enum's variant + //~| SUGGESTION macro_source::diagnostic_module::DiagnosticEnum::Variant +} + +fn main() {} diff --git a/tests/ui/edition-redirect/diagnostic.stderr b/tests/ui/edition-redirect/diagnostic.stderr new file mode 100644 index 0000000000000..925f4900801e8 --- /dev/null +++ b/tests/ui/edition-redirect/diagnostic.stderr @@ -0,0 +1,78 @@ +error[E0432]: unresolved import `macro_source::nested::redirected_macro` + --> $DIR/diagnostic.rs:10:5 + | +LL | use macro_source::nested::redirected_macro; + | ^^^^^^^^^^^^^^^^^^^^^^---------------- + | | + | no `redirected_macro` in `nested` + | + = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined +help: a macro with this name exists at the root of the crate + | +LL - use macro_source::nested::redirected_macro; +LL + use macro_source::redirected_macro; + | + +error[E0432]: unresolved import `macro_source::NoSuchImport` + --> $DIR/diagnostic.rs:19:5 + | +LL | use macro_source::NoSuchImport; + | ^^^^^^^^^^^^^^------------ + | | + | no `NoSuchImport` in the root + +error[E0405]: cannot find trait `Candidate` in this scope + --> $DIR/diagnostic.rs:25:24 + | +LL | fn import_candidate() {} + | ^^^^^^^^^ not found in this scope + | +help: consider importing this trait + | +LL + use macro_source::Candidate; + | + +error[E0405]: cannot find trait `Canddate` in crate `macro_source` + --> $DIR/diagnostic.rs:30:36 + | +LL | fn typo_candidate() {} + | ^^^^^^^^ not found in `macro_source` + | +note: similarly named trait `Candidate` defined here + --> $DIR/auxiliary/macro-source.rs:17:5 + | +LL | pub trait Candidate {} + | ^^^^^^^^^^^^^^^^^^^ +help: a trait with a similar name exists + | +LL | fn typo_candidate() {} + | + + +error[E0425]: cannot find type `OldAlias` in crate `macro_source` + --> $DIR/diagnostic.rs:37:31 + | +LL | fn doc_alias(_: macro_source::OldAlias) {} + | ^^^^^^^^ + | +help: `AliasCarrier` has a name defined in the doc alias attribute as `OldAlias` + | +LL - fn doc_alias(_: macro_source::OldAlias) {} +LL + fn doc_alias(_: macro_source::AliasCarrier) {} + | + +error[E0423]: cannot find function, tuple struct or tuple variant `DiagnosticEnum` in module `macro_source::diagnostic_module` + --> $DIR/diagnostic.rs:45:38 + | +LL | macro_source::diagnostic_module::DiagnosticEnum(0) + | ^^^^^^^^^^^^^^ + | + = note: an enum named `macro_source::diagnostic_module::DiagnosticEnum` exists in another namespace +help: try to construct the enum's variant + | +LL | macro_source::diagnostic_module::DiagnosticEnum::Variant(0) + | +++++++++ + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0405, E0423, E0425, E0432. +For more information about an error, try `rustc --explain E0405`. diff --git a/tests/ui/edition-redirect/feature-gate.rs b/tests/ui/edition-redirect/feature-gate.rs new file mode 100644 index 0000000000000..e3e5c32db4dc4 --- /dev/null +++ b/tests/ui/edition-redirect/feature-gate.rs @@ -0,0 +1,13 @@ +// gate-test-edition_redirect + +#![feature(rustc_attrs)] + +pub struct Old; + +#[rustc_edition_redirect = "2024"] +//~^ ERROR the `rustc_edition_redirect` attribute is an experimental feature +pub use Old as Current; + +pub struct Current; + +fn main() {} diff --git a/tests/ui/edition-redirect/feature-gate.stderr b/tests/ui/edition-redirect/feature-gate.stderr new file mode 100644 index 0000000000000..34c58b2cf5857 --- /dev/null +++ b/tests/ui/edition-redirect/feature-gate.stderr @@ -0,0 +1,12 @@ +error[E0658]: the `rustc_edition_redirect` attribute is an experimental feature + --> $DIR/feature-gate.rs:7:3 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(edition_redirect)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/edition-redirect/invalid.rs b/tests/ui/edition-redirect/invalid.rs new file mode 100644 index 0000000000000..0ffbd59cdf113 --- /dev/null +++ b/tests/ui/edition-redirect/invalid.rs @@ -0,0 +1,68 @@ +#![feature(edition_redirect)] + +pub struct NotAUse; + +#[rustc_edition_redirect = "2024"] +//~^ ERROR the `rustc_edition_redirect` attribute cannot be used on structs +pub struct AlsoNotAUse; + +mod source { + pub struct Old; + pub struct Current; +} + +#[rustc_edition_redirect = "2024"] +//~^ ERROR `#[rustc_edition_redirect]` can only be applied to a single import +pub use source::{Current}; + +#[rustc_edition_redirect = "2024"] +//~^ ERROR `#[rustc_edition_redirect]` can only be applied to a single import +pub use source::*; + +mod private { + pub(crate) struct Old; +} + +#[rustc_edition_redirect = "2024"] +//~^ ERROR edition redirect for `Public` must have the same visibility as its default item +pub use private::Old as Public; +//~^ ERROR `Old` is only public within the crate, and cannot be re-exported outside + +pub struct Public; + +#[rustc_edition_redirect = "2024"] +pub use source::Missing as Unresolved; +//~^ ERROR unresolved import `source::Missing` + +pub struct Unresolved; + +pub type DuplicateTargetA = (); +pub type DuplicateTargetB = (); + +#[rustc_edition_redirect = "2024"] +pub use DuplicateTargetA as Duplicate; +#[rustc_edition_redirect = "2024"] +//~^ ERROR multiple edition redirects with edition 2024 for `Duplicate` +pub use DuplicateTargetB as Duplicate; + +pub type Duplicate = (); + +pub struct RestrictedTarget; + +#[rustc_edition_redirect = "2024"] +//~^ ERROR edition redirect for `Restricted` must have the same visibility as its default item +pub(crate) use RestrictedTarget as Restricted; + +pub struct Restricted; + +pub struct MissingDefaultTarget; + +#[rustc_edition_redirect = "2024"] +//~^ ERROR edition redirect for `MissingDefault` has no default item +pub use MissingDefaultTarget as MissingDefault; + +#[rustc_edition_redirect = "not an edition"] +//~^ ERROR invalid edition in edition redirect +pub use source::Old as InvalidEdition; + +fn main() {} diff --git a/tests/ui/edition-redirect/invalid.stderr b/tests/ui/edition-redirect/invalid.stderr new file mode 100644 index 0000000000000..73cc97ede10fd --- /dev/null +++ b/tests/ui/edition-redirect/invalid.stderr @@ -0,0 +1,78 @@ +error: edition redirect for `Public` must have the same visibility as its default item + --> $DIR/invalid.rs:26:1 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple edition redirects with edition 2024 for `Duplicate` + --> $DIR/invalid.rs:44:1 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: edition redirect for `Restricted` must have the same visibility as its default item + --> $DIR/invalid.rs:52:1 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: edition redirect for `MissingDefault` has no default item + --> $DIR/invalid.rs:60:1 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0364]: `Old` is only public within the crate, and cannot be re-exported outside + --> $DIR/invalid.rs:28:9 + | +LL | pub use private::Old as Public; + | ^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider marking `Old` as `pub` in the imported module + --> $DIR/invalid.rs:28:9 + | +LL | pub use private::Old as Public; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error[E0432]: unresolved import `source::Missing` + --> $DIR/invalid.rs:34:9 + | +LL | pub use source::Missing as Unresolved; + | ^^^^^^^^-------^^^^^^^^^^^^^^ + | | + | no `Missing` in `source` + +error: the `rustc_edition_redirect` attribute cannot be used on structs + --> $DIR/invalid.rs:5:3 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: the `rustc_edition_redirect` attribute can only be applied to use statements + +error: invalid edition in edition redirect + --> $DIR/invalid.rs:64:1 + | +LL | #[rustc_edition_redirect = "not an edition"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[rustc_edition_redirect]` can only be applied to a single import + --> $DIR/invalid.rs:14:1 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a separate, non-braced `use` item + +error: `#[rustc_edition_redirect]` can only be applied to a single import + --> $DIR/invalid.rs:18:1 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a separate, non-braced `use` item + +error: aborting due to 10 previous errors + +Some errors have detailed explanations: E0364, E0432. +For more information about an error, try `rustc --explain E0364`. diff --git a/tests/ui/edition-redirect/macro-glob.rs b/tests/ui/edition-redirect/macro-glob.rs new file mode 100644 index 0000000000000..73449a5c0243a --- /dev/null +++ b/tests/ui/edition-redirect/macro-glob.rs @@ -0,0 +1,25 @@ +//@ revisions: edition2018 edition2024 +//@[edition2018] edition: 2018 +//@[edition2024] edition: 2024 +//@ aux-build: macro-source.rs +//@ aux-build: macro-2018.rs +//@ aux-build: macro-2024.rs +//@ check-pass + +extern crate macro_2018; +extern crate macro_2024; +extern crate macro_source; + +// Redirects in a macro-generated glob use the edition of the glob's path, not +// the edition of the crate where the macro is invoked. +#[cfg(edition2018)] +macro_2024::import_all!(); +#[cfg(edition2024)] +macro_2018::import_all!(); + +fn main() { + #[cfg(edition2018)] + let _: macro_source::Current = Name; + #[cfg(edition2024)] + let _: macro_source::Old = Name; +} diff --git a/tests/ui/edition-redirect/macro-use.rs b/tests/ui/edition-redirect/macro-use.rs new file mode 100644 index 0000000000000..0e318e3829bf7 --- /dev/null +++ b/tests/ui/edition-redirect/macro-use.rs @@ -0,0 +1,32 @@ +//@ revisions: edition2018 edition2024 +//@[edition2018] edition: 2018 +//@[edition2024] edition: 2024 +//@ aux-build: macro-source.rs +//@ aux-build: macro-2018.rs +//@ aux-build: macro-2024.rs +//@ check-pass + +extern crate macro_2018; +extern crate macro_2024; +extern crate macro_source as source; + +// As with a glob import, importing every macro through `#[macro_use]` uses the +// edition of the generated `extern crate` item. +#[cfg(edition2018)] +macro_2024::macro_use_source!(); +#[cfg(edition2024)] +macro_2018::macro_use_source!(); + +redirected_macro!(); + +#[cfg(edition2018)] +fn check(value: Selected) -> source::Current { + value +} + +#[cfg(edition2024)] +fn check(value: Selected) -> source::Old { + value +} + +fn main() {} diff --git a/tests/ui/edition-redirect/prelude-import.rs b/tests/ui/edition-redirect/prelude-import.rs new file mode 100644 index 0000000000000..2406b9e12befb --- /dev/null +++ b/tests/ui/edition-redirect/prelude-import.rs @@ -0,0 +1,31 @@ +//@ edition: 2024 +//@ aux-build: macro-source.rs +//@ aux-build: macro-2018.rs +//@ aux-build: macro-2024.rs +//@ check-pass + +#![feature(prelude_import)] + +extern crate macro_2018; +extern crate macro_2024; +extern crate macro_source; + +#[prelude_import] +use macro_source::trait_prelude::*; + +fn main() { + // Ordinary names in the prelude are resolved using the identifier's + // edition. Exercise both namespaces of the redirected unit struct. + let _: macro_2018::redirected_type!() = macro_source::trait_prelude::OldItem; + let _: macro_2024::redirected_type!() = + macro_source::trait_prelude::CurrentItem; + let _: macro_source::trait_prelude::OldItem = macro_2018::redirected_value!(); + let _: macro_source::trait_prelude::CurrentItem = + macro_2024::redirected_value!(); + + // Both calls search the same external prelude module. Trait discovery must + // select the redirect using each macro-generated method name's edition + // rather than reuse the first cached result. + let _: OldMarker = macro_2018::call_redirected_trait!(); + let _: CurrentMarker = macro_2024::call_redirected_trait!(); +} diff --git a/tests/ui/edition-redirect/reexport.rs b/tests/ui/edition-redirect/reexport.rs new file mode 100644 index 0000000000000..7a3ee65079f33 --- /dev/null +++ b/tests/ui/edition-redirect/reexport.rs @@ -0,0 +1,24 @@ +//@ revisions: old current +//@[old] edition: 2021 +//@[current] edition: 2024 +//@ aux-crate: reexport_source=reexport-source.rs +//@ aux-crate: reexport_preserving=reexport-preserving.rs +//@ aux-crate: reexport_old=reexport-old.rs +//@ aux-crate: reexport_current=reexport-current.rs +//@ check-pass + +fn main() { + // A redirect is consumed by the first crate that imports it. The resulting + // re-export is therefore fixed to that crate's edition for all downstream + // users. + let _: reexport_preserving::Item = reexport_source::old(); + let _: reexport_preserving::Child = reexport_source::old_child(); + + let _: reexport_old::Item = reexport_source::old(); + let _: reexport_current::Item = reexport_source::current(); + + // Redirecting a module changes path traversal at the first ordinary `use`, + // but does not make the module's children independently redirected. + let _: reexport_old::Child = reexport_source::old_child(); + let _: reexport_current::Child = reexport_source::current_child(); +} diff --git a/tests/ui/edition-redirect/stability.old.stderr b/tests/ui/edition-redirect/stability.old.stderr new file mode 100644 index 0000000000000..58ddffdc86b72 --- /dev/null +++ b/tests/ui/edition-redirect/stability.old.stderr @@ -0,0 +1,12 @@ +error[E0658]: use of unstable library feature `edition_redirect_old` + --> $DIR/stability.rs:11:25 + | +LL | const _: [(); 1] = [(); redirected_macro!()]; + | ^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(edition_redirect_old)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/edition-redirect/stability.rs b/tests/ui/edition-redirect/stability.rs new file mode 100644 index 0000000000000..efcabfed0fed3 --- /dev/null +++ b/tests/ui/edition-redirect/stability.rs @@ -0,0 +1,17 @@ +//@ revisions: old current +//@[old] edition: 2021 +//@[current] edition: 2024 +//@ aux-build: stability.rs +//@[current] check-pass + +#[macro_use] +extern crate stability as edition_redirect_stability; + +#[cfg(old)] +const _: [(); 1] = [(); redirected_macro!()]; +//[old]~^ ERROR use of unstable library feature `edition_redirect_old` + +#[cfg(current)] +const _: [(); 2] = [(); redirected_macro!()]; + +fn main() {} From b82ebbdee984993a440bd8bee500d81e6ebb4df9 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 7 Sep 2026 00:37:42 +0100 Subject: [PATCH 2/4] Address review feedback --- .../src/attributes/rustc_internal.rs | 2 +- compiler/rustc_feature/src/unstable.rs | 2 - .../rustc_resolve/src/build_reduced_graph.rs | 116 +++++++----------- compiler/rustc_resolve/src/imports.rs | 67 +++++----- compiler/rustc_resolve/src/lib.rs | 40 +++--- compiler/rustc_span/src/symbol.rs | 2 - tests/ui/edition-redirect/auxiliary/basic.rs | 2 +- .../auxiliary/macro-source.rs | 2 +- .../auxiliary/reexport-source.rs | 2 +- .../edition-redirect/auxiliary/stability.rs | 2 +- tests/ui/edition-redirect/feature-gate.rs | 13 -- tests/ui/edition-redirect/feature-gate.stderr | 12 -- tests/ui/edition-redirect/invalid.rs | 2 +- 13 files changed, 97 insertions(+), 167 deletions(-) delete mode 100644 tests/ui/edition-redirect/feature-gate.rs delete mode 100644 tests/ui/edition-redirect/feature-gate.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 565c2c96b9ebc..748396a98ac2f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -349,7 +349,7 @@ impl SingleAttributeParser for RustcEditionRedirectParser { const PATH: &[Symbol] = &[sym::rustc_edition_redirect]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Use)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "2024"); - const STABILITY: AttributeStability = unstable!(edition_redirect); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let value = cx.expect_name_value(args, cx.attr_span, Some(sym::rustc_edition_redirect))?; diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 948214aaa48f1..ca70389815141 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -235,8 +235,6 @@ declare_features! ( (internal, const_param_ty_unchecked, "1.97.0", None), /// Allows writing custom MIR (internal, custom_mir, "1.65.0", None), - /// Allows defining edition redirects and preserving redirects on re-exports. - (internal, edition_redirect, "CURRENT_RUSTC_VERSION", None), /// Implementation details of externally implementable items (internal, eii_internals, "1.94.0", None), /// Implementation details of field representing types. diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 7fcac95b257fa..4b8bfa26b74fd 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -12,13 +12,13 @@ use rustc_ast::visit::{self, AssocCtxt, Visitor, WalkItemKind}; use rustc_ast::{ self as ast, AssocItem, AssocItemKind, Block, ConstItem, DUMMY_NODE_ID, Delegation, DelegationSource, Fn, ForeignItem, ForeignItemKind, Inline, Item, ItemKind, NodeId, StaticItem, - StmtKind, TraitAlias, TyAlias, + StmtKind, TraitAlias, TyAlias, attr, }; use rustc_attr_parsing::AttributeParser; use rustc_data_structures::fx::FxIndexMap; use rustc_expand::base::{ResolverExpand, SyntaxExtension, SyntaxExtensionKind}; use rustc_hir::Attribute; -use rustc_hir::attrs::{AttributeKind, EditionRedirect, MacroUseArgs}; +use rustc_hir::attrs::{AttributeKind, MacroUseArgs}; use rustc_hir::def::{self, *}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_index::bit_set::DenseBitSet; @@ -29,7 +29,6 @@ use rustc_middle::{bug, span_bug}; use rustc_span::def_id::{CRATE_MOD_ID, ModId}; use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind}; use rustc_span::{Ident, Span, Symbol, kw, sym}; -use smallvec::SmallVec; use thin_vec::ThinVec; use tracing::debug; @@ -40,10 +39,10 @@ use crate::imports::{ImportData, ImportKind, NameResolution, NameResolutionRef}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; use crate::{ - BindingKey, Decl, DeclData, DeclKind, DelayedVisResolutionError, ExternModule, - ExternPreludeEntry, Finalize, IdentKey, LocalModule, Module, ModuleKind, ModuleOrUniformRoot, - ParentScope, PathResult, Res, ResolutionTable, Resolver, Segment, Used, VisResolutionError, - diagnostics, + BindingKey, Decl, DeclData, DeclKind, DelayedVisResolutionError, EditionRedirectDecl, + ExternModule, ExternPreludeEntry, Finalize, IdentKey, LocalModule, Module, ModuleKind, + ModuleOrUniformRoot, ParentScope, PathResult, Res, ResolutionTable, Resolver, Segment, Used, + VisResolutionError, diagnostics, }; impl<'ra, 'tcx> Resolver<'ra, 'tcx> { @@ -401,32 +400,25 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Record primary definitions. let mut define_extern = |ns| { let orig_ident_span = orig_ident.span; - let edition_redirects = if edition_redirects.is_empty() { - // Fast path when there are no edition redirects. - &[] - } else { - let edition_redirects = edition_redirects - .iter() - .map(|redirect| crate::EditionRedirectDecl { - edition: redirect.edition, - // Model this as a one-step reexport under the original - // child's name: the target supplies the resolution, while - // the child supplies its visibility and provenance. - target: self.arenas.alloc_decl(DeclData { - kind: DeclKind::Def(redirect.target.expect_non_local()), - ambiguity: CmCell::new(None), - initial_vis: vis, - ambiguity_vis_max: CmCell::new(None), - ambiguity_vis_min: CmCell::new(None), - span, - expansion, - parent_module: Some(parent.to_module()), - edition_redirects: &[], - }), - }) - .collect::>(); - self.arenas.alloc_edition_redirects(&edition_redirects) - }; + let edition_redirects = edition_redirects + .iter() + .map(|redirect| EditionRedirectDecl { + edition: redirect.edition, + // Model this as a one-step reexport under the original child's name: the + // target supplies the resolution, while the child supplies its visibility, + // span, and parent module. + target: self.arenas.alloc_decl(DeclData { + kind: DeclKind::Def(redirect.target.expect_non_local()), + ambiguity: CmCell::new(None), + initial_vis: vis, + ambiguity_vis_max: CmCell::new(None), + ambiguity_vis_min: CmCell::new(None), + span, + expansion, + parent_module: Some(parent.to_module()), + }), + }) + .collect::>(); let decl = self.arenas.alloc_decl(DeclData { kind: DeclKind::Def(res), ambiguity: CmCell::new(ambig), @@ -436,10 +428,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { span, expansion, parent_module: Some(parent.to_module()), - edition_redirects, }); - let resolution = - self.arenas.alloc_name_resolution(NameResolution::new(Some(decl), orig_ident_span)); + let resolution = self.arenas.alloc_name_resolution(NameResolution::new( + Some(decl), + edition_redirects, + orig_ident_span, + )); let key = BindingKey::new_disambiguated(ident, ns, || (child_index + 1).try_into().unwrap()); @@ -571,12 +565,10 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { root_span: Span, root_id: NodeId, vis: Visibility, - edition_redirect: Option, ) { let current_module = self.parent_scope.module.expect_local(); let import = self.r.arenas.alloc_import(ImportData { kind, - edition_redirect, parent_scope: self.parent_scope, module_path, imported_module: CmCell::new(None), @@ -593,13 +585,13 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { self.r.indeterminate_imports.push((import, None, 0)); match import.kind { - ImportKind::Single { target, .. } => { + ImportKind::Single { target, edition_redirect, .. } => { // Don't add underscore imports to `single_imports` // because they cannot define any usable names. // // Same with edition redirects: these redirects are attached to // an existing name and don't introduce one themselves. - if target.name != kw::Underscore && import.edition_redirect.is_none() { + if target.name != kw::Underscore && edition_redirect.is_none() { self.r.per_ns_mut(|this, ns| { let key = BindingKey::new(IdentKey::new(target), ns); this.resolution_or_default(current_module.to_module(), key, target.span) @@ -756,17 +748,8 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { return; } - let kind = ImportKind::Single { - source: source.ident, - target: ident, - decls: Default::default(), - nested, - id, - def_id: feed.def_id(), - }; - let edition_redirect = if !nested - && ast::attr::contains_name(&item.attrs, sym::rustc_edition_redirect) + && attr::contains_name(&item.attrs, sym::rustc_edition_redirect) && let Some(Attribute::Parsed(AttributeKind::RustcEditionRedirect(redirect))) = AttributeParser::parse_limited_sym( self.r.tcx.sess, @@ -778,31 +761,23 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { None }; - self.add_import( - module_path, - kind, - use_tree.span(), - item, - root_span, - item.id, - vis, + let kind = ImportKind::Single { + source: source.ident, + target: ident, + decls: Default::default(), + nested, edition_redirect, - ); + id, + def_id: feed.def_id(), + }; + + self.add_import(module_path, kind, use_tree.span(), item, root_span, item.id, vis); } ast::UseTreeKind::Glob(_) => { if !ast::attr::contains_name(&item.attrs, sym::prelude_import) { let kind = ImportKind::Glob { max_vis: CmCell::new(None), id, def_id: feed.def_id() }; - self.add_import( - prefix, - kind, - use_tree.span(), - item, - root_span, - item.id, - vis, - None, - ); + self.add_import(prefix, kind, use_tree.span(), item, root_span, item.id, vis); } else { // Resolve the prelude import early. let path_res = @@ -1104,7 +1079,6 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { id: item.id, def_id: local_def_id, }, - edition_redirect: None, root_id: item.id, parent_scope, imported_module: CmCell::new(module), @@ -1237,7 +1211,6 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { let macro_use_import = |this: &Self, span, warn_private| { this.r.arenas.alloc_import(ImportData { kind: ImportKind::MacroUse { warn_private }, - edition_redirect: None, root_id: item.id, parent_scope: this.parent_scope, imported_module: CmCell::new(Some(ModuleOrUniformRoot::Module(module))), @@ -1416,7 +1389,6 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { if is_macro_export { let import = self.r.arenas.alloc_import(ImportData { kind: ImportKind::MacroExport, - edition_redirect: None, root_id: item.id, parent_scope: ParentScope { module: self.r.graph_root.to_module(), diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 28a14ebc12dde..36f48f3b33f72 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -38,10 +38,10 @@ use crate::diagnostics::{ }; use crate::ref_mut::{CmCell, CmRefCell}; use crate::{ - AmbiguityError, BindingKey, Decl, DeclData, DeclKind, Determinacy, Finalize, IdentKey, - ImportSuggestion, ImportSummary, LocalEditionRedirect, LocalModule, ModuleOrUniformRoot, - ParentScope, PathResult, PerNS, Res, ResolutionError, Resolver, ScopeSet, Segment, Used, - module_to_string, names_to_string, + AmbiguityError, BindingKey, Decl, DeclData, DeclKind, Determinacy, EditionRedirectDecl, + Finalize, IdentKey, ImportSuggestion, ImportSummary, LocalEditionRedirect, LocalModule, + ModuleOrUniformRoot, ParentScope, PathResult, PerNS, Res, ResolutionError, Resolver, ScopeSet, + Segment, Used, module_to_string, names_to_string, }; /// A potential import declaration in the process of being planted into a module. @@ -85,6 +85,10 @@ pub(crate) enum ImportKind<'ra> { decls: PerNS>>, /// Did this import result from a nested import? i.e. `use foo::{bar, baz};` nested: bool, + /// If present, this import supplies one edition-specific alternative for its target name. + /// It is resolved and checked like an ordinary import, but is not visible in the current + /// crate. + edition_redirect: Option, /// The ID of the `UseTree` that imported this `Import`. /// /// In the case where the `Import` was expanded from a "nested" use tree, @@ -126,7 +130,7 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use ImportKind::*; match self { - Single { source, target, decls, nested, id, def_id } => f + Single { source, target, decls, nested, edition_redirect, id, def_id } => f .debug_struct("Single") .field("source", source) .field("target", target) @@ -136,6 +140,7 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { &decls.clone().map(|b| b.into_inner().decl().map(|_| format_args!(".."))), ) .field("nested", nested) + .field("edition_redirect", edition_redirect) .field("id", id) .field("def_id", def_id) .finish(), @@ -213,10 +218,6 @@ pub(crate) struct ImportData<'ra> { /// /// This is `None` if the feature flag for `diagnostic::on_unknown` is disabled. pub on_unknown_attr: Option, - - /// If present, this import supplies one edition-specific alternative for its target name. - /// It is resolved and checked like an ordinary import, but is not visible in the current crate. - pub edition_redirect: Option, } /// `Interned` is used because values of this type have "identity" and compare as unequal even if @@ -274,6 +275,9 @@ impl<'ra> ImportData<'ra> { } } +// Keep `non_glob_decl` private outside this module so every access must +// explicitly choose whether to apply edition redirects or assert that none are +// present. mod name_resolution { use super::*; @@ -285,35 +289,40 @@ mod name_resolution { pub single_imports: FxIndexSet>, /// The non-glob declaration for this name, if it is known to exist. non_glob_decl: Option>, + /// Fully resolved cross-crate redirects attached to `non_glob_decl`. + edition_redirects: Box<[EditionRedirectDecl<'ra>]>, /// The glob declaration for this name, if it is known to exist. pub glob_decl: Option> = None, pub orig_ident_span: Span, } impl<'ra> NameResolution<'ra> { - pub(crate) fn new(non_glob_decl: Option>, orig_ident_span: Span) -> Self { + pub(crate) fn new( + non_glob_decl: Option>, + edition_redirects: Box<[EditionRedirectDecl<'ra>]>, + orig_ident_span: Span, + ) -> Self { NameResolution { single_imports: FxIndexSet::default(), non_glob_decl, + edition_redirects, orig_ident_span, .. } } pub(crate) fn non_glob_decl(&self) -> Option> { - self.non_glob_decl.map(|decl| { - assert!(decl.edition_redirects.is_empty()); - decl - }) + assert!(self.edition_redirects.is_empty()); + self.non_glob_decl } pub(crate) fn non_glob_decl_redir(&self, span: Span) -> Option> { self.non_glob_decl.map(|decl| { - if decl.edition_redirects.is_empty() { + if self.edition_redirects.is_empty() { return decl; } let edition = span.edition(); - match decl.edition_redirects.iter().find(|redirect| edition <= redirect.edition) { + match self.edition_redirects.iter().find(|redirect| edition <= redirect.edition) { Some(redirect) => redirect.target, None => decl, } @@ -341,8 +350,8 @@ impl<'ra> NameResolution<'ra> { /// code breakage in practice. /// FIXME: relationship between this function and similar `DeclData::determined` is unclear. pub(crate) fn determined_decl(&self) -> Option> { - if self.non_glob_decl().is_some() { - self.non_glob_decl() + if let non_glob_decl @ Some(..) = self.non_glob_decl() { + non_glob_decl } else if self.glob_decl.is_some() && self.single_imports.is_empty() { self.glob_decl } else { @@ -351,8 +360,7 @@ impl<'ra> NameResolution<'ra> { } pub(crate) fn determined_decl_redir(&self, span: Span) -> Option> { - let non_glob_decl = self.non_glob_decl_redir(span); - if non_glob_decl.is_some() { + if let non_glob_decl @ Some(..) = self.non_glob_decl_redir(span) { non_glob_decl } else if self.glob_decl.is_some() && self.single_imports.is_empty() { self.glob_decl @@ -540,7 +548,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ambiguity_vis_min: CmCell::new(None), expansion: import.parent_scope.expansion, parent_module: Some(import.parent_scope.module), - edition_redirects: &[], }) } @@ -794,11 +801,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if !(is_indeterminate || decls.iter().all(|d| d.get().decl().is_none())) { return; // Has resolution, do not create the dummy binding } - if import.edition_redirect.is_some() { - let dummy_decl = self.new_import_decl(self.dummy_decl, import); - self.record_use(target, dummy_decl, Used::Other); - return; - } let dummy_decl = self.dummy_decl; let dummy_decl = self.new_import_decl(dummy_decl, import); self.per_ns_mut(|this, ns| { @@ -903,7 +905,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { match (&import.kind, resolution_kind) { ( - ImportKind::Single { source, target, decls, .. }, + ImportKind::Single { source, target, decls, edition_redirect, .. }, ImportResolutionKind::Single(import_decls), ) => { self.per_ns_mut(|this, ns| { @@ -923,7 +925,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .emit(); } let ident = IdentKey::new(*target); - if let Some(redirect) = import.edition_redirect { + if let Some(redirect) = edition_redirect { // Redirect imports are checked like ordinary imports, but their // aliases are not visible while compiling this crate. They are // combined with the ordinary binding when metadata is produced. @@ -950,9 +952,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Don't remove underscores and edition // redirects from `single_imports`, they were // never added. - if target.name != kw::Underscore - && import.edition_redirect.is_none() - { + if target.name != kw::Underscore && edition_redirect.is_none() { let key = BindingKey::new(IdentKey::new(*target), ns); this.update_local_resolution( import.parent_scope.module.expect_local(), @@ -1011,9 +1011,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut module_children = Default::default(); let mut ambig_module_children = Default::default(); - for index in 0..self.local_modules.len() { - let module = self.local_modules[index]; - self.finalize_resolutions_in(module, &mut module_children, &mut ambig_module_children); + for module in &self.local_modules { + self.finalize_resolutions_in(*module, &mut module_children, &mut ambig_module_children); } self.module_children = module_children; self.ambig_module_children = ambig_module_children; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index ab6e6cd2cc741..a8cdd0bb26b48 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -698,13 +698,12 @@ struct ModuleData<'ra> { /// Used to memoize the traits in this module for faster searches through all traits in scope. /// - /// The cache is tagged with the edition it was generated with, since redirected trait - /// declarations can select different traits in each edition. + /// Redirected trait declarations can select different traits in each edition. traits: CmRefCell< - Option<( + FxHashMap< Edition, Box<[(Symbol, Decl<'ra>, Option>, bool /* lint ambiguous */)]>, - )>, + >, >, /// Span of the module itself. Used for error reporting. @@ -760,7 +759,7 @@ impl<'ra> ModuleData<'ra> { no_implicit_prelude, glob_importers: CmRefCell::new(Vec::new()), globs: CmRefCell::new(Vec::new()), - traits: CmRefCell::new(None), + traits: CmRefCell::new(FxHashMap::default()), span, expansion, self_decl, @@ -807,7 +806,7 @@ impl<'ra> ModuleData<'ra> { } impl<'ra> Module<'ra> { - /// Visits children without applying edition redirects. + /// Visits children and panics if any edition redirects are encountered. fn for_each_child<'tcx, R: AsRef>>( self, resolver: &R, @@ -855,13 +854,7 @@ impl<'ra> Module<'ra> { fn ensure_traits<'tcx>(self, resolver: &Resolver<'ra, 'tcx>, redirect_span: Span) { let edition = redirect_span.edition(); let mut traits = self.traits.borrow_mut_checked(resolver); - // Macro expansion can cause the same module to be queried with spans from different - // editions. Cache the most recently requested edition and recompute when it changes. - let needs_update = match traits.as_ref() { - Some((cached_edition, _)) => *cached_edition != edition, - None => true, - }; - if needs_update { + traits.entry(edition).or_insert_with(|| { let mut collected_traits = Vec::new(); self.for_each_child_redir(resolver, redirect_span, |r, ident, _, ns, mut decl| { if ns != TypeNS { @@ -891,8 +884,8 @@ impl<'ra> Module<'ra> { decl = ambig_decl; } }); - *traits = Some((edition, collected_traits.into_boxed_slice())); - } + collected_traits.into_boxed_slice() + }); } // `self` resolves to the first module ancestor that `is_normal`. @@ -1061,8 +1054,6 @@ struct DeclData<'ra> { /// declaration from the set, if its visibility is different from `initial_vis`. ambiguity_vis_min: CmCell>>, parent_module: Option>, - /// Fully resolved cross-crate redirects attached to this declaration. - edition_redirects: &'ra [EditionRedirectDecl<'ra>], } #[derive(Clone, Copy, Debug)] @@ -1645,7 +1636,6 @@ impl<'ra> ResolverArenas<'ra> { span, expansion, parent_module, - edition_redirects: &[], }) } @@ -1657,12 +1647,6 @@ impl<'ra> ResolverArenas<'ra> { // SAFETY: `Interned` is valid because values of this type have "identity". Interned::new_unchecked(self.dropless.alloc(data)) } - fn alloc_edition_redirects( - &'ra self, - redirects: &[EditionRedirectDecl<'ra>], - ) -> &'ra [EditionRedirectDecl<'ra>] { - if redirects.is_empty() { &[] } else { self.dropless.alloc_slice(redirects) } - } fn alloc_import(&'ra self, import: ImportData<'ra>) -> Import<'ra> { // SAFETY: `Interned` is valid because values of this type have "identity". Interned::new_unchecked(self.imports.alloc(import)) @@ -2207,7 +2191,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) { module.ensure_traits(self, redirect_span); let traits = module.traits.borrow(self); - let (_, traits) = traits.as_ref().unwrap(); + let traits = traits.get(&redirect_span.edition()).unwrap(); for &(trait_name, trait_binding, trait_module, lint_ambiguous) in traits.iter() { if self.trait_may_have_item(trait_module, assoc_item) { let def_id = trait_binding.res().def_id(); @@ -2296,7 +2280,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { orig_ident_span: Span, ) -> NameResolutionRef<'ra> { *self.resolutions_mut(module).entry(key).or_insert_with(|| { - self.arenas.alloc_name_resolution(NameResolution::new(None, orig_ident_span)) + self.arenas.alloc_name_resolution(NameResolution::new( + None, + Box::default(), + orig_ident_span, + )) }) } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 795cad2b3a2e8..8652123f13eb9 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -519,7 +519,6 @@ symbols! { await_macro, backchain, backend_repr, - before, begin_panic, bench, bevy_ecs, @@ -869,7 +868,6 @@ symbols! { dyn_trait, dynamic_no_pic: "dynamic-no-pic", edition_panic, - edition_redirect, effective_target_features, effects, eh_personality, diff --git a/tests/ui/edition-redirect/auxiliary/basic.rs b/tests/ui/edition-redirect/auxiliary/basic.rs index efc4a13ffa266..7d84ca00c4e05 100644 --- a/tests/ui/edition-redirect/auxiliary/basic.rs +++ b/tests/ui/edition-redirect/auxiliary/basic.rs @@ -1,4 +1,4 @@ -#![feature(edition_redirect)] +#![feature(rustc_attrs)] pub struct Oldest; pub struct Middle; diff --git a/tests/ui/edition-redirect/auxiliary/macro-source.rs b/tests/ui/edition-redirect/auxiliary/macro-source.rs index 2e510d249281f..dcdbe49cf5d35 100644 --- a/tests/ui/edition-redirect/auxiliary/macro-source.rs +++ b/tests/ui/edition-redirect/auxiliary/macro-source.rs @@ -1,6 +1,6 @@ //@ edition: 2024 -#![feature(edition_redirect)] +#![feature(rustc_attrs)] #![allow(internal_features)] pub struct Old; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-source.rs b/tests/ui/edition-redirect/auxiliary/reexport-source.rs index 5a60c5d63f04b..f0c501e515ceb 100644 --- a/tests/ui/edition-redirect/auxiliary/reexport-source.rs +++ b/tests/ui/edition-redirect/auxiliary/reexport-source.rs @@ -1,6 +1,6 @@ //@ edition: 2024 -#![feature(edition_redirect)] +#![feature(rustc_attrs)] pub struct Old; diff --git a/tests/ui/edition-redirect/auxiliary/stability.rs b/tests/ui/edition-redirect/auxiliary/stability.rs index ddb457430b4a5..a2c2f35ea9ee6 100644 --- a/tests/ui/edition-redirect/auxiliary/stability.rs +++ b/tests/ui/edition-redirect/auxiliary/stability.rs @@ -1,4 +1,4 @@ -#![feature(allow_internal_unstable, edition_redirect, staged_api)] +#![feature(allow_internal_unstable, rustc_attrs, staged_api)] #![stable(feature = "edition_redirect_stability", since = "1.0.0")] #[doc(hidden)] diff --git a/tests/ui/edition-redirect/feature-gate.rs b/tests/ui/edition-redirect/feature-gate.rs deleted file mode 100644 index e3e5c32db4dc4..0000000000000 --- a/tests/ui/edition-redirect/feature-gate.rs +++ /dev/null @@ -1,13 +0,0 @@ -// gate-test-edition_redirect - -#![feature(rustc_attrs)] - -pub struct Old; - -#[rustc_edition_redirect = "2024"] -//~^ ERROR the `rustc_edition_redirect` attribute is an experimental feature -pub use Old as Current; - -pub struct Current; - -fn main() {} diff --git a/tests/ui/edition-redirect/feature-gate.stderr b/tests/ui/edition-redirect/feature-gate.stderr deleted file mode 100644 index 34c58b2cf5857..0000000000000 --- a/tests/ui/edition-redirect/feature-gate.stderr +++ /dev/null @@ -1,12 +0,0 @@ -error[E0658]: the `rustc_edition_redirect` attribute is an experimental feature - --> $DIR/feature-gate.rs:7:3 - | -LL | #[rustc_edition_redirect = "2024"] - | ^^^^^^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(edition_redirect)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/edition-redirect/invalid.rs b/tests/ui/edition-redirect/invalid.rs index 0ffbd59cdf113..ffc4d92bf430e 100644 --- a/tests/ui/edition-redirect/invalid.rs +++ b/tests/ui/edition-redirect/invalid.rs @@ -1,4 +1,4 @@ -#![feature(edition_redirect)] +#![feature(rustc_attrs)] pub struct NotAUse; From 40558b27a60589ac0e36b6a3978f7a7612a2197c Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 7 Sep 2026 17:31:51 +0100 Subject: [PATCH 3/4] Switch rustc_edition_redirect to accept a range --- compiler/rustc_attr_ir/src/data_structures.rs | 3 +- .../src/attributes/rustc_internal.rs | 21 ++++--- .../rustc_attr_parsing/src/diagnostics.rs | 2 +- compiler/rustc_middle/src/middle/resolve.rs | 7 ++- .../rustc_resolve/src/build_reduced_graph.rs | 3 +- compiler/rustc_resolve/src/imports.rs | 27 +++++---- compiler/rustc_resolve/src/lib.rs | 6 +- tests/ui/edition-redirect/auxiliary/basic.rs | 22 +++---- .../auxiliary/macro-source.rs | 14 ++--- .../auxiliary/reexport-source.rs | 4 +- .../edition-redirect/auxiliary/stability.rs | 2 +- tests/ui/edition-redirect/invalid.rs | 47 +++++++++------ tests/ui/edition-redirect/invalid.stderr | 58 +++++++++++++------ 13 files changed, 133 insertions(+), 83 deletions(-) diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 77823c9a453bd..c38af0f706d70 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -122,7 +122,8 @@ pub enum InstrumentFnAttr { #[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)] pub struct EditionRedirect { - pub edition: Edition, + pub start: Edition, + pub end: Edition, pub span: Span, } diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 748396a98ac2f..68b130ba280bb 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -348,21 +348,26 @@ pub(crate) struct RustcEditionRedirectParser; impl SingleAttributeParser for RustcEditionRedirectParser { const PATH: &[Symbol] = &[sym::rustc_edition_redirect]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Use)]); - const TEMPLATE: AttributeTemplate = template!(NameValueStr: "2024"); + const TEMPLATE: AttributeTemplate = template!(NameValueStr: "2021..=2024"); const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let value = cx.expect_name_value(args, cx.attr_span, Some(sym::rustc_edition_redirect))?; let value = cx.expect_string_literal(value)?; - let edition = match value.as_str().parse::() { - Ok(edition) => edition, - Err(()) => { - cx.emit_err(diagnostics::InvalidEditionRedirect { span: cx.attr_span }); - return None; - } + let Some((start, end)) = value.as_str().split_once("..=").and_then(|(start, end)| { + let start = if start.is_empty() { Edition::Edition2015 } else { start.parse().ok()? }; + let end = end.parse().ok()?; + (start <= end).then_some((start, end)) + }) else { + cx.emit_err(diagnostics::InvalidEditionRedirect { span: cx.attr_span }); + return None; }; - Some(AttributeKind::RustcEditionRedirect(EditionRedirect { edition, span: cx.attr_span })) + Some(AttributeKind::RustcEditionRedirect(EditionRedirect { + start, + end, + span: cx.attr_span, + })) } } diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index 94052df963b59..5fc0aabfed83b 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -38,7 +38,7 @@ pub(crate) struct ItemFollowingInnerAttr { } #[derive(Diagnostic)] -#[diag("invalid edition in edition redirect")] +#[diag("invalid edition range in edition redirect")] pub(crate) struct InvalidEditionRedirect { #[primary_span] pub span: Span, diff --git a/compiler/rustc_middle/src/middle/resolve.rs b/compiler/rustc_middle/src/middle/resolve.rs index 5c1a98f925720..415cd67b3a4f6 100644 --- a/compiler/rustc_middle/src/middle/resolve.rs +++ b/compiler/rustc_middle/src/middle/resolve.rs @@ -133,10 +133,11 @@ impl Reexport { } } -/// A different item that a module child resolves to on or before a given edition. +/// A different item that a module child resolves to within an inclusive edition range. #[derive(Clone, Copy, Debug, TyEncodable, TyDecodable, StableHash)] pub struct EditionRedirect { - pub edition: Edition, + pub start: Edition, + pub end: Edition, pub target: Res, } @@ -157,7 +158,7 @@ pub struct ModChild { /// Reexport chain linking this module child to its original reexported item. /// Empty if the module child is a proper item. pub reexport_chain: SmallVec<[Reexport; 2]>, - /// Edition-dependent alternatives, sorted from the earliest boundary to the latest. + /// Edition-dependent alternatives, sorted by their range. pub edition_redirects: SmallVec<[EditionRedirect; 1]>, } diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 4b8bfa26b74fd..9c321b150dc2f 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -403,7 +403,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let edition_redirects = edition_redirects .iter() .map(|redirect| EditionRedirectDecl { - edition: redirect.edition, + start: redirect.start, + end: redirect.end, // Model this as a one-step reexport under the original child's name: the // target supplies the resolution, while the child supplies its visibility, // span, and parent module. diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 36f48f3b33f72..dca0c95be5949 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -322,7 +322,11 @@ mod name_resolution { return decl; } let edition = span.edition(); - match self.edition_redirects.iter().find(|redirect| edition <= redirect.edition) { + match self + .edition_redirects + .iter() + .find(|redirect| redirect.start <= edition && edition <= redirect.end) + { Some(redirect) => redirect.target, None => decl, } @@ -932,7 +936,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { this.local_edition_redirects.push(LocalEditionRedirect { module: import.parent_scope.module.expect_local(), key: BindingKey::new(ident, ns), - edition: redirect.edition, + start: redirect.start, + end: redirect.end, import_decl, default_decl: None, span: redirect.span, @@ -1971,7 +1976,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // cases. let mut diagnosed_missing_default = FxHashSet::default(); let mut diagnosed_visibility = FxHashSet::default(); - let mut diagnosed_duplicate = FxHashSet::default(); + let mut diagnosed_overlap = FxHashSet::default(); // Group redirects by the base decl that they are attached to. let mut groups = FxIndexMap::<_, SmallVec<[usize; 2]>>::default(); @@ -2003,18 +2008,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { self.local_edition_redirects[index].default_decl = Some(default_decl); } - // Check that there are no duplicate editions in the group. - indices.sort_by_key(|&index| self.local_edition_redirects[index].edition); + // Check that the edition ranges in the group do not overlap. + indices.sort_by_key(|&index| self.local_edition_redirects[index].start); for &[previous, redirect] in indices.array_windows() { let previous = &self.local_edition_redirects[previous]; let redirect = &self.local_edition_redirects[redirect]; - if previous.edition == redirect.edition && diagnosed_duplicate.insert(redirect.span) - { + if previous.end >= redirect.start && diagnosed_overlap.insert(redirect.span) { self.dcx().span_err( redirect.span, format!( - "multiple edition redirects with edition {} for `{}`", - redirect.edition, redirect.key.ident.name + "edition redirect range {}..={} overlaps with another range for `{}`", + redirect.start, redirect.end, redirect.key.ident.name ), ); } @@ -2049,11 +2053,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .filter(|redirect| redirect.default_decl == Some(decl)) .collect::>(); - redirects.sort_by_key(|redirect| redirect.edition); + redirects.sort_by_key(|redirect| (redirect.start, redirect.end)); redirects .into_iter() .map(|redirect| MetadataEditionRedirect { - edition: redirect.edition, + start: redirect.start, + end: redirect.end, target: redirect.import_decl.res().expect_non_local(), }) .collect() diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index a8cdd0bb26b48..682f1effca869 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1058,7 +1058,8 @@ struct DeclData<'ra> { #[derive(Clone, Copy, Debug)] struct EditionRedirectDecl<'ra> { - edition: Edition, + start: Edition, + end: Edition, target: Decl<'ra>, } @@ -1067,7 +1068,8 @@ struct EditionRedirectDecl<'ra> { struct LocalEditionRedirect<'ra> { module: LocalModule<'ra>, key: BindingKey, - edition: Edition, + start: Edition, + end: Edition, import_decl: Decl<'ra>, default_decl: Option>, span: Span, diff --git a/tests/ui/edition-redirect/auxiliary/basic.rs b/tests/ui/edition-redirect/auxiliary/basic.rs index 7d84ca00c4e05..2e48902e20ce0 100644 --- a/tests/ui/edition-redirect/auxiliary/basic.rs +++ b/tests/ui/edition-redirect/auxiliary/basic.rs @@ -3,9 +3,9 @@ pub struct Oldest; pub struct Middle; -#[rustc_edition_redirect = "2021"] +#[rustc_edition_redirect = "2021..=2021"] pub use Middle as Redirected; -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use Oldest as Redirected; pub struct Redirected; @@ -17,9 +17,9 @@ pub mod middle_module { pub const VALUE: usize = 2; } -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use oldest_module as redirected_module; -#[rustc_edition_redirect = "2021"] +#[rustc_edition_redirect = "2021..=2021"] pub use middle_module as redirected_module; pub mod redirected_module { pub const VALUE: usize = 3; @@ -31,9 +31,9 @@ pub mod use_targets { pub struct CurrentUse; } -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use use_targets::OldestUse as RedirectedUse; -#[rustc_edition_redirect = "2021"] +#[rustc_edition_redirect = "2021..=2021"] pub use use_targets::MiddleUse as RedirectedUse; pub use use_targets::CurrentUse as RedirectedUse; @@ -54,7 +54,7 @@ pub mod reexport_scope { pub struct Old; pub struct Current; - #[rustc_edition_redirect = "2021"] + #[rustc_edition_redirect = "..=2021"] pub use self::OldAlias as Redirected; pub use self::Current as Redirected; @@ -73,9 +73,9 @@ macro_rules! middle_macro { () => { 2 }; } -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use oldest_macro as redirected_macro; -#[rustc_edition_redirect = "2021"] +#[rustc_edition_redirect = "2021..=2021"] pub use middle_macro as redirected_macro; #[macro_export] macro_rules! redirected_macro { @@ -88,13 +88,13 @@ pub mod ambiguity { pub struct OldB; pub mod alias_a { - #[rustc_edition_redirect = "2021"] + #[rustc_edition_redirect = "..=2021"] pub use super::OldA as Item; pub use super::Shared as Item; } pub mod alias_b { - #[rustc_edition_redirect = "2021"] + #[rustc_edition_redirect = "..=2021"] pub use super::OldB as Item; pub use super::Shared as Item; } diff --git a/tests/ui/edition-redirect/auxiliary/macro-source.rs b/tests/ui/edition-redirect/auxiliary/macro-source.rs index dcdbe49cf5d35..ad0561cc82d3c 100644 --- a/tests/ui/edition-redirect/auxiliary/macro-source.rs +++ b/tests/ui/edition-redirect/auxiliary/macro-source.rs @@ -6,7 +6,7 @@ pub struct Old; pub struct Current; -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use Old as Name; pub use Current as Name; @@ -23,15 +23,15 @@ mod diagnostic_targets { } } -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use diagnostic_targets::AliasCarrier as AliasCarrier; pub struct AliasCarrier; -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use diagnostic_targets::Candidate as Candidate; pub struct Candidate; -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use diagnostic_targets::diagnostic_module as diagnostic_module; pub mod diagnostic_module { pub enum DiagnosticEnum { @@ -43,7 +43,7 @@ pub mod trait_prelude { pub struct OldItem; pub struct CurrentItem; - #[rustc_edition_redirect = "2018"] + #[rustc_edition_redirect = "..=2018"] pub use OldItem as RedirectedItem; pub use CurrentItem as RedirectedItem; @@ -62,7 +62,7 @@ pub mod trait_prelude { } } - #[rustc_edition_redirect = "2018"] + #[rustc_edition_redirect = "..=2018"] pub use old::RedirectedTrait as RedirectedTrait; pub trait RedirectedTrait { @@ -83,7 +83,7 @@ macro_rules! old_macro { }; } -#[rustc_edition_redirect = "2018"] +#[rustc_edition_redirect = "..=2018"] pub use old_macro as redirected_macro; #[macro_export] diff --git a/tests/ui/edition-redirect/auxiliary/reexport-source.rs b/tests/ui/edition-redirect/auxiliary/reexport-source.rs index f0c501e515ceb..f2717a1e90b0c 100644 --- a/tests/ui/edition-redirect/auxiliary/reexport-source.rs +++ b/tests/ui/edition-redirect/auxiliary/reexport-source.rs @@ -4,7 +4,7 @@ pub struct Old; -#[rustc_edition_redirect = "2021"] +#[rustc_edition_redirect = "..=2021"] pub use Old as Current; pub struct Current; @@ -20,7 +20,7 @@ pub mod old_module { pub struct Child; } -#[rustc_edition_redirect = "2021"] +#[rustc_edition_redirect = "..=2021"] pub use old_module as redirected_module; pub mod redirected_module { pub struct Child; diff --git a/tests/ui/edition-redirect/auxiliary/stability.rs b/tests/ui/edition-redirect/auxiliary/stability.rs index a2c2f35ea9ee6..8eb6dc0070ab6 100644 --- a/tests/ui/edition-redirect/auxiliary/stability.rs +++ b/tests/ui/edition-redirect/auxiliary/stability.rs @@ -8,7 +8,7 @@ macro_rules! old_macro { () => { 1 }; } -#[rustc_edition_redirect = "2021"] +#[rustc_edition_redirect = "..=2021"] #[stable(feature = "edition_redirect_stability", since = "1.0.0")] pub use old_macro as redirected_macro; diff --git a/tests/ui/edition-redirect/invalid.rs b/tests/ui/edition-redirect/invalid.rs index ffc4d92bf430e..51d570e45ee1d 100644 --- a/tests/ui/edition-redirect/invalid.rs +++ b/tests/ui/edition-redirect/invalid.rs @@ -2,7 +2,7 @@ pub struct NotAUse; -#[rustc_edition_redirect = "2024"] +#[rustc_edition_redirect = "..=2024"] //~^ ERROR the `rustc_edition_redirect` attribute cannot be used on structs pub struct AlsoNotAUse; @@ -11,11 +11,11 @@ mod source { pub struct Current; } -#[rustc_edition_redirect = "2024"] +#[rustfmt::skip] +#[rustc_edition_redirect = "..=2024"] //~^ ERROR `#[rustc_edition_redirect]` can only be applied to a single import pub use source::{Current}; - -#[rustc_edition_redirect = "2024"] +#[rustc_edition_redirect = "..=2024"] //~^ ERROR `#[rustc_edition_redirect]` can only be applied to a single import pub use source::*; @@ -23,33 +23,33 @@ mod private { pub(crate) struct Old; } -#[rustc_edition_redirect = "2024"] +#[rustc_edition_redirect = "..=2024"] //~^ ERROR edition redirect for `Public` must have the same visibility as its default item pub use private::Old as Public; //~^ ERROR `Old` is only public within the crate, and cannot be re-exported outside pub struct Public; -#[rustc_edition_redirect = "2024"] +#[rustc_edition_redirect = "..=2024"] pub use source::Missing as Unresolved; //~^ ERROR unresolved import `source::Missing` pub struct Unresolved; -pub type DuplicateTargetA = (); -pub type DuplicateTargetB = (); +pub type OverlapTargetA = (); +pub type OverlapTargetB = (); -#[rustc_edition_redirect = "2024"] -pub use DuplicateTargetA as Duplicate; -#[rustc_edition_redirect = "2024"] -//~^ ERROR multiple edition redirects with edition 2024 for `Duplicate` -pub use DuplicateTargetB as Duplicate; +#[rustc_edition_redirect = "..=2021"] +pub use OverlapTargetA as Overlap; +#[rustc_edition_redirect = "2021..=2024"] +//~^ ERROR edition redirect range 2021..=2024 overlaps with another range for `Overlap` +pub use OverlapTargetB as Overlap; -pub type Duplicate = (); +pub type Overlap = (); pub struct RestrictedTarget; -#[rustc_edition_redirect = "2024"] +#[rustc_edition_redirect = "..=2024"] //~^ ERROR edition redirect for `Restricted` must have the same visibility as its default item pub(crate) use RestrictedTarget as Restricted; @@ -57,12 +57,23 @@ pub struct Restricted; pub struct MissingDefaultTarget; -#[rustc_edition_redirect = "2024"] +#[rustc_edition_redirect = "..=2024"] //~^ ERROR edition redirect for `MissingDefault` has no default item pub use MissingDefaultTarget as MissingDefault; - #[rustc_edition_redirect = "not an edition"] -//~^ ERROR invalid edition in edition redirect +//~^ ERROR invalid edition range in edition redirect pub use source::Old as InvalidEdition; +#[rustc_edition_redirect = "2024"] +//~^ ERROR invalid edition range in edition redirect +pub use source::Old as MissingRangeOperator; +#[rustc_edition_redirect = "2021.."] +//~^ ERROR invalid edition range in edition redirect +pub use source::Old as MissingInclusiveEnd; +#[rustc_edition_redirect = "..2021"] +//~^ ERROR invalid edition range in edition redirect +pub use source::Old as ExclusiveRange; +#[rustc_edition_redirect = "2024..=2021"] +//~^ ERROR invalid edition range in edition redirect +pub use source::Old as InvertedRange; fn main() {} diff --git a/tests/ui/edition-redirect/invalid.stderr b/tests/ui/edition-redirect/invalid.stderr index 73cc97ede10fd..ff50ccb8a66eb 100644 --- a/tests/ui/edition-redirect/invalid.stderr +++ b/tests/ui/edition-redirect/invalid.stderr @@ -1,26 +1,26 @@ error: edition redirect for `Public` must have the same visibility as its default item --> $DIR/invalid.rs:26:1 | -LL | #[rustc_edition_redirect = "2024"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[rustc_edition_redirect = "..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: multiple edition redirects with edition 2024 for `Duplicate` +error: edition redirect range 2021..=2024 overlaps with another range for `Overlap` --> $DIR/invalid.rs:44:1 | -LL | #[rustc_edition_redirect = "2024"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[rustc_edition_redirect = "2021..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: edition redirect for `Restricted` must have the same visibility as its default item --> $DIR/invalid.rs:52:1 | -LL | #[rustc_edition_redirect = "2024"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[rustc_edition_redirect = "..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: edition redirect for `MissingDefault` has no default item --> $DIR/invalid.rs:60:1 | -LL | #[rustc_edition_redirect = "2024"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[rustc_edition_redirect = "..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0364]: `Old` is only public within the crate, and cannot be re-exported outside --> $DIR/invalid.rs:28:9 @@ -45,34 +45,58 @@ LL | pub use source::Missing as Unresolved; error: the `rustc_edition_redirect` attribute cannot be used on structs --> $DIR/invalid.rs:5:3 | -LL | #[rustc_edition_redirect = "2024"] +LL | #[rustc_edition_redirect = "..=2024"] | ^^^^^^^^^^^^^^^^^^^^^^ | = help: the `rustc_edition_redirect` attribute can only be applied to use statements -error: invalid edition in edition redirect - --> $DIR/invalid.rs:64:1 +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:63:1 | LL | #[rustc_edition_redirect = "not an edition"] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `#[rustc_edition_redirect]` can only be applied to a single import - --> $DIR/invalid.rs:14:1 +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:66:1 | LL | #[rustc_edition_redirect = "2024"] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:69:1 + | +LL | #[rustc_edition_redirect = "2021.."] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:72:1 + | +LL | #[rustc_edition_redirect = "..2021"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:75:1 + | +LL | #[rustc_edition_redirect = "2024..=2021"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[rustc_edition_redirect]` can only be applied to a single import + --> $DIR/invalid.rs:15:1 + | +LL | #[rustc_edition_redirect = "..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a separate, non-braced `use` item error: `#[rustc_edition_redirect]` can only be applied to a single import --> $DIR/invalid.rs:18:1 | -LL | #[rustc_edition_redirect = "2024"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[rustc_edition_redirect = "..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a separate, non-braced `use` item -error: aborting due to 10 previous errors +error: aborting due to 14 previous errors Some errors have detailed explanations: E0364, E0432. For more information about an error, try `rustc --explain E0364`. From 8617f6183d9ed9f5d46790a966c597662280cb5a Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 9 Sep 2026 15:46:27 -0400 Subject: [PATCH 4/4] Use RangeInclusive for edition ranges --- compiler/rustc_attr_ir/src/data_structures.rs | 4 ++-- compiler/rustc_attr_ir/src/pretty_printing.rs | 2 ++ .../src/attributes/rustc_internal.rs | 10 +++++----- compiler/rustc_middle/src/middle/resolve.rs | 5 +++-- .../rustc_resolve/src/build_reduced_graph.rs | 3 +-- compiler/rustc_resolve/src/imports.rs | 18 +++++++++--------- compiler/rustc_resolve/src/lib.rs | 7 +++---- compiler/rustc_serialize/src/serialize.rs | 14 ++++++++++++++ 8 files changed, 39 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index c38af0f706d70..79139b694d5a2 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::fmt; use std::path::PathBuf; +use std::range::RangeInclusive; pub use ReprAttr::*; use rustc_abi::Align; @@ -122,8 +123,7 @@ pub enum InstrumentFnAttr { #[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)] pub struct EditionRedirect { - pub start: Edition, - pub end: Edition, + pub range: RangeInclusive, pub span: Span, } diff --git a/compiler/rustc_attr_ir/src/pretty_printing.rs b/compiler/rustc_attr_ir/src/pretty_printing.rs index 8afb5aad76419..e37b8e02178ae 100644 --- a/compiler/rustc_attr_ir/src/pretty_printing.rs +++ b/compiler/rustc_attr_ir/src/pretty_printing.rs @@ -1,6 +1,7 @@ use std::num::NonZero; use std::ops::Deref; use std::path::PathBuf; +use std::range::RangeInclusive; use rustc_abi::Align; use rustc_ast::ast::{Path, join_path_idents}; @@ -211,4 +212,5 @@ print_debug!( CrateType, NativeLibKind, CollapseMacroDebuginfo, + RangeInclusive, ); diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 68b130ba280bb..f9881f3e844ad 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use std::range::RangeInclusive; use rustc_ast::{LitIntType, LitKind, MetaItemLit}; use rustc_attr_ir::lang_items::LangItem; @@ -354,18 +355,17 @@ impl SingleAttributeParser for RustcEditionRedirectParser { fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let value = cx.expect_name_value(args, cx.attr_span, Some(sym::rustc_edition_redirect))?; let value = cx.expect_string_literal(value)?; - let Some((start, end)) = value.as_str().split_once("..=").and_then(|(start, end)| { + let Some((start, last)) = value.as_str().split_once("..=").and_then(|(start, end)| { let start = if start.is_empty() { Edition::Edition2015 } else { start.parse().ok()? }; - let end = end.parse().ok()?; - (start <= end).then_some((start, end)) + let last = end.parse().ok()?; + (start <= last).then_some((start, last)) }) else { cx.emit_err(diagnostics::InvalidEditionRedirect { span: cx.attr_span }); return None; }; Some(AttributeKind::RustcEditionRedirect(EditionRedirect { - start, - end, + range: RangeInclusive { start, last }, span: cx.attr_span, })) } diff --git a/compiler/rustc_middle/src/middle/resolve.rs b/compiler/rustc_middle/src/middle/resolve.rs index 415cd67b3a4f6..85542e5764ad6 100644 --- a/compiler/rustc_middle/src/middle/resolve.rs +++ b/compiler/rustc_middle/src/middle/resolve.rs @@ -1,6 +1,8 @@ //! This module contains types that carry name resolution results from `rustc_resolve` to a //! consumer in another crate (e.g. AST lowering, metadata, or a query). +use std::range::RangeInclusive; + use rustc_ast::node_id::NodeMap; use rustc_ast::{self as ast, NodeId}; use rustc_attr_ir::StrippedCfgItem; @@ -136,8 +138,7 @@ impl Reexport { /// A different item that a module child resolves to within an inclusive edition range. #[derive(Clone, Copy, Debug, TyEncodable, TyDecodable, StableHash)] pub struct EditionRedirect { - pub start: Edition, - pub end: Edition, + pub range: RangeInclusive, pub target: Res, } diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 9c321b150dc2f..e859faba3bccc 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -403,8 +403,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let edition_redirects = edition_redirects .iter() .map(|redirect| EditionRedirectDecl { - start: redirect.start, - end: redirect.end, + range: redirect.range, // Model this as a one-step reexport under the original child's name: the // target supplies the resolution, while the child supplies its visibility, // span, and parent module. diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index dca0c95be5949..fb1b3cab631ef 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -325,7 +325,7 @@ mod name_resolution { match self .edition_redirects .iter() - .find(|redirect| redirect.start <= edition && edition <= redirect.end) + .find(|redirect| redirect.range.contains(&edition)) { Some(redirect) => redirect.target, None => decl, @@ -936,8 +936,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { this.local_edition_redirects.push(LocalEditionRedirect { module: import.parent_scope.module.expect_local(), key: BindingKey::new(ident, ns), - start: redirect.start, - end: redirect.end, + range: redirect.range, import_decl, default_decl: None, span: redirect.span, @@ -2009,16 +2008,18 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } // Check that the edition ranges in the group do not overlap. - indices.sort_by_key(|&index| self.local_edition_redirects[index].start); + indices.sort_by_key(|&index| self.local_edition_redirects[index].range.start); for &[previous, redirect] in indices.array_windows() { let previous = &self.local_edition_redirects[previous]; let redirect = &self.local_edition_redirects[redirect]; - if previous.end >= redirect.start && diagnosed_overlap.insert(redirect.span) { + if previous.range.last >= redirect.range.start + && diagnosed_overlap.insert(redirect.span) + { self.dcx().span_err( redirect.span, format!( "edition redirect range {}..={} overlaps with another range for `{}`", - redirect.start, redirect.end, redirect.key.ident.name + redirect.range.start, redirect.range.last, redirect.key.ident.name ), ); } @@ -2053,12 +2054,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .filter(|redirect| redirect.default_decl == Some(decl)) .collect::>(); - redirects.sort_by_key(|redirect| (redirect.start, redirect.end)); + redirects.sort_by_key(|redirect| redirect.range.start); redirects .into_iter() .map(|redirect| MetadataEditionRedirect { - start: redirect.start, - end: redirect.end, + range: redirect.range, target: redirect.import_decl.res().expect_non_local(), }) .collect() diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 682f1effca869..65ca41dbb3d7c 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -24,6 +24,7 @@ use std::cell::RefMut; use std::collections::BTreeSet; use std::ops::ControlFlow; +use std::range::RangeInclusive; use std::sync::{Arc, OnceLock}; use std::{fmt, mem}; @@ -1058,8 +1059,7 @@ struct DeclData<'ra> { #[derive(Clone, Copy, Debug)] struct EditionRedirectDecl<'ra> { - start: Edition, - end: Edition, + range: RangeInclusive, target: Decl<'ra>, } @@ -1068,8 +1068,7 @@ struct EditionRedirectDecl<'ra> { struct LocalEditionRedirect<'ra> { module: LocalModule<'ra>, key: BindingKey, - start: Edition, - end: Edition, + range: RangeInclusive, import_decl: Decl<'ra>, default_decl: Option>, span: Span, diff --git a/compiler/rustc_serialize/src/serialize.rs b/compiler/rustc_serialize/src/serialize.rs index 32a301a28ae8e..6c4c40d714d19 100644 --- a/compiler/rustc_serialize/src/serialize.rs +++ b/compiler/rustc_serialize/src/serialize.rs @@ -7,6 +7,7 @@ use std::hash::{BuildHasher, Hash}; use std::marker::{PhantomData, PointeeSized}; use std::num::{NonZero, ZeroablePrimitive}; use std::path; +use std::range::RangeInclusive; use std::rc::Rc; use std::sync::Arc; @@ -425,6 +426,19 @@ impl, T2: Decodable> Decodable for Result> Encodable for RangeInclusive { + fn encode(&self, s: &mut S) { + self.start.encode(s); + self.last.encode(s); + } +} + +impl> Decodable for RangeInclusive { + fn decode(d: &mut D) -> Self { + RangeInclusive { start: T::decode(d), last: T::decode(d) } + } +} + macro_rules! peel { ($name:ident, $($other:ident,)*) => (tuple! { $($other,)* }) }