diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index de4f9f63a51fe..8549fd145818f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -2,13 +2,15 @@ use std::path::PathBuf; use rustc_ast::{LitIntType, LitKind, MetaItemLit}; 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_hir::{LangItem, find_attr}; use rustc_span::Symbol; +use rustc_span::edition::Edition; use super::prelude::*; use super::util::parse_single_integer; @@ -329,6 +331,118 @@ impl AttributeParser for RustcCguTestAttributeParser { } } +pub(crate) struct RustcEditionRedirectParser; + +impl CombineAttributeParser for RustcEditionRedirectParser { + const PATH: &[Symbol] = &[sym::rustc_edition_redirect]; + type Item = EditionRedirect; + const CONVERT: ConvertFn = |items, _| AttributeKind::RustcEditionRedirect(items); + const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[ + Allow(Target::Const), + Allow(Target::Enum), + Allow(Target::ExternCrate), + Allow(Target::Fn), + Allow(Target::MacroDef), + Allow(Target::Mod), + Allow(Target::Static), + Allow(Target::Struct), + Allow(Target::Trait), + Allow(Target::TraitAlias), + Allow(Target::TyAlias), + Allow(Target::Union), + Allow(Target::Use), + ]); + const TEMPLATE: AttributeTemplate = + template!(List: &[r#"before = "2024", target(path::to::item)"#]); + const STABILITY: AttributeStability = unstable!(edition_redirect); + + fn extend( + cx: &mut AcceptContext<'_, '_>, + args: &ArgParser, + ) -> impl IntoIterator { + let list = cx.expect_list(args, cx.attr_span)?; + let mut before = None; + let mut target = None; + + for item in list.mixed() { + let Some(meta) = item.meta_item() else { + cx.adcx().expected_identifier(item.span()); + return None; + }; + let Some(name) = meta.path().word() else { + cx.adcx() + .expected_specific_argument(meta.path().span(), &[sym::before, sym::target]); + return None; + }; + match name.name { + sym::before if before.is_none() => { + let Some(value) = + cx.expect_name_value(meta.args(), item.span(), Some(sym::before)) + else { + return None; + }; + let Some(value) = cx.expect_string_literal(value) else { return None }; + before = Some(value); + } + sym::target if target.is_none() => { + let Some(single) = cx.expect_single_element_list(meta.args(), item.span()) + else { + return None; + }; + let Some(target_path) = single.meta_item_no_args() else { + cx.adcx().expected_not_literal(single.span()); + return None; + }; + target = Some(target_path.path().0.clone()); + } + sym::before | sym::target => { + cx.adcx().duplicate_key(item.span(), name.name); + return None; + } + _ => { + cx.adcx().expected_specific_argument(item.span(), &[sym::before, sym::target]); + return None; + } + } + } + + let Some(before) = before else { + cx.dcx().span_err(cx.attr_span, "missing `before` argument"); + return None; + }; + let Some(target) = target else { + cx.dcx().span_err(cx.attr_span, "missing `target` argument"); + return None; + }; + let before = match before.as_str().parse::() { + Ok(before) => before, + Err(()) => { + cx.dcx().span_err(cx.attr_span, "invalid edition in edition redirect"); + return None; + } + }; + + Some(EditionRedirect { before, target, span: cx.attr_span }) + } + + fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, _attr_span: Span) { + let redirects = find_attr!( + cx.parsed_attrs, + RustcEditionRedirect(redirects) => redirects + ) + .unwrap(); + + for (index, redirect) in redirects.iter().enumerate() { + if redirects[..index].iter().any(|existing| existing.before == redirect.before) { + cx.emit_err(diagnostics::MultipleEditionRedirects { + span: redirect.span, + edition: redirect.before.to_string(), + }); + } + } + } +} + 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 6254dd73f3263..d759c27de7c63 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -193,6 +193,7 @@ attribute_parsers!( Combine, Combine, Combine, + Combine, Combine, Combine, Combine, diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index 171c34232411b..8d133a29e9be1 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -52,6 +52,14 @@ pub(crate) struct UnreachableCfgSelectPredicateWildcard { pub wildcard_span: Span, } +#[derive(Diagnostic)] +#[diag("multiple edition redirects before edition {$edition}")] +pub(crate) struct MultipleEditionRedirects { + #[primary_span] + pub span: Span, + pub edition: String, +} + #[derive(Diagnostic)] #[diag("must be a name of an associated function")] pub(crate) struct MustBeNameOfAssociatedFunction { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 1f6f97f1310ae..b939b4ca30aea 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 165f06d2fde8b..cbebe85446ba6 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,13 @@ pub enum InstrumentFnAttr { Off, } +#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] +pub struct EditionRedirect { + pub before: Edition, + pub target: Path, + pub span: Span, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Default, PrintAttribute)] #[derive(Encodable, Decodable, StableHash)] pub enum OptimizeAttr { @@ -1477,6 +1485,9 @@ pub enum AttributeKind { /// Represents `#[rustc_dyn_incompatible_trait]`. RustcDynIncompatibleTrait(Span), + /// Represents `#[rustc_edition_redirect(before = "...", target(...))]`. + RustcEditionRedirect(ThinVec), + /// 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 dded70ccd08ef..ea5846a4f9b7c 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -144,6 +144,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 2c59b10fd8be6..64f5f4b59fe35 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 a55d38251c843..2c4de02df801d 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -17,8 +17,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; @@ -230,6 +230,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::Linkage(_linkage, span) => { self.check_linkage(*span, hir_id, target, item) } + AttributeKind::RustcEditionRedirect(redirects) => { + self.check_rustc_edition_redirect(item, redirects) + } // All of the following attributes have no specific checks. // tidy-alphabetical-start @@ -411,6 +414,20 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + /// Rejects the use of edition redirect on non-single use statements. + fn check_rustc_edition_redirect(&self, item: Option<&Item<'_>>, redirects: &[EditionRedirect]) { + let Some(Item { kind: ItemKind::Use(_, use_kind), .. }) = item else { + return; + }; + if matches!(use_kind, hir::UseKind::Single(_)) { + return; + } + if let Some(redirect) = redirects.first() { + 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 61a32c97b3cc6..82416b876df8a 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..c9f3c157bc2ae 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -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; @@ -40,9 +41,9 @@ 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, + ExternPreludeEntry, Finalize, IdentKey, LocalEditionRedirect, LocalModule, Module, ModuleKind, + ModuleOrUniformRoot, ParentScope, PathResult, Res, ResolutionTable, Resolver, Segment, Used, + VisResolutionError, diagnostics, }; impl<'ra, 'tcx> Resolver<'ra, 'tcx> { @@ -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), @@ -1406,6 +1436,40 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { pub(crate) fn brg_visit_item(&mut self, item: &'a Item, feed: TyCtxtFeed<'tcx, LocalDefId>) { + // The resolver runs before these attributes are available through HIR. + // Parse and retain them here, but leave their target paths unresolved + // until ordinary imports have settled. + if ast::attr::contains_name(&item.attrs, sym::rustc_edition_redirect) + && let Some(Attribute::Parsed(AttributeKind::RustcEditionRedirect(mut redirects))) = + AttributeParser::parse_limited_sym( + self.r.tcx.sess, + &item.attrs, + &[sym::rustc_edition_redirect], + ) + { + redirects.sort_by_key(|redirect| redirect.before); + let edition_redirects = redirects + .into_iter() + .map(|redirect| LocalEditionRedirect { + before: redirect.before, + // Attribute path segments have dummy node IDs and are not lowered as paths, so + // resolution must not try to record per-segment results for them. + target: Segment::from_path(&redirect.target) + .into_iter() + .map(|mut segment| { + segment.id = None; + segment + }) + .collect(), + // Attribute paths use the lexical and hygiene context of the marked item. + parent_scope: self.parent_scope, + node_id: item.id, + span: redirect.span, + }) + .collect(); + self.r.local_edition_redirects.insert(feed.key(), edition_redirects); + } + let orig_module_scope = self.parent_scope.module; self.parent_scope.macro_rules = match item.kind { ItemKind::MacroDef(..) => { diff --git a/compiler/rustc_resolve/src/diagnostics/mod.rs b/compiler/rustc_resolve/src/diagnostics/mod.rs index cadfab22c8862..80d031108be39 100644 --- a/compiler/rustc_resolve/src/diagnostics/mod.rs +++ b/compiler/rustc_resolve/src/diagnostics/mod.rs @@ -824,6 +824,20 @@ pub(crate) struct CannotBeReexportedCratePublicNS { pub(crate) ident: Ident, } +#[derive(Diagnostic)] +#[diag("edition redirect target `{$target}` is less visible than the redirected item")] +#[help("make the target at least as visible as the redirected item")] +pub(crate) struct EditionRedirectTargetLessVisible { + #[primary_span] + #[label("target has more restricted visibility")] + pub(crate) target_span: Span, + #[label("target is defined here")] + pub(crate) target_definition_span: Span, + #[label("redirected item is defined here")] + pub(crate) redirected_item_span: Span, + pub(crate) target: String, +} + #[derive(Diagnostic)] #[diag("extern crate `{$ident}` is private and cannot be re-exported", code = E0365)] pub(crate) struct PrivateExternCrateReexport { diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 3f34af1d01d83..3026e08f3af4b 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())), }, Scope::BuiltinAttrs => match self.builtin_attr_decls.get(&ident.name) { @@ -1114,7 +1114,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 6e2ea9abf2de8..e9e98cf10fb6a 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -10,7 +10,9 @@ use rustc_errors::{Applicability, BufferedEarlyLint, Diagnostic}; use rustc_expand::base::SyntaxExtensionKind; 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 +24,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, *}; @@ -460,7 +463,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 +479,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 +520,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 +629,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 +668,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( @@ -915,8 +985,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { pub(crate) fn finalize_imports(&mut self) { 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; @@ -1801,7 +1872,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .filter_map(|(key, resolution)| { let res = resolution.borrow(); - 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) @@ -1857,10 +1928,144 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { false } + /// Finds edition redirects declared in the current crate that apply to + /// `decl`. + /// + /// Import declarations are followed toward their source, with an attribute + /// on a re-export taking precedence over one on the re-exported item. + /// Constructor declarations are mapped back to their parent ADT, which is + /// where the attribute is stored. The redirects are cloned so the caller + /// can use the resolver mutably while resolving their targets. Returns + /// `None` for external declarations, whose resolved redirects are stored + /// directly in `DeclData`. + fn local_edition_redirects( + &self, + mut decl: Decl<'ra>, + ) -> Option>> { + // 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. + loop { + match decl.kind { + DeclKind::Import { source_decl, import } => { + if let Some(def_id) = import.def_id() + && let Some(redirects) = self.local_edition_redirects.get(&def_id) + { + return Some(redirects.clone()); + } + decl = source_decl; + } + DeclKind::Def(Res::Def(_, def_id)) => { + let Some(mut def_id) = def_id.as_local() else { return None }; + + // Tuple and unit structs also export a value-namespace + // constructor, but the attribute belongs to the parent + // struct item. + if matches!(self.tcx.def_kind(def_id), DefKind::Ctor(..)) { + def_id = self.tcx.local_parent(def_id); + } + return self.local_edition_redirects.get(&def_id).cloned(); + } + DeclKind::Def(_) => return None, + } + } + } + + /// Resolves local redirect targets after ordinary imports have settled and + /// once the namespace exported by `decl` is known. + fn resolve_local_edition_redirects( + &mut self, + ns: Namespace, + decl: Decl<'ra>, + redirects: Vec>, + ) -> SmallVec<[MetadataEditionRedirect; 1]> { + redirects + .into_iter() + .filter_map(|redirect| { + let target_path = Segment::names_to_string(&redirect.target); + let target_span = + redirect.target[0].ident.span.to(redirect.target.last().unwrap().ident.span); + let target = match self.cm_mut().resolve_path( + &redirect.target, + Some(ns), + &redirect.parent_scope, + Some(Finalize::new(redirect.node_id, redirect.span)), + None, + None, + ) { + PathResult::Module(ModuleOrUniformRoot::Module(module)) => module.res(), + PathResult::NonModule(partial_res) => partial_res.full_res(), + PathResult::Failed { + span, + label, + suggestion, + module, + segment, + message, + note, + .. + } => { + let mut err = self.into_struct_error( + span, + ResolutionError::FailedToResolve { + segment: segment.name, + label, + suggestion, + module, + message, + }, + ); + if let Some(note) = note { + err.note(note); + } + err.note(format!( + "while resolving edition redirect target `{target_path}`" + )); + err.emit(); + return None; + } + PathResult::Module(_) => None, + PathResult::Indeterminate => { + unreachable!("finalized edition redirect resolution was indeterminate") + } + }; + let Some(target) = target else { + self.dcx().span_err( + target_span, + format!( + "edition redirect target `{target_path}` does not resolve to a module item" + ), + ); + return None; + }; + if let Some(target_def_id) = target.opt_def_id() + && matches!( + self.tcx.visibility(target_def_id).partial_cmp(decl.vis(), self.tcx), + None | Some(Ordering::Less) + ) + { + let source_map = self.tcx.sess.source_map(); + self.dcx().emit_err(diagnostics::EditionRedirectTargetLessVisible { + target_span, + target_definition_span: source_map + .guess_head_span(self.def_span(target_def_id)), + redirected_item_span: source_map.guess_head_span(decl.span), + target: target_path, + }); + return None; + } + Some(MetadataEditionRedirect { + before: redirect.before, + target: target.expect_non_local(), + }) + }) + .collect() + } + // Miscellaneous post-processing, including recording re-exports, // reporting conflicts, and reporting unresolved imports. fn finalize_resolutions_in( - &self, + &mut self, module: LocalModule<'ra>, module_children: &mut LocalDefIdMap>, ambig_module_children: &mut LocalDefIdMap>, @@ -1870,10 +2075,16 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let Some(def_id) = module.opt_def_id() else { return }; + enum EditionRedirectSlot { + Child(usize), + AmbiguousMain(usize), + AmbiguousSecond(usize), + } + let mut children = Vec::new(); let mut ambig_children = Vec::new(); - - module.to_module().for_each_child(self, |this, ident, orig_ident_span, _, decl| { + let mut pending_edition_redirects = Vec::new(); + module.to_module().for_each_child(self, |this, ident, orig_ident_span, ns, decl| { let res = decl.res().expect_non_local(); if res != def::Res::Err { let vis = if this.rust_embed_hack(module, decl) { @@ -1882,22 +2093,84 @@ 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 }; + let mut edition_redirects = |decl, child| { + // Local redirect targets must be resolved after + // `for_each_child` releases its borrow. Otherwise copy any + // already-resolved redirects decoded from external crate + // metadata. + if !this.local_edition_redirects.is_empty() + && let Some(redirects) = this.local_edition_redirects(decl) + { + pending_edition_redirects.push((child, ns, decl, redirects)); + SmallVec::new() + } else if decl.edition_redirects.is_empty() { + SmallVec::new() + } else { + decl.edition_redirects + .iter() + .map(|redirect| MetadataEditionRedirect { + before: redirect.before, + target: redirect.target.res().expect_non_local(), + }) + .collect() + } + }; if let Some((ambig_binding1, ambig_binding2)) = decl.descent_to_ambiguity() { - let main = child(ambig_binding1.reexport_chain()); + let index = ambig_children.len(); + let main = ModChild { + ident, + res, + vis, + reexport_chain: ambig_binding1.reexport_chain(), + edition_redirects: edition_redirects( + ambig_binding1, + EditionRedirectSlot::AmbiguousMain(index), + ), + }; let second = ModChild { ident, res: ambig_binding2.res().expect_non_local(), vis: ambig_binding2.vis(), reexport_chain: ambig_binding2.reexport_chain(), + edition_redirects: edition_redirects( + ambig_binding2, + EditionRedirectSlot::AmbiguousSecond(index), + ), }; ambig_children.push(AmbigModChild { main, second }) } else { - children.push(child(decl.reexport_chain())); + let index = children.len(); + children.push(ModChild { + ident, + res, + vis, + reexport_chain: decl.reexport_chain(), + edition_redirects: edition_redirects( + decl, + EditionRedirectSlot::Child(index), + ), + }); } } }); + // Resolving a target can record import uses and diagnostics, so wait + // until `for_each_child` releases its borrow of the module resolutions. + for (child, ns, decl, redirects) in pending_edition_redirects { + let redirects = self.resolve_local_edition_redirects(ns, decl, redirects); + match child { + EditionRedirectSlot::Child(index) => { + children[index].edition_redirects = redirects; + } + EditionRedirectSlot::AmbiguousMain(index) => { + ambig_children[index].main.edition_redirects = redirects; + } + EditionRedirectSlot::AmbiguousSecond(index) => { + ambig_children[index].second.edition_redirects = redirects; + } + } + } + if !children.is_empty() { module_children.insert(def_id.expect_local(), children); } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index a3c804e56ee22..e97b705e160db 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}; @@ -1016,6 +1017,28 @@ 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 redirect declared in the current crate, before it is resolved and converted to crate metadata. +/// Redirects for each item are stored in ascending order of `before`. +#[derive(Clone)] +struct LocalEditionRedirect<'ra> { + before: Edition, + /// The parsed target path, resolved when module children are finalized. + target: Vec, + /// The scope of the item carrying the attribute, in which `target` must be resolved. + parent_scope: ParentScope<'ra>, + /// The item carrying the attribute, used when finalizing target-path diagnostics. + node_id: NodeId, + span: Span, } /// `Interned` is used because values of this type have "identity" and compare as unequal even if @@ -1368,6 +1391,11 @@ pub struct Resolver<'ra, 'tcx> { extern_crate_map: UnordMap = Default::default(), module_children: LocalDefIdMap> = Default::default(), ambig_module_children: LocalDefIdMap> = Default::default(), + /// Current-crate redirects indexed by the item carrying the attribute. + /// Their targets are resolved lazily while producing the item's `ModChild`s, + /// after ordinary imports have reached a fixed point. + local_edition_redirects: FxHashMap>> = + default::fx_hash_map(), /// A map from nodes to anonymous modules. /// Anonymous modules are pseudo-modules that are implicitly created around items @@ -1578,6 +1606,7 @@ impl<'ra> ResolverArenas<'ra> { span, expansion, parent_module, + edition_redirects: &[], }) } @@ -1589,6 +1618,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)) @@ -2028,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 a293e106bd914..ae38a2e82bde5 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, @@ -867,6 +868,7 @@ symbols! { dyn_trait, dynamic_no_pic: "dynamic-no-pic", edition_panic, + edition_redirect, effective_target_features, effects, eh_personality, @@ -1804,6 +1806,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 3b50c3f82d575..9e328137e8cea 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..c071d4f896419 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/basic.rs @@ -0,0 +1,90 @@ +#![feature(edition_redirect)] + +pub struct Oldest; +pub struct Middle; + +#[rustc_edition_redirect(before = "2024", target(Middle))] +#[rustc_edition_redirect(before = "2021", target(Oldest))] +pub struct Redirected; + +pub mod oldest_module { + pub const VALUE: usize = 1; +} + +pub mod middle_module { + pub const VALUE: usize = 2; +} + +#[rustc_edition_redirect(before = "2021", target(oldest_module))] +#[rustc_edition_redirect(before = "2024", target(middle_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(before = "2021", target(use_targets::OldestUse))] +#[rustc_edition_redirect(before = "2024", target(use_targets::MiddleUse))] +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(before = "2024", target(OldAlias))] + 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(before = "2021", target(oldest_macro))] +#[rustc_edition_redirect(before = "2024", target(middle_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(before = "2024", target(super::OldA))] + pub use super::Shared as Item; + } + + pub mod alias_b { + #[rustc_edition_redirect(before = "2024", target(super::OldB))] + pub use super::Shared as Item; + } +} 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..bdd86cea697f6 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-source.rs @@ -0,0 +1,33 @@ +//@ edition: 2024 + +#![feature(edition_redirect)] + +pub struct Old; + +#[rustc_edition_redirect(before = "2024", target(Old))] +pub struct Current; + +pub fn old() -> Old { + Old +} + +pub fn current() -> Current { + Current +} + +pub mod old_module { + pub struct Child; +} + +#[rustc_edition_redirect(before = "2024", target(old_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..e90e62ad63f99 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/stability.rs @@ -0,0 +1,17 @@ +#![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(before = "2024", target(old_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..3e5de33cca899 --- /dev/null +++ b/tests/ui/edition-redirect/feature-gate.rs @@ -0,0 +1,11 @@ +// gate-test-edition_redirect + +#![feature(rustc_attrs)] + +struct Old; + +#[rustc_edition_redirect(before = "2024", target(Old))] +//~^ ERROR the `rustc_edition_redirect` attribute is an experimental feature +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..805b5d418a860 --- /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(before = "2024", target(Old))] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = 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..f1bf5cedab6d7 --- /dev/null +++ b/tests/ui/edition-redirect/invalid.rs @@ -0,0 +1,64 @@ +#![feature(edition_redirect)] + +mod source { + pub struct Old; + pub struct Current; +} + +#[rustc_edition_redirect(before = "2024", target(source::Old))] +//~^ ERROR `#[rustc_edition_redirect]` can only be applied to a single import +pub use source::{Current}; + +#[rustc_edition_redirect(before = "2024", target(source::Old))] +//~^ ERROR `#[rustc_edition_redirect]` can only be applied to a single import +pub use source::*; + +mod private { + pub(crate) struct Old { + _private: (), + } +} + +#[rustc_edition_redirect(before = "2024", target(private::Old))] +//~^ ERROR edition redirect target `private::Old` is less visible than the redirected item +pub struct Public { + _private: (), +} + +#[rustc_edition_redirect(before = "2024", target(private::Missing))] +//~^ ERROR cannot find `Missing` in `private` +pub struct Unresolved { + _private: (), +} + +struct DuplicateTarget; + +#[rustc_edition_redirect(before = "2024", target(DuplicateTarget))] +#[rustc_edition_redirect(before = "2024", target(DuplicateTarget))] +//~^ ERROR multiple edition redirects before edition 2024 +struct Duplicate; + +mod ambiguous_target { + mod first { + pub struct Old { + _private: (), + } + } + + mod second { + pub struct Old { + _private: (), + } + } + + use self::first::*; + use self::second::*; + + #[rustc_edition_redirect(before = "2024", target(Old))] + //~^ ERROR `Old` is ambiguous + pub struct Ambiguous { + _private: (), + } +} + +fn main() {} diff --git a/tests/ui/edition-redirect/invalid.stderr b/tests/ui/edition-redirect/invalid.stderr new file mode 100644 index 0000000000000..d56f995d4c3f4 --- /dev/null +++ b/tests/ui/edition-redirect/invalid.stderr @@ -0,0 +1,68 @@ +error: edition redirect target `private::Old` is less visible than the redirected item + --> $DIR/invalid.rs:22:50 + | +LL | pub(crate) struct Old { + | --------------------- target is defined here +... +LL | #[rustc_edition_redirect(before = "2024", target(private::Old))] + | ^^^^^^^^^^^^ target has more restricted visibility +LL | +LL | pub struct Public { + | ----------------- redirected item is defined here + | + = help: make the target at least as visible as the redirected item + +error[E0433]: cannot find `Missing` in `private` + --> $DIR/invalid.rs:28:59 + | +LL | #[rustc_edition_redirect(before = "2024", target(private::Missing))] + | ^^^^^^^ could not find `Missing` in `private` + | + = note: while resolving edition redirect target `private::Missing` + +error[E0659]: `Old` is ambiguous + --> $DIR/invalid.rs:57:54 + | +LL | #[rustc_edition_redirect(before = "2024", target(Old))] + | ^^^ ambiguous name + | + = note: ambiguous because of multiple glob imports of a name in the same module +note: `Old` could refer to the struct imported here + --> $DIR/invalid.rs:54:9 + | +LL | use self::first::*; + | ^^^^^^^^^^^^^^ + = help: consider adding an explicit import of `Old` to disambiguate +note: `Old` could also refer to the struct imported here + --> $DIR/invalid.rs:55:9 + | +LL | use self::second::*; + | ^^^^^^^^^^^^^^^ + = help: consider adding an explicit import of `Old` to disambiguate + +error: multiple edition redirects before edition 2024 + --> $DIR/invalid.rs:37:1 + | +LL | #[rustc_edition_redirect(before = "2024", target(DuplicateTarget))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[rustc_edition_redirect]` can only be applied to a single import + --> $DIR/invalid.rs:8:1 + | +LL | #[rustc_edition_redirect(before = "2024", target(source::Old))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a separate, non-braced `use` item + +error: `#[rustc_edition_redirect]` can only be applied to a single import + --> $DIR/invalid.rs:12:1 + | +LL | #[rustc_edition_redirect(before = "2024", target(source::Old))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a separate, non-braced `use` item + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0433, E0659. +For more information about an error, try `rustc --explain E0433`. 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() {}