diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index b101d378bab98..ee04bb47d09c0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -6,10 +6,12 @@ use rustc_feature::AttributeStability; use rustc_hir::LangItem; use rustc_hir::attrs::{ BorrowckGraphvizFormatKind, CguFields, CguKind, DivergingBlockBehavior, - DivergingFallbackBehavior, RustcCleanAttribute, RustcCleanQueries, RustcMirKind, + DivergingFallbackBehavior, EditionRedirect, RustcCleanAttribute, RustcCleanQueries, + RustcMirKind, }; use rustc_hir::target::GenericParamKind; use rustc_span::Symbol; +use rustc_span::edition::Edition; use super::prelude::*; use super::util::parse_single_integer; @@ -342,6 +344,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 before = match value.as_str().parse::() { + Ok(before) => before, + Err(()) => { + cx.emit_err(diagnostics::InvalidEditionRedirect { span: cx.attr_span }); + return None; + } + }; + + Some(AttributeKind::RustcEditionRedirect(EditionRedirect { before, 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 55732edfbd166..d7253db188459 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -239,6 +239,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 f9ebd78580b4c..d965e0edf72ad 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -27,6 +27,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 72b51ad204b9d..37b71ec6134d9 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -246,6 +246,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 b15ea5da6f0cb..166a9e4157208 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -262,6 +262,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_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 530483e87329c..64c9eebb525cb 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -15,6 +15,7 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_error_messages::{DiagArgValue, IntoDiagArg}; use rustc_macros::{Decodable, Encodable, 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}; pub use rustc_target::spec::SanitizerSet; @@ -152,6 +153,12 @@ pub enum InstrumentFnAttr { Off, } +#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)] +pub struct EditionRedirect { + pub before: Edition, + pub span: Span, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Default, PrintAttribute)] #[derive(Encodable, Decodable, StableHash)] pub enum OptimizeAttr { @@ -1480,6 +1487,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_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 455af47142446..2a57dc6ee8a50 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/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_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index 1cecd49aa1424..68ded594813af 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -13,6 +13,7 @@ use rustc_ast_pretty::pp::Printer; use rustc_data_structures::Limit; 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_target::spec::SanitizerSet; @@ -191,7 +192,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_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 09d6290fcd2fc..bb31c3bb54d42 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 b32a23f53f8cc..033dd624de0f3 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1736,11 +1736,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 f4180af492345..99775cfa2fd3d 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/metadata.rs b/compiler/rustc_middle/src/metadata.rs index 0c9b44a93a20e..e60cc51f446d0 100644 --- a/compiler/rustc_middle/src/metadata.rs +++ b/compiler/rustc_middle/src/metadata.rs @@ -2,6 +2,7 @@ use rustc_hir::def::Res; use rustc_macros::{StableHash, TyDecodable, TyEncodable}; use rustc_span::Ident; use rustc_span::def_id::{DefId, ModId}; +use rustc_span::edition::Edition; use smallvec::SmallVec; use crate::ty; @@ -26,6 +27,13 @@ impl Reexport { } } +/// A different item that a module child resolves to before an edition boundary. +#[derive(Clone, Copy, Debug, TyEncodable, TyDecodable, StableHash)] +pub struct EditionRedirect { + pub before: 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. @@ -43,6 +51,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 54d17e4cd7ff7..4ceb88e6ae012 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -16,8 +16,8 @@ use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, msg}; use rustc_feature::BUILTIN_ATTRIBUTE_MAP; use rustc_hir::attrs::diagnostic::Directive; 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; @@ -229,6 +229,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 @@ -411,6 +414,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 b0faff303d523..209c97c81d9ba 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -12,6 +12,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 ad00003af9482..b4754cf793f87 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; @@ -381,13 +382,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. @@ -397,6 +400,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 { + before: redirect.before, + // 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), @@ -406,6 +435,7 @@ 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), @@ -543,10 +573,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), @@ -566,7 +598,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(|this, ns| { let key = BindingKey::new(IdentKey::new(target), ns); this.resolution_or_default(current_module.to_module(), key, target.span) @@ -732,13 +767,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 = @@ -1039,6 +1105,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), @@ -1171,6 +1238,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))), @@ -1349,6 +1417,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/ident.rs b/compiler/rustc_resolve/src/ident.rs index 2a1b208f94c34..66776a1d64678 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -713,7 +713,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } Scope::MacroUsePrelude => match self.macro_use_prelude.get(&ident.name).cloned() { - Some(decl) => Ok(decl), + Some(decl) => Ok(self.edition_adjusted_decl(decl, orig_ident_span)), None => { Err(Determinacy::determined(!self.graph_root.has_unexpanded_invocations(&self))) } @@ -1112,7 +1112,16 @@ 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.filter(|b| Some(*b) != ignore_decl).map(|binding| { + // This check is redundant with the one inside + // edition_adjusted_decl, but this is a hot path and we want to + // avoid the call if it isn't necessary. + if !binding.edition_redirects.is_empty() { + self.edition_adjusted_decl(binding, orig_ident_span) + } else { + binding + } + }); if let Some(finalize) = finalize { return self.get_mut().finalize_module_binding( diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 499f9ea297362..3f2d2c8bb38dd 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -4,13 +4,16 @@ 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, PartialRes}; use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap}; -use rustc_middle::metadata::{AmbigModChild, ModChild, Reexport}; +use rustc_middle::metadata::{ + AmbigModChild, EditionRedirect as MetadataEditionRedirect, ModChild, Reexport, +}; use rustc_middle::span_bug; use rustc_middle::ty::Visibility; use rustc_session::diagnostics::feature_err; @@ -22,6 +25,7 @@ use rustc_session::lint::builtin::{ 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 @@ -460,7 +468,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { /// Given an import and the declaration that it points to, /// create the corresponding import declaration. - pub(crate) fn new_import_decl(&self, decl: Decl<'ra>, import: Import<'ra>) -> Decl<'ra> { + pub(crate) fn new_import_decl(&self, mut decl: Decl<'ra>, import: Import<'ra>) -> Decl<'ra> { + // Without `edition_redirect`, the first import consumes the redirect + // using that import's edition. The resulting binding is fixed for + // downstream users. + decl = self.edition_adjusted_decl(decl, import.span); + let vis = self.import_decl_vis(decl, import.summary()); if let ImportKind::Glob { ref max_vis, .. } = import.kind @@ -471,6 +484,38 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { max_vis.set(Some(vis), self) } + // Imports in crates with `edition_redirect` preserve redirects. Wrap + // each target in this import so a downstream edition adjustment retains + // its visibility and re-export provenance. Other crates consume the + // redirects above and produce re-exports with no redirects. + let edition_redirects = if decl.edition_redirects.is_empty() { + // Fast path when there are no edition redirects. + &[] + } else { + let edition_redirects = if self.features.edition_redirect() { + decl.edition_redirects + .iter() + .map(|redirect| crate::EditionRedirectDecl { + before: redirect.before, + target: self.arenas.alloc_decl(DeclData { + kind: DeclKind::Import { source_decl: redirect.target, import }, + ambiguity: CmCell::new(None), + span: import.span, + initial_vis: vis.to_mod_id(), + ambiguity_vis_max: CmCell::new(None), + ambiguity_vis_min: CmCell::new(None), + expansion: import.parent_scope.expansion, + parent_module: Some(import.parent_scope.module), + edition_redirects: &[], + }), + }) + .collect::>() + } else { + SmallVec::new() + }; + self.arenas.alloc_edition_redirects(&edition_redirects) + }; + self.arenas.alloc_decl(DeclData { kind: DeclKind::Import { source_decl: decl, import }, ambiguity: CmCell::new(None), @@ -480,6 +525,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, }) } @@ -588,7 +634,15 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { glob_decl.ambiguity.set(Some((old_ambig, true)), self); } glob_decl - } else if glob_decl.res() != old_glob_decl.res() { + } else if glob_decl.res() != old_glob_decl.res() + || (!(old_glob_decl.edition_redirects.is_empty() + && glob_decl.edition_redirects.is_empty()) + && !Self::same_edition_redirects(old_glob_decl, glob_decl)) + { + // Redirects are part of a binding's behavior. If two globs resolve + // to the same item but redirect differently, retaining either + // declaration would make the result depend on glob insertion order, + // so keep an ambiguity witness just as we do for distinct `Res`s. let warning = self.is_noise_0_7_0(old_glob_decl, glob_decl) || self.is_rustybuzz_0_4_0(old_glob_decl, glob_decl) || self.is_pdf_0_9_0(old_glob_decl, glob_decl) @@ -619,6 +673,27 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } + /// Whether two declarations imported from other crates have the same + /// edition-dependent behavior. + /// + /// Redirects declared in the current crate are intentionally not + /// considered: they affect only the metadata produced for downstream + /// crates, not local name resolution. + fn same_edition_redirects(decl1: Decl<'ra>, decl2: Decl<'ra>) -> bool { + // Fast path when there are no edition redirects. + if decl1.edition_redirects.is_empty() && decl2.edition_redirects.is_empty() { + return true; + } + + decl1.edition_redirects.len() == decl2.edition_redirects.len() + && decl1.edition_redirects.iter().zip(decl2.edition_redirects).all( + |(redirect1, redirect2)| { + redirect1.before == redirect2.before + && redirect1.target.res() == redirect2.target.res() + }, + ) + } + /// Attempt to put the declaration with the given name and namespace into the module, /// and return existing declaration if there is a collision. pub(crate) fn try_plant_decl_into_local_module( @@ -733,6 +808,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| { @@ -837,7 +917,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| { @@ -856,17 +936,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), + before: redirect.before, + 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(), @@ -921,10 +1021,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; @@ -1809,7 +1912,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .filter_map(|(key, resolution)| { let res = resolution.borrow(self); - let decl = res.determined_decl()?; + let decl = self.edition_adjusted_decl(res.determined_decl()?, import.span); let mut key = *key; let scope = match key.ident.ctxt.update_unchecked(|ctxt| { ctxt.reverse_glob_adjust(module.expansion, import.span) @@ -1865,6 +1968,131 @@ 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].before); + for &[previous, redirect] in indices.array_windows() { + let previous = &self.local_edition_redirects[previous]; + let redirect = &self.local_edition_redirects[redirect]; + if previous.before == redirect.before && diagnosed_duplicate.insert(redirect.span) { + self.dcx().span_err( + redirect.span, + format!( + "multiple edition redirects before edition {} for `{}`", + redirect.before, 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, + mut decl: Decl<'ra>, + ) -> SmallVec<[MetadataEditionRedirect; 1]> { + let original_decl = decl; + if !self.local_edition_redirects.is_empty() { + // Redirects declared in this crate are attached to their default + // declaration. Follow the import chain to preserve them through + // named and glob re-exports. + loop { + // A module child can be wrapped in one or more + // `DeclKind::Import`s. Prefer an attribute on a re-export + // itself; otherwise continue to the item at the end of the + // import chain. + let mut redirects = self + .local_edition_redirects + .iter() + .filter(|redirect| redirect.default_decl == Some(decl)) + .collect::>(); + if !redirects.is_empty() { + redirects.sort_by_key(|redirect| redirect.before); + return redirects + .into_iter() + .map(|redirect| MetadataEditionRedirect { + before: redirect.before, + target: redirect.import_decl.res().expect_non_local(), + }) + .collect(); + } + + let DeclKind::Import { source_decl, .. } = decl.kind else { break }; + decl = source_decl; + } + } + + // Otherwise use the fully resolved redirects already attached to the + // original declaration. + original_decl + .edition_redirects + .iter() + .map(|redirect| MetadataEditionRedirect { + before: redirect.before, + target: redirect.target.res().expect_non_local(), + }) + .collect() + } + // Miscellaneous post-processing, including recording re-exports, // reporting conflicts, and reporting unresolved imports. fn finalize_resolutions_in( @@ -1890,18 +2118,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: this.edition_redirects_for_decl(ambig_binding1), + }; let second = ModChild { ident, res: ambig_binding2.res().expect_non_local(), vis: ambig_binding2.vis(), reexport_chain: ambig_binding2.reexport_chain(), + edition_redirects: this.edition_redirects_for_decl(ambig_binding2), }; 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/lib.rs b/compiler/rustc_resolve/src/lib.rs index b7e57ad8ec37e..0548b3228ab6f 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -72,6 +72,7 @@ use rustc_middle::{bug, span_bug}; use rustc_session::config::CrateType; use rustc_session::lint::builtin::PRIVATE_MACRO_USE; 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 smallvec::{SmallVec, smallvec}; @@ -1017,6 +1018,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> { + before: 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, + before: 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 @@ -1370,6 +1390,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 @@ -1580,6 +1602,7 @@ impl<'ra> ResolverArenas<'ra> { span, expansion, parent_module, + edition_redirects: &[], }) } @@ -1591,6 +1614,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)) @@ -2034,6 +2063,34 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { f(self, MacroNS); } + fn edition_adjusted_decl(&self, decl: Decl<'ra>, span: Span) -> Decl<'ra> { + // Nothing to do if the decl has no redirects. + if decl.edition_redirects.is_empty() { + return decl; + } + + // Crates with `edition_redirect` resolve canonical bindings so + // redirects can be preserved through their re-exports. Other crates + // select using the use-site edition. + if self.features.edition_redirect() { + return decl; + } + + // Glob selection has already determined that this name has multiple + // distinct candidates. Applying only the representative declaration's + // redirect would discard the ambiguity and make the result depend on + // which glob happened to be retained. + if decl.is_ambiguity_recursive() { + return decl; + } + + let edition = span.edition(); + decl.edition_redirects + .iter() + .find(|redirect| edition < redirect.before) + .map_or(decl, |redirect| redirect.target) + } + fn is_builtin_macro(&self, res: Res) -> bool { self.get_macro(res).is_some_and(|ext| ext.builtin_name.is_some()) } 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 ff1d4253c4414..dc9bdd0ea90c7 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -517,6 +517,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, @@ -1808,6 +1810,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 a3617fb6b07c9..cacfe69b58c70 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -512,6 +512,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..c98913604ffb3 --- /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 = "2024"] +pub use Middle as Redirected; +#[rustc_edition_redirect = "2021"] +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 = "2021"] +pub use oldest_module as redirected_module; +#[rustc_edition_redirect = "2024"] +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 = "2021"] +pub use use_targets::OldestUse as RedirectedUse; +#[rustc_edition_redirect = "2024"] +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 = "2024"] + 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 = "2021"] +pub use oldest_macro as redirected_macro; +#[rustc_edition_redirect = "2024"] +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 = "2024"] + pub use super::OldA as Item; + pub use super::Shared as Item; + } + + pub mod alias_b { + #[rustc_edition_redirect = "2024"] + 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/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..07594c1858f89 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs @@ -0,0 +1,11 @@ +//@ edition: 2021 +//@ aux-crate: reexport_source=reexport-source.rs + +#![feature(edition_redirect)] + +pub use reexport_source::Current as Item; +// The module itself is redirected, but its children are not. Canonical path +// resolution in an `edition_redirect` crate therefore finds `Child` in the real +// `redirected_module` and does not attach the module's redirect to this +// re-export. +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..e247b2e584a60 --- /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 = "2024"] +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 = "2024"] +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..fbb2ec1da507c --- /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 = "2024"] +#[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..40c3434d272ad --- /dev/null +++ b/tests/ui/edition-redirect/basic.rs @@ -0,0 +1,64 @@ +//@ 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, +}; + +#[cfg(edition2018)] +use edition_redirect::{ + Oldest as ExpectedRedirected, reexport_scope::Old as ExpectedScopedRedirected, + use_targets::OldestUse as ExpectedRedirectedUse, +}; +#[cfg(edition2021)] +use edition_redirect::{ + Middle as ExpectedRedirected, reexport_scope::Old as ExpectedScopedRedirected, + use_targets::MiddleUse as ExpectedRedirectedUse, +}; +#[cfg(edition2024)] +use edition_redirect::{ + Redirected as ExpectedRedirected, reexport_scope::Current as ExpectedScopedRedirected, + 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 = ExpectedRedirectedUse; + 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/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..8e20f1aa296a1 --- /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 before 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..fad1203d4fcc1 --- /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 before 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/reexport.rs b/tests/ui/edition-redirect/reexport.rs new file mode 100644 index 0000000000000..c110959c48e48 --- /dev/null +++ b/tests/ui/edition-redirect/reexport.rs @@ -0,0 +1,28 @@ +//@ 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() { + // Crates with `edition_redirect` preserve redirects even when the re-export + // itself is written in an older edition. + #[cfg(old)] + let _: reexport_preserving::Item = reexport_source::old(); + #[cfg(current)] + let _: reexport_preserving::Item = reexport_source::current(); + + // Other crates consume a redirect at the first `use`, fixing the re-export + // to the item selected by that import's edition. + 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_preserving::Child = reexport_source::current_child(); + 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() {}