diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 81887e0176ee7..f7ba37b6c1d0c 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/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; @@ -153,6 +154,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 { @@ -1481,6 +1488,9 @@ pub enum AttributeKind { /// Represents `#[rustc_dyn_incompatible_trait]`. RustcDynIncompatibleTrait(Span), + /// Represents `#[rustc_edition_redirect = "..."]`. + RustcEditionRedirect(EditionRedirect), + /// Represents `#[rustc_effective_visibility]`. RustcEffectiveVisibility, diff --git a/compiler/rustc_attr_ir/src/encode_cross_crate.rs b/compiler/rustc_attr_ir/src/encode_cross_crate.rs index 6a9f37f80868a..4a2cb223d40f0 100644 --- a/compiler/rustc_attr_ir/src/encode_cross_crate.rs +++ b/compiler/rustc_attr_ir/src/encode_cross_crate.rs @@ -145,6 +145,7 @@ impl AttributeKind { RustcDumpVariancesOfOpaques => No, RustcDumpVtable(..) => No, RustcDynIncompatibleTrait(..) => No, + RustcEditionRedirect(..) => No, RustcEffectiveVisibility => Yes, RustcEiiForeignItem => No, RustcEvaluateWhereClauses => Yes, diff --git a/compiler/rustc_attr_ir/src/pretty_printing.rs b/compiler/rustc_attr_ir/src/pretty_printing.rs index 1cecd49aa1424..68ded594813af 100644 --- a/compiler/rustc_attr_ir/src/pretty_printing.rs +++ b/compiler/rustc_attr_ir/src/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_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 94d7d70d07afc..720a2bf1aaa76 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -5,11 +5,13 @@ use rustc_attr_ir::lang_items::LangItem; use rustc_attr_ir::target::GenericParamKind; use rustc_attr_ir::{ BorrowckGraphvizFormatKind, CguFields, CguKind, DivergingBlockBehavior, - DivergingFallbackBehavior, RustcCleanAttribute, RustcCleanQueries, RustcMirKind, + DivergingFallbackBehavior, EditionRedirect, RustcCleanAttribute, RustcCleanQueries, + RustcMirKind, }; use rustc_data_structures::fx::FxHashMap; use rustc_feature::AttributeStability; use rustc_span::Symbol; +use rustc_span::edition::Edition; use super::prelude::*; use super::util::parse_single_integer; @@ -342,6 +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 73195c7b77b10..aa354dc711ba5 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 9d72bdcb75ce3..c7d8371750e66 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -37,6 +37,13 @@ pub(crate) struct ItemFollowingInnerAttr { pub span: Span, } +#[derive(Diagnostic)] +#[diag("invalid edition in edition redirect")] +pub(crate) struct InvalidEditionRedirect { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("unreachable configuration predicate")] pub(crate) struct UnreachableCfgSelectPredicate { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index bc6f87a2a7f17..8b563e92f343e 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -248,6 +248,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 665661f43006e..4d449023628a1 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_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index f4d3380594c9f..c5afd357647b8 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 228600fa76794..dde5324824706 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1740,11 +1740,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 064d906293ae8..04174d39b2f74 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 e035e16cabb91..ef82dc7b3c2a9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -17,8 +17,8 @@ use rustc_feature::BUILTIN_ATTRIBUTE_MAP; use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::lang_items::LangItem; use rustc_hir::attrs::{ - AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr, - OptimizeAttr, ReprAttr, + AttributeKind, DocAttribute, DocInline, EditionRedirect, EiiDecl, EiiImpl, EiiImplResolution, + InlineAttr, OptimizeAttr, ReprAttr, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModId; @@ -230,6 +230,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 @@ -459,6 +462,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 16a2cc4007318..ecf200bb9fc2e 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..475a9f6e6f8e3 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,13 +435,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { span, expansion, parent_module: Some(parent.to_module()), + edition_redirects, }); - let resolution = self.arenas.alloc_name_resolution(NameResolution { - non_glob_decl: Some(decl), - orig_ident_span, - single_imports: Default::default(), - .. - }); + let resolution = + self.arenas.alloc_name_resolution(NameResolution::new(Some(decl), orig_ident_span)); let key = BindingKey::new_disambiguated(ident, ns, || (child_index + 1).try_into().unwrap()); @@ -543,10 +569,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 +594,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 +763,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 +1101,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 +1234,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))), @@ -1190,7 +1254,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { if let Some(span) = import_all { let import = macro_use_import(self, span, false); self.r.potentially_unused_imports.push(import); - module.for_each_child_mut(self, |this, ident, _, ns, binding| { + module.for_each_child_redir_mut(self, span, |this, ident, _, ns, binding| { if ns == MacroNS { let import = if this.r.is_accessible_from(binding.vis(), this.parent_scope.module) { @@ -1349,6 +1413,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { if is_macro_export { let import = self.r.arenas.alloc_import(ImportData { kind: ImportKind::MacroExport, + edition_redirect: None, root_id: item.id, parent_scope: ParentScope { module: self.r.graph_root.to_module(), diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 149f34cb6c35d..62f0a60be741b 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -763,16 +763,21 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { pub(crate) fn add_module_candidates( &self, module: Module<'ra>, + redirect_span: Span, names: &mut Vec, filter_fn: &impl Fn(Res) -> bool, ctxt: Option, ) { - module.for_each_child(self, |_this, ident, orig_ident_span, _ns, binding| { - let res = binding.res(); - if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { - names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); - } - }); + module.for_each_child_redir( + self, + redirect_span, + |_this, ident, orig_ident_span, _ns, binding| { + let res = binding.res(); + if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { + names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); + } + }, + ); } /// Combines an error with provided span and emits it. @@ -1007,6 +1012,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut local_names = vec![]; self.add_module_candidates( parent_scope.module, + name.span, &mut local_names, &|res| matches!(res, Res::Def(_, _)), None, @@ -1493,7 +1499,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } Scope::ModuleNonGlobs(module, _) => { - this.add_module_candidates(module, suggestions, filter_fn, None); + this.add_module_candidates(module, sp, suggestions, filter_fn, None); } Scope::ModuleGlobs(..) => { // Already handled in `ModuleNonGlobs`. @@ -1535,7 +1541,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope::StdLibPrelude => { if let Some(prelude) = this.prelude { let mut tmp_suggestions = Vec::new(); - this.add_module_candidates(prelude, &mut tmp_suggestions, filter_fn, None); + this.add_module_candidates( + prelude, + sp, + &mut tmp_suggestions, + filter_fn, + None, + ); suggestions.extend( tmp_suggestions .into_iter() @@ -1619,174 +1631,180 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } { let in_module_is_extern = !in_module.def_id().is_local(); - in_module.for_each_child(self, |this, ident, orig_ident_span, ns, name_binding| { - // Avoid non-importable candidates. - if name_binding.is_assoc_item() - && !this.features.import_trait_associated_functions() - { - return; - } + in_module.for_each_child_redir( + self, + lookup_ident.span, + |this, ident, orig_ident_span, ns, name_binding| { + // Avoid non-importable candidates. + if name_binding.is_assoc_item() + && !this.features.import_trait_associated_functions() + { + return; + } - if ident.name == kw::Underscore { - return; - } + if ident.name == kw::Underscore { + return; + } - let child_accessible = - accessible && this.is_accessible_from(name_binding.vis(), parent_scope.module); + let child_accessible = accessible + && this.is_accessible_from(name_binding.vis(), parent_scope.module); - // do not venture inside inaccessible items of other crates - if in_module_is_extern && !child_accessible { - return; - } + // do not venture inside inaccessible items of other crates + if in_module_is_extern && !child_accessible { + return; + } - let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); + let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); - // There is an assumption elsewhere that paths of variants are in the enum's - // declaration and not imported. With this assumption, the variant component is - // chopped and the rest of the path is assumed to be the enum's own path. For - // errors where a variant is used as the type instead of the enum, this causes - // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. - if via_import && name_binding.is_possibly_imported_variant() { - return; - } + // There is an assumption elsewhere that paths of variants are in the enum's + // declaration and not imported. With this assumption, the variant component is + // chopped and the rest of the path is assumed to be the enum's own path. For + // errors where a variant is used as the type instead of the enum, this causes + // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. + if via_import && name_binding.is_possibly_imported_variant() { + return; + } - // #90113: Do not count an inaccessible reexported item as a candidate. - if let DeclKind::Import { source_decl, .. } = name_binding.kind - && this.is_accessible_from(source_decl.vis(), parent_scope.module) - && !this.is_accessible_from(name_binding.vis(), parent_scope.module) - { - return; - } + // #90113: Do not count an inaccessible reexported item as a candidate. + if let DeclKind::Import { source_decl, .. } = name_binding.kind + && this.is_accessible_from(source_decl.vis(), parent_scope.module) + && !this.is_accessible_from(name_binding.vis(), parent_scope.module) + { + return; + } - let res = name_binding.res(); - let did = match res { - Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), - _ => res.opt_def_id(), - }; - let child_doc_visible = doc_visible - && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); - - // collect results based on the filter function - // avoid suggesting anything from the same module in which we are resolving - // avoid suggesting anything with a hygienic name - if ident.name == lookup_ident.name - && ns == namespace - && in_module != parent_scope.module - && ident.ctxt.is_root() - && filter_fn(res) - { - // create the path - let mut segms = if lookup_ident.span.at_least_rust_2018() { - // crate-local absolute paths start with `crate::` in edition 2018 - // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) - crate_path.clone() - } else { - ThinVec::new() + let res = name_binding.res(); + let did = match res { + Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), + _ => res.opt_def_id(), }; - segms.append(&mut path_segments.clone()); + let child_doc_visible = doc_visible + && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); + + // collect results based on the filter function + // avoid suggesting anything from the same module in which we are resolving + // avoid suggesting anything with a hygienic name + if ident.name == lookup_ident.name + && ns == namespace + && in_module != parent_scope.module + && ident.ctxt.is_root() + && filter_fn(res) + { + // create the path + let mut segms = if lookup_ident.span.at_least_rust_2018() { + // crate-local absolute paths start with `crate::` in edition 2018 + // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) + crate_path.clone() + } else { + ThinVec::new() + }; + segms.append(&mut path_segments.clone()); - segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let path = Path { span: name_binding.span, segments: segms }; + segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let path = Path { span: name_binding.span, segments: segms }; - if child_accessible + if child_accessible // Remove invisible match if exists && let Some(idx) = candidates .iter() .position(|v: &ImportSuggestion| v.did == did && !v.accessible) - { - candidates.remove(idx); - } + { + candidates.remove(idx); + } - let is_stable = if is_stable - && let Some(did) = did - && this.is_stable(did, path.span) - { - true - } else { - false - }; + let is_stable = if is_stable + && let Some(did) = did + && this.is_stable(did, path.span) + { + true + } else { + false + }; - // Rreplace unstable suggestions if we meet a new stable one, - // and do nothing if any other situation. For example, if we - // meet `std::ops::Range` after `std::range::legacy::Range`, - // we will remove the latter and then insert the former. - if is_stable - && let Some(idx) = candidates - .iter() - .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) - { - candidates.remove(idx); - } + // Rreplace unstable suggestions if we meet a new stable one, + // and do nothing if any other situation. For example, if we + // meet `std::ops::Range` after `std::range::legacy::Range`, + // we will remove the latter and then insert the former. + if is_stable + && let Some(idx) = candidates + .iter() + .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) + { + candidates.remove(idx); + } - if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { - // See if we're recommending TryFrom, TryInto, or FromIterator and add - // a note about editions - let note = if let Some(did) = did { - let requires_note = !did.is_local() - && find_attr!( - this.tcx, - did, - RustcDiagnosticItem( - sym::TryInto | sym::TryFrom | sym::FromIterator + if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { + // See if we're recommending TryFrom, TryInto, or FromIterator and add + // a note about editions + let note = if let Some(did) = did { + let requires_note = !did.is_local() + && find_attr!( + this.tcx, + did, + RustcDiagnosticItem( + sym::TryInto | sym::TryFrom | sym::FromIterator + ) + ); + requires_note.then(|| { + format!( + "'{}' is included in the prelude starting in Edition 2021", + path_names_to_string(&path) ) - ); - requires_note.then(|| { - format!( - "'{}' is included in the prelude starting in Edition 2021", - path_names_to_string(&path) - ) - }) - } else { - None - }; + }) + } else { + None + }; - candidates.push(ImportSuggestion { - did, - descr: res.descr(), - path, - accessible: child_accessible, - doc_visible: child_doc_visible, - note, - via_import, - is_stable, - }); + candidates.push(ImportSuggestion { + did, + descr: res.descr(), + path, + accessible: child_accessible, + doc_visible: child_doc_visible, + note, + via_import, + is_stable, + }); + } } - } - // collect submodules to explore - if let Some(def_id) = name_binding.res().module_like_def_id() { - // form the path - let mut path_segments = path_segments.clone(); - path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - - let alias_import = if let DeclKind::Import { import, .. } = name_binding.kind - && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind - && import.parent_scope.expansion == parent_scope.expansion - { - true - } else { - false - }; + // collect submodules to explore + if let Some(def_id) = name_binding.res().module_like_def_id() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments + .push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + + let alias_import = if let DeclKind::Import { import, .. } = + name_binding.kind + && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind + && import.parent_scope.expansion == parent_scope.expansion + { + true + } else { + false + }; - let is_extern_crate_that_also_appears_in_prelude = - name_binding.is_extern_crate() && lookup_ident.span.at_least_rust_2018(); - - if !is_extern_crate_that_also_appears_in_prelude || alias_import { - // add the module to the lookup - if seen_modules.insert(def_id) { - if via_import { &mut worklist_via_import } else { &mut worklist }.push( - ( - this.expect_module(def_id), - path_segments, - child_accessible, - child_doc_visible, - is_stable && this.is_stable(def_id, name_binding.span), - ), - ); + let is_extern_crate_that_also_appears_in_prelude = name_binding + .is_extern_crate() + && lookup_ident.span.at_least_rust_2018(); + + if !is_extern_crate_that_also_appears_in_prelude || alias_import { + // add the module to the lookup + if seen_modules.insert(def_id) { + if via_import { &mut worklist_via_import } else { &mut worklist } + .push(( + this.expect_module(def_id), + path_segments, + child_accessible, + child_doc_visible, + is_stable && this.is_stable(def_id, name_binding.span), + )); + } } } - } - }) + }, + ); } candidates @@ -3475,7 +3493,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } let binding_key = BindingKey::new(IdentKey::new(ident), MacroNS); - let binding = self.resolution(crate_module, binding_key)?.best_decl()?; + let binding = self.resolution(crate_module, binding_key)?.best_decl_redir(ident.span)?; let Res::Def(DefKind::Macro(kinds), _) = binding.res() else { return None; }; diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 2a1b208f94c34..deecaea8639f5 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1112,7 +1112,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let resolution = &*self.resolution(module.to_module(), key).ok_or(ControlFlow::Continue(Determined))?; - let binding = resolution.non_glob_decl.filter(|b| Some(*b) != ignore_decl); + let binding = + resolution.non_glob_decl_redir(orig_ident_span).filter(|b| Some(*b) != ignore_decl); if let Some(finalize) = finalize { return self.get_mut().finalize_module_binding( @@ -1152,7 +1153,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let resolution = self.resolution(module.to_module(), key); let binding = - resolution.as_ref().and_then(|r| r.non_glob_decl).filter(|b| Some(*b) != ignore_decl); + resolution.as_ref().and_then(|r| r.non_glob_decl()).filter(|b| Some(*b) != ignore_decl); if let Some(finalize) = finalize { // finalize implies that the module is fully expanded @@ -1397,7 +1398,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { parent_scope: &ParentScope<'ra>, ) -> bool { for single_import in &resolution.single_imports { - if let Some(decl) = resolution.non_glob_decl + if let Some(decl) = resolution.non_glob_decl() && let DeclKind::Import { import, .. } = decl.kind && import == *single_import { diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 499f9ea297362..2a1ec6ed9b05e 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 @@ -266,28 +274,64 @@ impl<'ra> ImportData<'ra> { } } -/// Records information about the resolution of a name in a namespace of a module. -#[derive(Debug)] -pub(crate) struct NameResolution<'ra> { - /// Single imports that may define the name in the namespace. - /// Imports are arena-allocated, so it's ok to use pointers as keys. - pub single_imports: FxIndexSet>, - /// The non-glob declaration for this name, if it is known to exist. - pub non_glob_decl: Option> = None, - /// The glob declaration for this name, if it is known to exist. - pub glob_decl: Option> = None, - pub orig_ident_span: Span, +mod name_resolution { + use super::*; + + /// Records information about the resolution of a name in a namespace of a module. + #[derive(Debug)] + pub(crate) struct NameResolution<'ra> { + /// Single imports that may define the name in the namespace. + /// Imports are arena-allocated, so it's ok to use pointers as keys. + pub single_imports: FxIndexSet>, + /// The non-glob declaration for this name, if it is known to exist. + non_glob_decl: Option>, + /// The glob declaration for this name, if it is known to exist. + pub glob_decl: Option> = None, + pub orig_ident_span: Span, + } + + impl<'ra> NameResolution<'ra> { + pub(crate) fn new(non_glob_decl: Option>, orig_ident_span: Span) -> Self { + NameResolution { + single_imports: FxIndexSet::default(), + non_glob_decl, + orig_ident_span, + .. + } + } + + pub(crate) fn non_glob_decl(&self) -> Option> { + self.non_glob_decl.map(|decl| { + assert!(decl.edition_redirects.is_empty()); + decl + }) + } + + pub(crate) fn non_glob_decl_redir(&self, span: Span) -> Option> { + self.non_glob_decl.map(|decl| { + if decl.edition_redirects.is_empty() { + return decl; + } + let edition = span.edition(); + match decl.edition_redirects.iter().find(|redirect| edition < redirect.before) { + Some(redirect) => redirect.target, + None => decl, + } + }) + } + + pub(super) fn set_non_glob_decl(&mut self, decl: Decl<'ra>) { + self.non_glob_decl = Some(decl); + } + } } +pub(crate) use name_resolution::NameResolution; /// `Interned` is used because values of this type have "identity" and compare as unequal even if /// they have the same contents. pub(crate) type NameResolutionRef<'ra> = Interned<'ra, CmRefCell>>; impl<'ra> NameResolution<'ra> { - pub(crate) fn new(orig_ident_span: Span) -> Self { - NameResolution { single_imports: FxIndexSet::default(), orig_ident_span, .. } - } - /// Returns the best declaration if it is not going to change, and `None` if the best /// declaration may still change to something else. /// FIXME: this function considers `single_imports`, but not `unexpanded_invocations`, so @@ -297,8 +341,19 @@ impl<'ra> NameResolution<'ra> { /// code breakage in practice. /// FIXME: relationship between this function and similar `DeclData::determined` is unclear. pub(crate) fn determined_decl(&self) -> Option> { - if self.non_glob_decl.is_some() { - self.non_glob_decl + if self.non_glob_decl().is_some() { + self.non_glob_decl() + } else if self.glob_decl.is_some() && self.single_imports.is_empty() { + self.glob_decl + } else { + None + } + } + + pub(crate) fn determined_decl_redir(&self, span: Span) -> Option> { + let non_glob_decl = self.non_glob_decl_redir(span); + if non_glob_decl.is_some() { + non_glob_decl } else if self.glob_decl.is_some() && self.single_imports.is_empty() { self.glob_decl } else { @@ -307,7 +362,11 @@ impl<'ra> NameResolution<'ra> { } pub(crate) fn best_decl(&self) -> Option> { - self.non_glob_decl.or(self.glob_decl) + self.non_glob_decl().or(self.glob_decl) + } + + pub(crate) fn best_decl_redir(&self, span: Span) -> Option> { + self.non_glob_decl_redir(span).or(self.glob_decl) } } @@ -480,6 +539,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: &[], }) } @@ -658,10 +718,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None => decl, }); } else { - resolution.non_glob_decl = Some(match resolution.non_glob_decl { + match resolution.non_glob_decl() { Some(old_decl) => return Err(old_decl), - None => decl, - }) + None => resolution.set_non_glob_decl(decl), + } } Ok(()) @@ -733,6 +793,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 +902,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 +921,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 +1006,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; @@ -1016,7 +1104,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Report "cannot reexport" errors for exotic cases involving macros 2.0 // privacy bending or invariant-breaking code under deprecation lints. - for decl in [resolution.non_glob_decl, resolution.glob_decl] { + for decl in [resolution.non_glob_decl(), resolution.glob_decl] { if let Some(decl) = decl && let DeclKind::Import { source_decl, import } = decl.kind // FIXME: Do not check visibility-ambiguous imports for now. To check them @@ -1060,7 +1148,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } if let Some(glob_decl) = resolution.glob_decl - && resolution.non_glob_decl.is_some() + && resolution.non_glob_decl().is_some() { if binding.res() != Res::Err && glob_decl.res() != Res::Err @@ -1499,7 +1587,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } // `use _` is never valid let resolution = resolution.borrow(self); - if let Some(name_binding) = resolution.best_decl() { + if let Some(name_binding) = resolution.best_decl_redir(ident.span) { match name_binding.kind { DeclKind::Import { source_decl, .. } => { match source_decl.kind { @@ -1809,7 +1897,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .filter_map(|(key, resolution)| { let res = resolution.borrow(self); - let decl = res.determined_decl()?; + let decl = res.determined_decl_redir(import.span)?; let mut key = *key; let scope = match key.ident.ctxt.update_unchecked(|ctxt| { ctxt.reverse_glob_adjust(module.expansion, import.span) @@ -1865,6 +1953,103 @@ 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, + decl: Decl<'ra>, + ) -> SmallVec<[MetadataEditionRedirect; 1]> { + let mut redirects = self + .local_edition_redirects + .iter() + .filter(|redirect| redirect.default_decl == Some(decl)) + .collect::>(); + redirects.sort_by_key(|redirect| redirect.before); + redirects + .into_iter() + .map(|redirect| MetadataEditionRedirect { + before: redirect.before, + target: redirect.import_decl.res().expect_non_local(), + }) + .collect() + } + // Miscellaneous post-processing, including recording re-exports, // reporting conflicts, and reporting unresolved imports. fn finalize_resolutions_in( @@ -1890,18 +2075,30 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { decl.vis() }; let ident = ident.orig(orig_ident_span); - let child = |reexport_chain| ModChild { ident, res, vis, reexport_chain }; if let Some((ambig_binding1, ambig_binding2)) = decl.descent_to_ambiguity() { - let main = child(ambig_binding1.reexport_chain()); + let main = ModChild { + ident, + res, + vis, + reexport_chain: ambig_binding1.reexport_chain(), + edition_redirects: Default::default(), + }; let second = ModChild { ident, res: ambig_binding2.res().expect_non_local(), vis: ambig_binding2.vis(), reexport_chain: ambig_binding2.reexport_chain(), + edition_redirects: Default::default(), }; ambig_children.push(AmbigModChild { main, second }) } else { - children.push(child(decl.reexport_chain())); + children.push(ModChild { + ident, + res, + vis, + reexport_chain: decl.reexport_chain(), + edition_redirects: this.edition_redirects_for_decl(decl), + }); } } }); diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index b126272583692..3bb0f2a8549b2 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1162,10 +1162,12 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } fn lookup_doc_alias_name(&mut self, path: &[Segment], ns: Namespace) -> Option<(DefId, Ident)> { - let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item_name: Symbol| { + let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item: Ident| { for resolution in r.resolutions(m).values() { - let Some(did) = - resolution.borrow(r).best_decl().and_then(|binding| binding.res().opt_def_id()) + let Some(did) = resolution + .borrow(r) + .best_decl_redir(item.span) + .and_then(|binding| binding.res().opt_def_id()) else { continue; }; @@ -1176,7 +1178,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { continue; } if let Some(d) = hir::find_attr!(r.tcx, did, Doc(d) => d) - && d.aliases.contains_key(&item_name) + && d.aliases.contains_key(&item.name) { return Some(did); } @@ -1188,7 +1190,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { for rib in self.ribs[ns].iter().rev() { let item = path[0].ident; if let RibKind::Module(module) | RibKind::Block(Some(module)) = rib.kind - && let Some(did) = find_doc_alias_name(self.r, module.to_module(), item.name) + && let Some(did) = find_doc_alias_name(self.r, module.to_module(), item) { return Some((did, item)); } @@ -1212,7 +1214,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let Res::Def(DefKind::Mod, module) = res.expect_full_res() && let module = self.r.expect_module(module) && let item = path[idx + 1].ident - && let Some(did) = find_doc_alias_name(self.r, module, item.name) + && let Some(did) = find_doc_alias_name(self.r, module, item) { return Some((did, item)); } @@ -2922,6 +2924,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let RibKind::Block(Some(module)) = rib.kind { self.r.add_module_candidates( module.to_module(), + segment.ident.span.with_ctxt(ctxt), &mut names, &filter_fn, Some(ctxt), @@ -2954,7 +2957,13 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = self.resolve_path(mod_path, Some(TypeNS), None, PathSource::Type) { - self.r.add_module_candidates(module, &mut names, &filter_fn, None); + self.r.add_module_candidates( + module, + path[path.len() - 1].ident.span, + &mut names, + &filter_fn, + None, + ); } } @@ -3066,7 +3075,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { false } - fn find_module(&self, def_id: DefId) -> Option<(Module<'ra>, ImportSuggestion)> { + fn find_module(&self, def_id: DefId, span: Span) -> Option<(Module<'ra>, ImportSuggestion)> { let mut result = None; let mut seen_modules = FxHashSet::default(); let mut worklist = vec![(self.r.graph_root.to_module(), ThinVec::new(), true)]; @@ -3077,48 +3086,57 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { break; } - in_module.for_each_child(self.r, |r, ident, orig_ident_span, _, name_binding| { - // abort if the module is already found or if name_binding is private external - if result.is_some() || !name_binding.vis().is_visible_locally() { - return; - } - if let Some(module_def_id) = name_binding.res().module_like_def_id() { - // form the path - let mut path_segments = path_segments.clone(); - path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let doc_visible = doc_visible - && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); - if module_def_id == def_id { - let path = Path { span: name_binding.span, segments: path_segments }; - result = Some(( - r.expect_module(module_def_id), - ImportSuggestion { - did: Some(def_id), - descr: "module", - path, - accessible: true, - doc_visible, - note: None, - via_import: false, - is_stable: true, - }, - )); - } else { - // add the module to the lookup - if seen_modules.insert(module_def_id) { - let module = r.expect_module(module_def_id); - worklist.push((module, path_segments, doc_visible)); + in_module.for_each_child_redir( + self.r, + span, + |r, ident, orig_ident_span, _, name_binding| { + // abort if the module is already found or if name_binding is private external + if result.is_some() || !name_binding.vis().is_visible_locally() { + return; + } + if let Some(module_def_id) = name_binding.res().module_like_def_id() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments + .push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let doc_visible = doc_visible + && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); + if module_def_id == def_id { + let path = Path { span: name_binding.span, segments: path_segments }; + result = Some(( + r.expect_module(module_def_id), + ImportSuggestion { + did: Some(def_id), + descr: "module", + path, + accessible: true, + doc_visible, + note: None, + via_import: false, + is_stable: true, + }, + )); + } else { + // add the module to the lookup + if seen_modules.insert(module_def_id) { + let module = r.expect_module(module_def_id); + worklist.push((module, path_segments, doc_visible)); + } } } - } - }); + }, + ); } result } - fn collect_enum_ctors(&self, def_id: DefId) -> Option> { - self.find_module(def_id).map(|(enum_module, enum_import_suggestion)| { + fn collect_enum_ctors( + &self, + def_id: DefId, + span: Span, + ) -> Option> { + self.find_module(def_id, span).map(|(enum_module, enum_import_suggestion)| { let mut variants = Vec::new(); enum_module.for_each_child(self.r, |_, ident, orig_ident_span, _, name_binding| { if let Res::Def(DefKind::Ctor(CtorOf::Variant, kind), def_id) = name_binding.res() { @@ -3140,7 +3158,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { def_id: DefId, span: Span, ) { - let Some(variant_ctors) = self.collect_enum_ctors(def_id) else { + let Some(variant_ctors) = self.collect_enum_ctors(def_id, span) else { err.note("you might have meant to use one of the enum's variants"); return; }; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index b7e57ad8ec37e..831227aa0cf2b 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}; @@ -684,8 +685,14 @@ struct ModuleData<'ra> { globs: CmRefCell>>, /// Used to memoize the traits in this module for faster searches through all traits in scope. + /// + /// The cache is tagged with the edition it was generated with, since redirected trait + /// declarations can select different traits in each edition. traits: CmRefCell< - Option, Option>, bool /* lint ambiguous */)]>>, + Option<( + Edition, + Box<[(Symbol, Decl<'ra>, Option>, bool /* lint ambiguous */)]>, + )>, >, /// Span of the module itself. Used for error reporting. @@ -788,6 +795,7 @@ impl<'ra> ModuleData<'ra> { } impl<'ra> Module<'ra> { + /// Visits children without applying edition redirects. fn for_each_child<'tcx, R: AsRef>>( self, resolver: &R, @@ -801,25 +809,49 @@ impl<'ra> Module<'ra> { } } - fn for_each_child_mut<'tcx, R: AsMut>>( + /// Visits children after applying edition redirects for `redirect_span`. + fn for_each_child_redir<'tcx, R: AsRef>>( + self, + resolver: &R, + redirect_span: Span, + mut f: impl FnMut(&R, IdentKey, Span, Namespace, Decl<'ra>), + ) { + for (key, name_resolution) in resolver.as_ref().resolutions(self).iter() { + let name_resolution = name_resolution.borrow(resolver.as_ref()); + if let Some(decl) = name_resolution.best_decl_redir(redirect_span) { + f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); + } + } + } + + /// Mutable variant of `for_each_child_redir`. + fn for_each_child_redir_mut<'tcx, R: AsMut>>( self, resolver: &mut R, + redirect_span: Span, mut f: impl FnMut(&mut R, IdentKey, Span, Namespace, Decl<'ra>), ) { for (key, name_resolution) in resolver.as_mut().resolutions(self).iter() { let name_resolution = name_resolution.borrow(resolver.as_mut()); - if let Some(decl) = name_resolution.best_decl() { + if let Some(decl) = name_resolution.best_decl_redir(redirect_span) { f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); } } } /// This modifies `self` in place. The traits will be stored in `self.traits`. - fn ensure_traits<'tcx>(self, resolver: &Resolver<'ra, 'tcx>) { + fn ensure_traits<'tcx>(self, resolver: &Resolver<'ra, 'tcx>, redirect_span: Span) { + let edition = redirect_span.edition(); let mut traits = self.traits.borrow_mut(resolver.as_ref()); - if traits.is_none() { + // Macro expansion can cause the same module to be queried with spans from different + // editions. Cache the most recently requested edition and recompute when it changes. + let needs_update = match traits.as_ref() { + Some((cached_edition, _)) => *cached_edition != edition, + None => true, + }; + if needs_update { let mut collected_traits = Vec::new(); - self.for_each_child(resolver, |r, ident, _, ns, mut decl| { + self.for_each_child_redir(resolver, redirect_span, |r, ident, _, ns, mut decl| { if ns != TypeNS { return; } @@ -847,7 +879,7 @@ impl<'ra> Module<'ra> { decl = ambig_decl; } }); - *traits = Some(collected_traits.into_boxed_slice()); + *traits = Some((edition, collected_traits.into_boxed_slice())); } } @@ -1017,6 +1049,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 +1421,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 +1633,7 @@ impl<'ra> ResolverArenas<'ra> { span, expansion, parent_module, + edition_redirects: &[], }) } @@ -1591,6 +1645,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)) @@ -2104,14 +2164,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { cmr.visit_scopes(scope_set, parent_scope, ctxt, sp, None, |mut this, scope, _, _| { match scope { Scope::ModuleNonGlobs(module, _) => { - this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); + this.get_mut().traits_in_module(module, sp, assoc_item, &mut found_traits); } Scope::ModuleGlobs(..) => { // Already handled in `ModuleNonGlobs` (but see #144993). } Scope::StdLibPrelude => { if let Some(module) = this.prelude { - this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); + this.get_mut().traits_in_module(module, sp, assoc_item, &mut found_traits); } } Scope::ExternPreludeItems @@ -2129,14 +2189,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { fn traits_in_module( &mut self, module: Module<'ra>, + redirect_span: Span, assoc_item: Option<(Symbol, Namespace)>, found_traits: &mut Vec>, ) { - module.ensure_traits(self); + module.ensure_traits(self, redirect_span); let traits = module.traits.borrow(self); - for &(trait_name, trait_binding, trait_module, lint_ambiguous) in - traits.as_ref().unwrap().iter() - { + let (_, traits) = traits.as_ref().unwrap(); + for &(trait_name, trait_binding, trait_module, lint_ambiguous) in traits.iter() { if self.trait_may_have_item(trait_module, assoc_item) { let def_id = trait_binding.res().def_id(); let import_ids = self.find_transitive_imports(&trait_binding.kind, trait_name); @@ -2224,7 +2284,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { orig_ident_span: Span, ) -> NameResolutionRef<'ra> { *self.resolutions_mut(module).entry(key).or_insert_with(|| { - self.arenas.alloc_name_resolution(NameResolution::new(orig_ident_span)) + self.arenas.alloc_name_resolution(NameResolution::new(None, orig_ident_span)) }) } diff --git a/compiler/rustc_span/src/edition.rs b/compiler/rustc_span/src/edition.rs index e24e05df113b4..235a0bb97dc8b 100644 --- a/compiler/rustc_span/src/edition.rs +++ b/compiler/rustc_span/src/edition.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use rustc_macros::{BlobDecodable, Encodable, StableHash}; /// The edition of the compiler. (See [RFC 2052](https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md).) -#[derive(Clone, Copy, Hash, PartialEq, PartialOrd, Debug, Encodable, BlobDecodable, Eq)] +#[derive(Clone, Copy, Hash, PartialEq, PartialOrd, Debug, Encodable, BlobDecodable, Eq, Ord)] #[derive(StableHash)] pub enum Edition { // When adding new editions, be sure to do the following: diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 45df107bf7469..3f0e5cdb2b188 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -519,6 +519,7 @@ symbols! { await_macro, backchain, backend_repr, + before, begin_panic, bench, bevy_ecs, @@ -871,6 +872,7 @@ symbols! { dyn_trait, dynamic_no_pic: "dynamic-no-pic", edition_panic, + edition_redirect, effective_target_features, effects, eh_personality, @@ -1812,6 +1814,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/macro-2018.rs b/tests/ui/edition-redirect/auxiliary/macro-2018.rs new file mode 100644 index 0000000000000..307240090c352 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-2018.rs @@ -0,0 +1,37 @@ +//@ edition: 2018 + +#[macro_export] +macro_rules! import_all { + () => { + use macro_source::*; + }; +} + +#[macro_export] +macro_rules! macro_use_source { + () => { + #[macro_use] + extern crate macro_source; + }; +} + +#[macro_export] +macro_rules! call_redirected_trait { + () => { + ().redirected_method() + }; +} + +#[macro_export] +macro_rules! redirected_type { + () => { + RedirectedItem + }; +} + +#[macro_export] +macro_rules! redirected_value { + () => { + RedirectedItem + }; +} diff --git a/tests/ui/edition-redirect/auxiliary/macro-2024.rs b/tests/ui/edition-redirect/auxiliary/macro-2024.rs new file mode 100644 index 0000000000000..8eebf7d35ac27 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-2024.rs @@ -0,0 +1,37 @@ +//@ edition: 2024 + +#[macro_export] +macro_rules! import_all { + () => { + use macro_source::*; + }; +} + +#[macro_export] +macro_rules! macro_use_source { + () => { + #[macro_use] + extern crate macro_source; + }; +} + +#[macro_export] +macro_rules! call_redirected_trait { + () => { + ().redirected_method() + }; +} + +#[macro_export] +macro_rules! redirected_type { + () => { + RedirectedItem + }; +} + +#[macro_export] +macro_rules! redirected_value { + () => { + RedirectedItem + }; +} diff --git a/tests/ui/edition-redirect/auxiliary/macro-source.rs b/tests/ui/edition-redirect/auxiliary/macro-source.rs new file mode 100644 index 0000000000000..154002b36085b --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-source.rs @@ -0,0 +1,96 @@ +//@ edition: 2024 + +#![feature(edition_redirect)] +#![allow(internal_features)] + +pub struct Old; +pub struct Current; + +#[rustc_edition_redirect = "2021"] +pub use Old as Name; +pub use Current as Name; + +mod diagnostic_targets { + #[doc(alias = "OldAlias")] + pub struct AliasCarrier; + + pub trait Candidate {} + + pub mod diagnostic_module { + pub enum DiagnosticEnum { + Variant(u8), + } + } +} + +#[rustc_edition_redirect = "2021"] +pub use diagnostic_targets::AliasCarrier as AliasCarrier; +pub struct AliasCarrier; + +#[rustc_edition_redirect = "2021"] +pub use diagnostic_targets::Candidate as Candidate; +pub struct Candidate; + +#[rustc_edition_redirect = "2021"] +pub use diagnostic_targets::diagnostic_module as diagnostic_module; +pub mod diagnostic_module { + pub enum DiagnosticEnum { + CurrentVariant(u8), + } +} + +pub mod trait_prelude { + pub struct OldItem; + pub struct CurrentItem; + + #[rustc_edition_redirect = "2021"] + pub use OldItem as RedirectedItem; + pub use CurrentItem as RedirectedItem; + + pub struct OldMarker; + pub struct CurrentMarker; + + mod old { + pub trait RedirectedTrait { + fn redirected_method(&self) -> super::OldMarker; + } + + impl RedirectedTrait for () { + fn redirected_method(&self) -> super::OldMarker { + super::OldMarker + } + } + } + + #[rustc_edition_redirect = "2021"] + pub use old::RedirectedTrait as RedirectedTrait; + + pub trait RedirectedTrait { + fn redirected_method(&self) -> CurrentMarker; + } + + impl RedirectedTrait for () { + fn redirected_method(&self) -> CurrentMarker { + CurrentMarker + } + } +} + +#[macro_export] +macro_rules! old_macro { + () => { + pub type Selected = $crate::Old; + }; +} + +#[rustc_edition_redirect = "2021"] +pub use old_macro as redirected_macro; + +#[macro_export] +macro_rules! redirected_macro { + () => { + pub type Selected = $crate::Current; + }; +} + +pub mod nested {} diff --git a/tests/ui/edition-redirect/auxiliary/reexport-current.rs b/tests/ui/edition-redirect/auxiliary/reexport-current.rs new file mode 100644 index 0000000000000..c65a8196383ac --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-current.rs @@ -0,0 +1,5 @@ +//@ edition: 2024 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-old.rs b/tests/ui/edition-redirect/auxiliary/reexport-old.rs new file mode 100644 index 0000000000000..9df0165313ea0 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-old.rs @@ -0,0 +1,5 @@ +//@ edition: 2021 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs b/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs new file mode 100644 index 0000000000000..f27156a68a3be --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs @@ -0,0 +1,7 @@ +//@ edition: 2021 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +// Redirects are selected when this crate first imports the external name, so +// both re-exports are fixed according to this crate's edition. +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-source.rs b/tests/ui/edition-redirect/auxiliary/reexport-source.rs new file mode 100644 index 0000000000000..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..726332cd21350 --- /dev/null +++ b/tests/ui/edition-redirect/basic.rs @@ -0,0 +1,65 @@ +//@ revisions: edition2018 edition2021 edition2024 +//@[edition2018] edition: 2018 +//@[edition2021] edition: 2021 +//@[edition2024] edition: 2024 +//@ aux-build: basic.rs +//@ check-pass + +#[macro_use] +extern crate basic as edition_redirect; + +use edition_redirect::{ + Redirected as ImportedRedirected, redirected_macro as imported_redirected_macro, +}; +use edition_redirect::{ + reexport_scope::Current as ExpectedScopedRedirected, + use_targets::CurrentUse as ExpectedReexportedUse, +}; + +#[cfg(edition2018)] +use edition_redirect::{ + Oldest as ExpectedRedirected, use_targets::OldestUse as ExpectedRedirectedUse, +}; +#[cfg(edition2021)] +use edition_redirect::{ + Middle as ExpectedRedirected, use_targets::MiddleUse as ExpectedRedirectedUse, +}; +#[cfg(edition2024)] +use edition_redirect::{ + Redirected as ExpectedRedirected, use_targets::CurrentUse as ExpectedRedirectedUse, +}; + +#[cfg(edition2018)] +const EXPECTED_VALUE: usize = 1; +#[cfg(edition2021)] +const EXPECTED_VALUE: usize = 2; +#[cfg(edition2024)] +const EXPECTED_VALUE: usize = 3; + +fn explicit() { + let _: ExpectedRedirected = edition_redirect::Redirected; + let _: ExpectedRedirectedUse = edition_redirect::RedirectedUse; + let _: ExpectedScopedRedirected = edition_redirect::ScopedRedirected; + let _: edition_redirect::same_redirects::Item = ExpectedReexportedUse; + const _: [(); EXPECTED_VALUE] = [(); edition_redirect::redirected_module::VALUE]; + const _: [(); EXPECTED_VALUE] = [(); edition_redirect::redirected_macro!()]; + const _: [(); EXPECTED_VALUE] = [(); redirected_macro!()]; + let _: ImportedRedirected = ExpectedRedirected; + const _: [(); EXPECTED_VALUE] = [(); imported_redirected_macro!()]; +} + +mod glob { + use super::{EXPECTED_VALUE, ExpectedRedirected, ExpectedRedirectedUse}; + use edition_redirect::*; + + fn check() { + let _: Redirected = ExpectedRedirected; + let _: RedirectedUse = ExpectedRedirectedUse; + const _: [(); EXPECTED_VALUE] = [(); redirected_module::VALUE]; + const _: [(); EXPECTED_VALUE] = [(); redirected_macro!()]; + } +} + +fn main() { + explicit(); +} diff --git a/tests/ui/edition-redirect/diagnostic.rs b/tests/ui/edition-redirect/diagnostic.rs new file mode 100644 index 0000000000000..89254cf83c498 --- /dev/null +++ b/tests/ui/edition-redirect/diagnostic.rs @@ -0,0 +1,51 @@ +//@ edition: 2018 +//@ aux-build: macro-source.rs +//@ check-fail + +extern crate macro_source; + +// A macro exported at the crate root should still be suggested when it is +// incorrectly imported through a module, even when the root binding has an +// edition redirect. +use macro_source::nested::redirected_macro; +//~^ ERROR unresolved import `macro_source::nested::redirected_macro` +//~| HELP a macro with this name exists at the root of the crate +//~| SUGGESTION macro_source::redirected_macro +//~| HELP consider importing this trait +//~| SUGGESTION use macro_source::Candidate; + +// A missing import from a module that also contains redirected names should +// produce the usual unresolved-import diagnostic. +use macro_source::NoSuchImport; +//~^ ERROR unresolved import `macro_source::NoSuchImport` + +// In edition 2018, `Candidate` redirects to a trait, so it should be suggested +// as an import for a missing unqualified trait. The default `Candidate` is a +// struct. +fn import_candidate() {} +//~^ ERROR cannot find trait `Candidate` in this scope + +// A misspelled qualified trait name should likewise suggest the trait selected +// in edition 2018. +fn typo_candidate() {} +//~^ ERROR cannot find trait `Canddate` in crate `macro_source` +//~| HELP a trait with a similar name exists +//~| SUGGESTION Candidate + +// Doc aliases from the edition-selected target should be available in typo +// suggestions. The default `AliasCarrier` does not have this alias. +fn doc_alias(_: macro_source::OldAlias) {} +//~^ ERROR cannot find type `OldAlias` in crate `macro_source` +//~| HELP has a name defined in the doc alias attribute as `OldAlias` +//~| SUGGESTION AliasCarrier + +// An enum reached through a redirected module should still produce a suggestion +// using a variant from the selected module. +fn enum_variant() -> macro_source::diagnostic_module::DiagnosticEnum { + macro_source::diagnostic_module::DiagnosticEnum(0) + //~^ ERROR cannot find function, tuple struct or tuple variant `DiagnosticEnum` + //~| HELP try to construct the enum's variant + //~| SUGGESTION macro_source::diagnostic_module::DiagnosticEnum::Variant +} + +fn main() {} diff --git a/tests/ui/edition-redirect/diagnostic.stderr b/tests/ui/edition-redirect/diagnostic.stderr new file mode 100644 index 0000000000000..f3e3bc596bc78 --- /dev/null +++ b/tests/ui/edition-redirect/diagnostic.stderr @@ -0,0 +1,78 @@ +error[E0432]: unresolved import `macro_source::nested::redirected_macro` + --> $DIR/diagnostic.rs:10:5 + | +LL | use macro_source::nested::redirected_macro; + | ^^^^^^^^^^^^^^^^^^^^^^---------------- + | | + | no `redirected_macro` in `nested` + | + = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined +help: a macro with this name exists at the root of the crate + | +LL - use macro_source::nested::redirected_macro; +LL + use macro_source::redirected_macro; + | + +error[E0432]: unresolved import `macro_source::NoSuchImport` + --> $DIR/diagnostic.rs:19:5 + | +LL | use macro_source::NoSuchImport; + | ^^^^^^^^^^^^^^------------ + | | + | no `NoSuchImport` in the root + +error[E0405]: cannot find trait `Candidate` in this scope + --> $DIR/diagnostic.rs:25:24 + | +LL | fn import_candidate() {} + | ^^^^^^^^^ not found in this scope + | +help: consider importing this trait + | +LL + use macro_source::Candidate; + | + +error[E0405]: cannot find trait `Canddate` in crate `macro_source` + --> $DIR/diagnostic.rs:30:36 + | +LL | fn typo_candidate() {} + | ^^^^^^^^ + | + ::: $DIR/auxiliary/macro-source.rs:17:5 + | +LL | pub trait Candidate {} + | ------------------- similarly named trait `Candidate` defined here + | +help: a trait with a similar name exists + | +LL | fn typo_candidate() {} + | + + +error[E0425]: cannot find type `OldAlias` in crate `macro_source` + --> $DIR/diagnostic.rs:37:31 + | +LL | fn doc_alias(_: macro_source::OldAlias) {} + | ^^^^^^^^ + | +help: `AliasCarrier` has a name defined in the doc alias attribute as `OldAlias` + | +LL - fn doc_alias(_: macro_source::OldAlias) {} +LL + fn doc_alias(_: macro_source::AliasCarrier) {} + | + +error[E0423]: cannot find function, tuple struct or tuple variant `DiagnosticEnum` in module `macro_source::diagnostic_module` + --> $DIR/diagnostic.rs:45:38 + | +LL | macro_source::diagnostic_module::DiagnosticEnum(0) + | ^^^^^^^^^^^^^^ + | + = note: an enum named `macro_source::diagnostic_module::DiagnosticEnum` exists in another namespace +help: try to construct the enum's variant + | +LL | macro_source::diagnostic_module::DiagnosticEnum::Variant(0) + | +++++++++ + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0405, E0423, E0425, E0432. +For more information about an error, try `rustc --explain E0405`. diff --git a/tests/ui/edition-redirect/feature-gate.rs b/tests/ui/edition-redirect/feature-gate.rs new file mode 100644 index 0000000000000..e3e5c32db4dc4 --- /dev/null +++ b/tests/ui/edition-redirect/feature-gate.rs @@ -0,0 +1,13 @@ +// gate-test-edition_redirect + +#![feature(rustc_attrs)] + +pub struct Old; + +#[rustc_edition_redirect = "2024"] +//~^ ERROR the `rustc_edition_redirect` attribute is an experimental feature +pub use Old as Current; + +pub struct Current; + +fn main() {} diff --git a/tests/ui/edition-redirect/feature-gate.stderr b/tests/ui/edition-redirect/feature-gate.stderr new file mode 100644 index 0000000000000..34c58b2cf5857 --- /dev/null +++ b/tests/ui/edition-redirect/feature-gate.stderr @@ -0,0 +1,12 @@ +error[E0658]: the `rustc_edition_redirect` attribute is an experimental feature + --> $DIR/feature-gate.rs:7:3 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(edition_redirect)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/edition-redirect/invalid.rs b/tests/ui/edition-redirect/invalid.rs new file mode 100644 index 0000000000000..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/macro-glob.rs b/tests/ui/edition-redirect/macro-glob.rs new file mode 100644 index 0000000000000..73449a5c0243a --- /dev/null +++ b/tests/ui/edition-redirect/macro-glob.rs @@ -0,0 +1,25 @@ +//@ revisions: edition2018 edition2024 +//@[edition2018] edition: 2018 +//@[edition2024] edition: 2024 +//@ aux-build: macro-source.rs +//@ aux-build: macro-2018.rs +//@ aux-build: macro-2024.rs +//@ check-pass + +extern crate macro_2018; +extern crate macro_2024; +extern crate macro_source; + +// Redirects in a macro-generated glob use the edition of the glob's path, not +// the edition of the crate where the macro is invoked. +#[cfg(edition2018)] +macro_2024::import_all!(); +#[cfg(edition2024)] +macro_2018::import_all!(); + +fn main() { + #[cfg(edition2018)] + let _: macro_source::Current = Name; + #[cfg(edition2024)] + let _: macro_source::Old = Name; +} diff --git a/tests/ui/edition-redirect/macro-use.rs b/tests/ui/edition-redirect/macro-use.rs new file mode 100644 index 0000000000000..0e318e3829bf7 --- /dev/null +++ b/tests/ui/edition-redirect/macro-use.rs @@ -0,0 +1,32 @@ +//@ revisions: edition2018 edition2024 +//@[edition2018] edition: 2018 +//@[edition2024] edition: 2024 +//@ aux-build: macro-source.rs +//@ aux-build: macro-2018.rs +//@ aux-build: macro-2024.rs +//@ check-pass + +extern crate macro_2018; +extern crate macro_2024; +extern crate macro_source as source; + +// As with a glob import, importing every macro through `#[macro_use]` uses the +// edition of the generated `extern crate` item. +#[cfg(edition2018)] +macro_2024::macro_use_source!(); +#[cfg(edition2024)] +macro_2018::macro_use_source!(); + +redirected_macro!(); + +#[cfg(edition2018)] +fn check(value: Selected) -> source::Current { + value +} + +#[cfg(edition2024)] +fn check(value: Selected) -> source::Old { + value +} + +fn main() {} diff --git a/tests/ui/edition-redirect/prelude-import.rs b/tests/ui/edition-redirect/prelude-import.rs new file mode 100644 index 0000000000000..2406b9e12befb --- /dev/null +++ b/tests/ui/edition-redirect/prelude-import.rs @@ -0,0 +1,31 @@ +//@ edition: 2024 +//@ aux-build: macro-source.rs +//@ aux-build: macro-2018.rs +//@ aux-build: macro-2024.rs +//@ check-pass + +#![feature(prelude_import)] + +extern crate macro_2018; +extern crate macro_2024; +extern crate macro_source; + +#[prelude_import] +use macro_source::trait_prelude::*; + +fn main() { + // Ordinary names in the prelude are resolved using the identifier's + // edition. Exercise both namespaces of the redirected unit struct. + let _: macro_2018::redirected_type!() = macro_source::trait_prelude::OldItem; + let _: macro_2024::redirected_type!() = + macro_source::trait_prelude::CurrentItem; + let _: macro_source::trait_prelude::OldItem = macro_2018::redirected_value!(); + let _: macro_source::trait_prelude::CurrentItem = + macro_2024::redirected_value!(); + + // Both calls search the same external prelude module. Trait discovery must + // select the redirect using each macro-generated method name's edition + // rather than reuse the first cached result. + let _: OldMarker = macro_2018::call_redirected_trait!(); + let _: CurrentMarker = macro_2024::call_redirected_trait!(); +} diff --git a/tests/ui/edition-redirect/reexport.rs b/tests/ui/edition-redirect/reexport.rs new file mode 100644 index 0000000000000..7a3ee65079f33 --- /dev/null +++ b/tests/ui/edition-redirect/reexport.rs @@ -0,0 +1,24 @@ +//@ revisions: old current +//@[old] edition: 2021 +//@[current] edition: 2024 +//@ aux-crate: reexport_source=reexport-source.rs +//@ aux-crate: reexport_preserving=reexport-preserving.rs +//@ aux-crate: reexport_old=reexport-old.rs +//@ aux-crate: reexport_current=reexport-current.rs +//@ check-pass + +fn main() { + // A redirect is consumed by the first crate that imports it. The resulting + // re-export is therefore fixed to that crate's edition for all downstream + // users. + let _: reexport_preserving::Item = reexport_source::old(); + let _: reexport_preserving::Child = reexport_source::old_child(); + + let _: reexport_old::Item = reexport_source::old(); + let _: reexport_current::Item = reexport_source::current(); + + // Redirecting a module changes path traversal at the first ordinary `use`, + // but does not make the module's children independently redirected. + let _: reexport_old::Child = reexport_source::old_child(); + let _: reexport_current::Child = reexport_source::current_child(); +} diff --git a/tests/ui/edition-redirect/stability.old.stderr b/tests/ui/edition-redirect/stability.old.stderr new file mode 100644 index 0000000000000..58ddffdc86b72 --- /dev/null +++ b/tests/ui/edition-redirect/stability.old.stderr @@ -0,0 +1,12 @@ +error[E0658]: use of unstable library feature `edition_redirect_old` + --> $DIR/stability.rs:11:25 + | +LL | const _: [(); 1] = [(); redirected_macro!()]; + | ^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(edition_redirect_old)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/edition-redirect/stability.rs b/tests/ui/edition-redirect/stability.rs new file mode 100644 index 0000000000000..efcabfed0fed3 --- /dev/null +++ b/tests/ui/edition-redirect/stability.rs @@ -0,0 +1,17 @@ +//@ revisions: old current +//@[old] edition: 2021 +//@[current] edition: 2024 +//@ aux-build: stability.rs +//@[current] check-pass + +#[macro_use] +extern crate stability as edition_redirect_stability; + +#[cfg(old)] +const _: [(); 1] = [(); redirected_macro!()]; +//[old]~^ ERROR use of unstable library feature `edition_redirect_old` + +#[cfg(current)] +const _: [(); 2] = [(); redirected_macro!()]; + +fn main() {}