diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 54d083a094883..79139b694d5a2 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::fmt; use std::path::PathBuf; +use std::range::RangeInclusive; pub use ReprAttr::*; use rustc_abi::Align; @@ -16,6 +17,7 @@ use rustc_macros::{ Decodable, Decodable_NoContext, Encodable, Encodable_NoContext, PrintAttribute, StableHash, }; use rustc_span::def_id::DefId; +use rustc_span::edition::Edition; use rustc_span::hygiene::Transparency; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_structures::{CollapseMacroDebuginfo, CrateType, Limit, NativeLibKind, SanitizerSet}; @@ -119,6 +121,12 @@ pub enum InstrumentFnAttr { Off, } +#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)] +pub struct EditionRedirect { + pub range: RangeInclusive, + pub span: Span, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Default, PrintAttribute)] #[derive(Encodable, Decodable, StableHash)] pub enum OptimizeAttr { @@ -1267,6 +1275,9 @@ pub enum AttributeKind { /// Represents `#[rustc_dyn_incompatible_trait]`. RustcDynIncompatibleTrait(Span), + /// Represents `#[rustc_edition_redirect = "..."]`. + RustcEditionRedirect(EditionRedirect), + /// Represents `#[rustc_effective_visibility]`. RustcEffectiveVisibility, diff --git a/compiler/rustc_attr_ir/src/encode_cross_crate.rs b/compiler/rustc_attr_ir/src/encode_cross_crate.rs index 270ec0399799e..b2e579ca0d587 100644 --- a/compiler/rustc_attr_ir/src/encode_cross_crate.rs +++ b/compiler/rustc_attr_ir/src/encode_cross_crate.rs @@ -145,6 +145,7 @@ impl AttributeKind { RustcDumpVariancesOfOpaques => No, RustcDumpVtable(..) => No, RustcDynIncompatibleTrait(..) => No, + RustcEditionRedirect(..) => No, RustcEffectiveVisibility => Yes, RustcEiiForeignItem => No, RustcEvaluateWhereClauses => Yes, diff --git a/compiler/rustc_attr_ir/src/pretty_printing.rs b/compiler/rustc_attr_ir/src/pretty_printing.rs index cd8a0c0e1e96f..e37b8e02178ae 100644 --- a/compiler/rustc_attr_ir/src/pretty_printing.rs +++ b/compiler/rustc_attr_ir/src/pretty_printing.rs @@ -1,6 +1,7 @@ use std::num::NonZero; use std::ops::Deref; use std::path::PathBuf; +use std::range::RangeInclusive; use rustc_abi::Align; use rustc_ast::ast::{Path, join_path_idents}; @@ -12,6 +13,7 @@ use rustc_ast::{AttrId, AttrStyle, IntTy, UintTy}; use rustc_ast_pretty::pp::Printer; use rustc_data_structures::fx::FxIndexMap; use rustc_span::def_id::DefId; +use rustc_span::edition::Edition; use rustc_span::hygiene::Transparency; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_structures::{CollapseMacroDebuginfo, CrateType, Limit, NativeLibKind, SanitizerSet}; @@ -190,7 +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, @@ -210,4 +212,5 @@ print_debug!( CrateType, NativeLibKind, CollapseMacroDebuginfo, + RangeInclusive, ); diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 0df3d9a626bee..f9881f3e844ad 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -1,15 +1,17 @@ use std::path::PathBuf; +use std::range::RangeInclusive; use rustc_ast::{LitIntType, LitKind, MetaItemLit}; use rustc_attr_ir::lang_items::LangItem; use rustc_attr_ir::target::GenericParamKind; use rustc_attr_ir::{ - BorrowckGraphvizFormatKind, CguFields, CguKind, RustcCleanAttribute, RustcCleanQueries, - RustcMirKind, + BorrowckGraphvizFormatKind, CguFields, CguKind, EditionRedirect, RustcCleanAttribute, + RustcCleanQueries, RustcMirKind, }; use rustc_data_structures::fx::FxHashMap; use rustc_feature::AttributeStability; use rustc_span::Symbol; +use rustc_span::edition::Edition; use super::prelude::*; use super::util::parse_single_integer; @@ -342,6 +344,33 @@ 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: "2021..=2024"); + const STABILITY: AttributeStability = unstable!(rustc_attrs); + + fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { + let value = cx.expect_name_value(args, cx.attr_span, Some(sym::rustc_edition_redirect))?; + let value = cx.expect_string_literal(value)?; + let Some((start, last)) = value.as_str().split_once("..=").and_then(|(start, end)| { + let start = if start.is_empty() { Edition::Edition2015 } else { start.parse().ok()? }; + let last = end.parse().ok()?; + (start <= last).then_some((start, last)) + }) else { + cx.emit_err(diagnostics::InvalidEditionRedirect { span: cx.attr_span }); + return None; + }; + + Some(AttributeKind::RustcEditionRedirect(EditionRedirect { + range: RangeInclusive { start, last }, + span: cx.attr_span, + })) + } +} + pub(crate) struct RustcDeprecatedSafe2024Parser; impl SingleAttributeParser for RustcDeprecatedSafe2024Parser { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index cf99311cc0cfc..497be1c627dae 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -240,6 +240,7 @@ attribute_parsers!( Single, Single, Single, + Single, Single, Single, Single, diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index 663915e39dccd..5fc0aabfed83b 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 range in edition redirect")] +pub(crate) struct InvalidEditionRedirect { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("unreachable configuration predicate")] pub(crate) struct UnreachableCfgSelectPredicate { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index f79a8e9ffc79c..20608137160eb 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -247,6 +247,7 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ sym::rustc_eii_foreign_item, sym::rustc_allowed_through_unstable_modules, sym::rustc_deprecated_safe_2024, + sym::rustc_edition_redirect, sym::rustc_pub_transparent, // ========================================================================== diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 8a565369d7610..4e4d272c04ffc 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1313,7 +1313,14 @@ impl CrateMetadata { let res = Res::Def(self.def_kind(id), self.local_def_id(id)); let vis = self.get_visibility(tcx, id); - ModChild { ident, res, vis, reexport_chain: Default::default() } + ModChild { + ident, + res, + vis, + reexport_chain: Default::default(), + // Children with redirects are encoded as full `ModChild`s. + edition_redirects: Default::default(), + } } /// Iterates over all named children of the given module, diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 713671c3a5b47..e69355bbe9650 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1737,11 +1737,13 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let module_children = tcx.module_children_local(local_def_id); record_array!(self.tables.module_children_non_reexports[def_id] <- - module_children.iter().filter(|child| child.reexport_chain.is_empty()) + module_children.iter().filter(|child| child.reexport_chain.is_empty() + && child.edition_redirects.is_empty()) .map(|child| child.res.def_id().index)); record_defaulted_array!(self.tables.module_children_reexports[def_id] <- - module_children.iter().filter(|child| !child.reexport_chain.is_empty())); + module_children.iter().filter(|child| !child.reexport_chain.is_empty() + || !child.edition_redirects.is_empty())); let ambig_module_children = tcx .resolutions(()) diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index d16839b910c4b..2d24b0a8ef514 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -402,10 +402,9 @@ define_tables! { explicit_implied_const_bounds: Table, Span)>>, inherent_impls: Table>, opt_rpitit_info: Table>>, - // Reexported names are not associated with individual `DefId`s, - // e.g. a glob import can introduce a lot of names, all with the same `DefId`. - // That's why the encoded list needs to contain `ModChild` structures describing all the names - // individually instead of `DefId`s. + // Names requiring data beyond the item's own `DefId` are encoded as full `ModChild`s. + // This includes reexports, where a glob can introduce many names with the same `DefId`, and + // proper items carrying edition redirects. module_children_reexports: Table>, ambig_module_children: Table>, cross_crate_inlinable: Table, diff --git a/compiler/rustc_middle/src/middle/resolve.rs b/compiler/rustc_middle/src/middle/resolve.rs index 2958048103320..85542e5764ad6 100644 --- a/compiler/rustc_middle/src/middle/resolve.rs +++ b/compiler/rustc_middle/src/middle/resolve.rs @@ -1,6 +1,8 @@ //! This module contains types that carry name resolution results from `rustc_resolve` to a //! consumer in another crate (e.g. AST lowering, metadata, or a query). +use std::range::RangeInclusive; + use rustc_ast::node_id::NodeMap; use rustc_ast::{self as ast, NodeId}; use rustc_attr_ir::StrippedCfgItem; @@ -13,6 +15,7 @@ use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalDefIdMap, LocalModId, use rustc_hir::definitions::PerParentDisambiguatorState; use rustc_hir::{MissingLifetimeKind, TraitCandidate}; use rustc_macros::{StableHash, TyDecodable, TyEncodable}; +use rustc_span::edition::Edition; use rustc_span::{ExpnId, Ident, Span, Symbol}; use smallvec::SmallVec; @@ -132,6 +135,13 @@ impl Reexport { } } +/// A different item that a module child resolves to within an inclusive edition range. +#[derive(Clone, Copy, Debug, TyEncodable, TyDecodable, StableHash)] +pub struct EditionRedirect { + pub range: RangeInclusive, + pub target: Res, +} + /// This structure is supposed to keep enough data to re-create `Decl`s for other crates /// during name resolution. Right now the bindings are not recreated entirely precisely so we may /// need to add more data in the future to correctly support macros 2.0, for example. @@ -149,6 +159,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 by their range. + pub edition_redirects: SmallVec<[EditionRedirect; 1]>, } /// Same as `ModChild`, however, it includes ambiguity error. diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index cd7f422fb72f3..204f391344b9e 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -17,8 +17,8 @@ use rustc_feature::BUILTIN_ATTRIBUTE_SET; use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::lang_items::LangItem; use rustc_hir::attrs::{ - AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr, - OptimizeAttr, ReprAttr, + AttributeKind, DocAttribute, DocInline, EditionRedirect, EiiDecl, EiiImpl, EiiImplResolution, + InlineAttr, OptimizeAttr, ReprAttr, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModId; @@ -239,6 +239,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::Linkage(_linkage, span) => { self.check_linkage(*span, hir_id, target, item) } + AttributeKind::RustcEditionRedirect(redirect) => { + self.check_rustc_edition_redirect(item, redirect) + } // All of the following attributes have no specific checks. // tidy-alphabetical-start @@ -467,6 +470,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> { }) } + /// Rejects the use of edition redirect on non-single use statements. + fn check_rustc_edition_redirect(&self, item: Option<&Item<'_>>, redirect: &EditionRedirect) { + let Some(Item { kind: ItemKind::Use(_, use_kind), .. }) = item else { + return; + }; + if matches!(use_kind, hir::UseKind::Single(_)) { + return; + } + self.dcx().emit_err(diagnostics::EditionRedirectNonSingleUse { attr_span: redirect.span }); + } + fn check_rustc_must_implement_one_of( &self, attr_span: Span, diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 5f99c4b133597..2b5e63f02a2f7 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -13,6 +13,14 @@ use rustc_span::{DUMMY_SP, Ident, Span, Symbol}; use crate::check_attr::ProcMacroKind; use crate::lang_items::Duplicate; +#[derive(Diagnostic)] +#[diag("`#[rustc_edition_redirect]` can only be applied to a single import")] +#[help("use a separate, non-braced `use` item")] +pub(crate) struct EditionRedirectNonSingleUse { + #[primary_span] + pub attr_span: Span, +} + #[derive(Diagnostic)] #[diag("`{$no_mangle_attr}` attribute may not be used in combination with `{$export_name_attr}`")] pub(crate) struct MixedExportNameAndNoMangle { diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 88f057c3a6d6d..e859faba3bccc 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -12,7 +12,7 @@ use rustc_ast::visit::{self, AssocCtxt, Visitor, WalkItemKind}; use rustc_ast::{ self as ast, AssocItem, AssocItemKind, Block, ConstItem, DUMMY_NODE_ID, Delegation, DelegationSource, Fn, ForeignItem, ForeignItemKind, Inline, Item, ItemKind, NodeId, StaticItem, - StmtKind, TraitAlias, TyAlias, + StmtKind, TraitAlias, TyAlias, attr, }; use rustc_attr_parsing::AttributeParser; use rustc_data_structures::fx::FxIndexMap; @@ -39,10 +39,10 @@ use crate::imports::{ImportData, ImportKind, NameResolution, NameResolutionRef}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; use crate::{ - BindingKey, Decl, DeclData, DeclKind, DelayedVisResolutionError, ExternModule, - ExternPreludeEntry, Finalize, IdentKey, LocalModule, Module, ModuleKind, ModuleOrUniformRoot, - ParentScope, PathResult, Res, ResolutionTable, Resolver, Segment, Used, VisResolutionError, - diagnostics, + BindingKey, Decl, DeclData, DeclKind, DelayedVisResolutionError, EditionRedirectDecl, + ExternModule, ExternPreludeEntry, Finalize, IdentKey, LocalModule, Module, ModuleKind, + ModuleOrUniformRoot, ParentScope, PathResult, Res, ResolutionTable, Resolver, Segment, Used, + VisResolutionError, diagnostics, }; impl<'ra, 'tcx> Resolver<'ra, 'tcx> { @@ -382,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. @@ -398,6 +400,25 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Record primary definitions. let mut define_extern = |ns| { let orig_ident_span = orig_ident.span; + let edition_redirects = edition_redirects + .iter() + .map(|redirect| EditionRedirectDecl { + range: redirect.range, + // Model this as a one-step reexport under the original child's name: the + // target supplies the resolution, while the child supplies its visibility, + // span, and parent module. + target: self.arenas.alloc_decl(DeclData { + kind: DeclKind::Def(redirect.target.expect_non_local()), + ambiguity: CmCell::new(None), + initial_vis: vis, + ambiguity_vis_max: CmCell::new(None), + ambiguity_vis_min: CmCell::new(None), + span, + expansion, + parent_module: Some(parent.to_module()), + }), + }) + .collect::>(); let decl = self.arenas.alloc_decl(DeclData { kind: DeclKind::Def(res), ambiguity: CmCell::new(ambig), @@ -408,12 +429,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { expansion, parent_module: Some(parent.to_module()), }); - let resolution = self.arenas.alloc_name_resolution(NameResolution { - non_glob_decl: Some(decl), + let resolution = self.arenas.alloc_name_resolution(NameResolution::new( + Some(decl), + edition_redirects, orig_ident_span, - single_imports: Default::default(), - .. - }); + )); let key = BindingKey::new_disambiguated(ident, ns, || (child_index + 1).try_into().unwrap()); @@ -565,10 +585,13 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { self.r.indeterminate_imports.push((import, None, 0)); match import.kind { - ImportKind::Single { target, .. } => { + ImportKind::Single { target, edition_redirect, .. } => { // Don't add underscore imports to `single_imports` // because they cannot define any usable names. - 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 && edition_redirect.is_none() { self.r.per_ns_mut(|this, ns| { let key = BindingKey::new(IdentKey::new(target), ns); this.resolution_or_default(current_module.to_module(), key, target.span) @@ -725,11 +748,25 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { return; } + let edition_redirect = if !nested + && 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 + }; + let kind = ImportKind::Single { source: source.ident, target: ident, decls: Default::default(), nested, + edition_redirect, id, def_id: feed.def_id(), }; @@ -1193,7 +1230,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) { diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index a005824e5dbfa..9cb3dea7a3c74 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -767,16 +767,21 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { pub(crate) fn add_module_candidates( &self, module: Module<'ra>, + redirect_span: Span, names: &mut Vec, filter_fn: &impl Fn(Res) -> bool, ctxt: Option, ) { - module.for_each_child(self, |_this, ident, orig_ident_span, _ns, binding| { - let res = binding.res(); - if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { - names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); - } - }); + module.for_each_child_redir( + self, + redirect_span, + |_this, ident, orig_ident_span, _ns, binding| { + let res = binding.res(); + if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { + names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); + } + }, + ); } /// Combines an error with provided span and emits it. @@ -1011,6 +1016,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut local_names = vec![]; self.add_module_candidates( parent_scope.module, + name.span, &mut local_names, &|res| matches!(res, Res::Def(_, _)), None, @@ -1534,7 +1540,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } Scope::ModuleNonGlobs(module, _) => { - this.add_module_candidates(module, suggestions, filter_fn, None); + this.add_module_candidates(module, sp, suggestions, filter_fn, None); } Scope::ModuleGlobs(..) => { // Already handled in `ModuleNonGlobs`. @@ -1576,7 +1582,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope::StdLibPrelude => { if let Some(prelude) = this.prelude { let mut tmp_suggestions = Vec::new(); - this.add_module_candidates(prelude, &mut tmp_suggestions, filter_fn, None); + this.add_module_candidates( + prelude, + sp, + &mut tmp_suggestions, + filter_fn, + None, + ); suggestions.extend( tmp_suggestions .into_iter() @@ -1660,174 +1672,180 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } { let in_module_is_extern = !in_module.def_id().is_local(); - in_module.for_each_child(self, |this, ident, orig_ident_span, ns, name_binding| { - // Avoid non-importable candidates. - if name_binding.is_assoc_item() - && !this.features.import_trait_associated_functions() - { - return; - } + in_module.for_each_child_redir( + self, + lookup_ident.span, + |this, ident, orig_ident_span, ns, name_binding| { + // Avoid non-importable candidates. + if name_binding.is_assoc_item() + && !this.features.import_trait_associated_functions() + { + return; + } - if ident.name == kw::Underscore { - return; - } + if ident.name == kw::Underscore { + return; + } - let child_accessible = - accessible && this.is_accessible_from(name_binding.vis(), parent_scope.module); + let child_accessible = accessible + && this.is_accessible_from(name_binding.vis(), parent_scope.module); - // do not venture inside inaccessible items of other crates - if in_module_is_extern && !child_accessible { - return; - } + // do not venture inside inaccessible items of other crates + if in_module_is_extern && !child_accessible { + return; + } - let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); + let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); - // There is an assumption elsewhere that paths of variants are in the enum's - // declaration and not imported. With this assumption, the variant component is - // chopped and the rest of the path is assumed to be the enum's own path. For - // errors where a variant is used as the type instead of the enum, this causes - // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. - if via_import && name_binding.is_possibly_imported_variant() { - return; - } + // There is an assumption elsewhere that paths of variants are in the enum's + // declaration and not imported. With this assumption, the variant component is + // chopped and the rest of the path is assumed to be the enum's own path. For + // errors where a variant is used as the type instead of the enum, this causes + // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. + if via_import && name_binding.is_possibly_imported_variant() { + return; + } - // #90113: Do not count an inaccessible reexported item as a candidate. - if let DeclKind::Import { source_decl, .. } = name_binding.kind - && this.is_accessible_from(source_decl.vis(), parent_scope.module) - && !this.is_accessible_from(name_binding.vis(), parent_scope.module) - { - return; - } + // #90113: Do not count an inaccessible reexported item as a candidate. + if let DeclKind::Import { source_decl, .. } = name_binding.kind + && this.is_accessible_from(source_decl.vis(), parent_scope.module) + && !this.is_accessible_from(name_binding.vis(), parent_scope.module) + { + return; + } - let res = name_binding.res(); - let did = match res { - Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), - _ => res.opt_def_id(), - }; - let child_doc_visible = doc_visible - && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); - - // collect results based on the filter function - // avoid suggesting anything from the same module in which we are resolving - // avoid suggesting anything with a hygienic name - if ident.name == lookup_ident.name - && ns == namespace - && in_module != parent_scope.module - && ident.ctxt.is_root() - && filter_fn(res) - { - // create the path - let mut segms = if lookup_ident.span.at_least_rust_2018() { - // crate-local absolute paths start with `crate::` in edition 2018 - // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) - crate_path.clone() - } else { - ThinVec::new() + let res = name_binding.res(); + let did = match res { + Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), + _ => res.opt_def_id(), }; - segms.append(&mut path_segments.clone()); + let child_doc_visible = doc_visible + && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); + + // collect results based on the filter function + // avoid suggesting anything from the same module in which we are resolving + // avoid suggesting anything with a hygienic name + if ident.name == lookup_ident.name + && ns == namespace + && in_module != parent_scope.module + && ident.ctxt.is_root() + && filter_fn(res) + { + // create the path + let mut segms = if lookup_ident.span.at_least_rust_2018() { + // crate-local absolute paths start with `crate::` in edition 2018 + // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) + crate_path.clone() + } else { + ThinVec::new() + }; + segms.append(&mut path_segments.clone()); - segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let path = Path { span: name_binding.span, segments: segms }; + segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let path = Path { span: name_binding.span, segments: segms }; - if child_accessible + if child_accessible // Remove invisible match if exists && let Some(idx) = candidates .iter() .position(|v: &ImportSuggestion| v.did == did && !v.accessible) - { - candidates.remove(idx); - } + { + candidates.remove(idx); + } - let is_stable = if is_stable - && let Some(did) = did - && this.is_stable(did, path.span) - { - true - } else { - false - }; + let is_stable = if is_stable + && let Some(did) = did + && this.is_stable(did, path.span) + { + true + } else { + false + }; - // Rreplace unstable suggestions if we meet a new stable one, - // and do nothing if any other situation. For example, if we - // meet `std::ops::Range` after `std::range::legacy::Range`, - // we will remove the latter and then insert the former. - if is_stable - && let Some(idx) = candidates - .iter() - .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) - { - candidates.remove(idx); - } + // Rreplace unstable suggestions if we meet a new stable one, + // and do nothing if any other situation. For example, if we + // meet `std::ops::Range` after `std::range::legacy::Range`, + // we will remove the latter and then insert the former. + if is_stable + && let Some(idx) = candidates + .iter() + .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) + { + candidates.remove(idx); + } - if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { - // See if we're recommending TryFrom, TryInto, or FromIterator and add - // a note about editions - let note = if let Some(did) = did { - let requires_note = !did.is_local() - && find_attr!( - this.tcx, - did, - RustcDiagnosticItem( - sym::TryInto | sym::TryFrom | sym::FromIterator + if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { + // See if we're recommending TryFrom, TryInto, or FromIterator and add + // a note about editions + let note = if let Some(did) = did { + let requires_note = !did.is_local() + && find_attr!( + this.tcx, + did, + RustcDiagnosticItem( + sym::TryInto | sym::TryFrom | sym::FromIterator + ) + ); + requires_note.then(|| { + format!( + "'{}' is included in the prelude starting in Edition 2021", + path_names_to_string(&path) ) - ); - requires_note.then(|| { - format!( - "'{}' is included in the prelude starting in Edition 2021", - path_names_to_string(&path) - ) - }) - } else { - None - }; + }) + } else { + None + }; - candidates.push(ImportSuggestion { - did, - descr: res.descr(), - path, - accessible: child_accessible, - doc_visible: child_doc_visible, - note, - via_import, - is_stable, - }); + candidates.push(ImportSuggestion { + did, + descr: res.descr(), + path, + accessible: child_accessible, + doc_visible: child_doc_visible, + note, + via_import, + is_stable, + }); + } } - } - // collect submodules to explore - if let Some(def_id) = name_binding.res().module_like_def_id() { - // form the path - let mut path_segments = path_segments.clone(); - path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - - let alias_import = if let DeclKind::Import { import, .. } = name_binding.kind - && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind - && import.parent_scope.expansion == parent_scope.expansion - { - true - } else { - false - }; + // collect submodules to explore + if let Some(def_id) = name_binding.res().module_like_def_id() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments + .push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + + let alias_import = if let DeclKind::Import { import, .. } = + name_binding.kind + && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind + && import.parent_scope.expansion == parent_scope.expansion + { + true + } else { + false + }; - let is_extern_crate_that_also_appears_in_prelude = - name_binding.is_extern_crate() && lookup_ident.span.at_least_rust_2018(); - - if !is_extern_crate_that_also_appears_in_prelude || alias_import { - // add the module to the lookup - if seen_modules.insert(def_id) { - if via_import { &mut worklist_via_import } else { &mut worklist }.push( - ( - this.expect_module(def_id), - path_segments, - child_accessible, - child_doc_visible, - is_stable && this.is_stable(def_id, name_binding.span), - ), - ); + let is_extern_crate_that_also_appears_in_prelude = name_binding + .is_extern_crate() + && lookup_ident.span.at_least_rust_2018(); + + if !is_extern_crate_that_also_appears_in_prelude || alias_import { + // add the module to the lookup + if seen_modules.insert(def_id) { + if via_import { &mut worklist_via_import } else { &mut worklist } + .push(( + this.expect_module(def_id), + path_segments, + child_accessible, + child_doc_visible, + is_stable && this.is_stable(def_id, name_binding.span), + )); + } } } - } - }) + }, + ); } candidates @@ -3542,7 +3560,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } let binding_key = BindingKey::new(IdentKey::new(ident), MacroNS); - let binding = self.resolution(crate_module, binding_key)?.best_decl()?; + let binding = self.resolution(crate_module, binding_key)?.best_decl_redir(ident.span)?; let Res::Def(DefKind::Macro(kinds), _) = binding.res() else { return None; }; diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index c1b3c6cd5eeba..afa2de12bd9b1 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1113,7 +1113,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let resolution = &*self.resolution(module.to_module(), key).ok_or(ControlFlow::Continue(Determined))?; - let binding = resolution.non_glob_decl.filter(|b| Some(*b) != ignore_decl); + let binding = + resolution.non_glob_decl_redir(orig_ident_span).filter(|b| Some(*b) != ignore_decl); if let Some(finalize) = finalize { return self.get_mut().finalize_module_binding( @@ -1153,7 +1154,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let resolution = self.resolution(module.to_module(), key); let binding = - resolution.as_ref().and_then(|r| r.non_glob_decl).filter(|b| Some(*b) != ignore_decl); + resolution.as_ref().and_then(|r| r.non_glob_decl()).filter(|b| Some(*b) != ignore_decl); if let Some(finalize) = finalize { // finalize implies that the module is fully expanded @@ -1398,7 +1399,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { parent_scope: &ParentScope<'ra>, ) -> bool { for single_import in &resolution.single_imports { - if let Some(decl) = resolution.non_glob_decl + if let Some(decl) = resolution.non_glob_decl() && let DeclKind::Import { import, .. } = decl.kind && import == *single_import { diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 1cda9b9139028..fb1b3cab631ef 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -4,10 +4,11 @@ use std::cmp::Ordering; use std::mem; use rustc_ast::NodeId; -use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_errors::{Applicability, BufferedEarlyLint, Diagnostic}; use rustc_expand::base::SyntaxExtensionKind; +use rustc_hir::attrs::EditionRedirect; use rustc_hir::def::{self, DefKind}; use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap}; use rustc_lint_defs::LintId; @@ -15,13 +16,16 @@ use rustc_lint_defs::builtin::{ AMBIGUOUS_GLOB_REEXPORTS, EXPORTED_PRIVATE_DEPENDENCIES, HIDDEN_GLOB_REEXPORTS, PUB_USE_OF_PRIVATE_EXTERN_CRATE, REDUNDANT_IMPORTS, UNUSED_IMPORTS, }; -use rustc_middle::middle::resolve::{AmbigModChild, ModChild, PartialRes, Reexport}; +use rustc_middle::middle::resolve::{ + AmbigModChild, EditionRedirect as MetadataEditionRedirect, ModChild, PartialRes, Reexport, +}; use rustc_middle::span_bug; use rustc_middle::ty::Visibility; use rustc_session::diagnostics::feature_err; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::hygiene::LocalExpnId; use rustc_span::{Ident, Span, Symbol, kw, sym}; +use smallvec::SmallVec; use tracing::debug; use crate::Namespace::{self, *}; @@ -34,10 +38,10 @@ 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, + AmbiguityError, BindingKey, Decl, DeclData, DeclKind, Determinacy, EditionRedirectDecl, + Finalize, IdentKey, ImportSuggestion, ImportSummary, LocalEditionRedirect, LocalModule, + ModuleOrUniformRoot, ParentScope, PathResult, PerNS, Res, ResolutionError, Resolver, ScopeSet, + Segment, Used, module_to_string, names_to_string, }; /// A potential import declaration in the process of being planted into a module. @@ -81,6 +85,10 @@ pub(crate) enum ImportKind<'ra> { decls: PerNS>>, /// Did this import result from a nested import? i.e. `use foo::{bar, baz};` nested: bool, + /// If present, this import supplies one edition-specific alternative for its target name. + /// It is resolved and checked like an ordinary import, but is not visible in the current + /// crate. + edition_redirect: Option, /// The ID of the `UseTree` that imported this `Import`. /// /// In the case where the `Import` was expanded from a "nested" use tree, @@ -122,7 +130,7 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use ImportKind::*; match self { - Single { source, target, decls, nested, id, def_id } => f + Single { source, target, decls, nested, edition_redirect, id, def_id } => f .debug_struct("Single") .field("source", source) .field("target", target) @@ -132,6 +140,7 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { &decls.clone().map(|b| b.into_inner().decl().map(|_| format_args!(".."))), ) .field("nested", nested) + .field("edition_redirect", edition_redirect) .field("id", id) .field("def_id", def_id) .finish(), @@ -266,28 +275,76 @@ 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, +// Keep `non_glob_decl` private outside this module so every access must +// explicitly choose whether to apply edition redirects or assert that none are +// present. +mod name_resolution { + use super::*; + + /// 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>, + /// Fully resolved cross-crate redirects attached to `non_glob_decl`. + edition_redirects: Box<[EditionRedirectDecl<'ra>]>, + /// The glob declaration for this name, if it is known to exist. + pub glob_decl: Option> = None, + pub orig_ident_span: Span, + } + + impl<'ra> NameResolution<'ra> { + pub(crate) fn new( + non_glob_decl: Option>, + edition_redirects: Box<[EditionRedirectDecl<'ra>]>, + orig_ident_span: Span, + ) -> Self { + NameResolution { + single_imports: FxIndexSet::default(), + non_glob_decl, + edition_redirects, + orig_ident_span, + .. + } + } + + pub(crate) fn non_glob_decl(&self) -> Option> { + assert!(self.edition_redirects.is_empty()); + self.non_glob_decl + } + + pub(crate) fn non_glob_decl_redir(&self, span: Span) -> Option> { + self.non_glob_decl.map(|decl| { + if self.edition_redirects.is_empty() { + return decl; + } + let edition = span.edition(); + match self + .edition_redirects + .iter() + .find(|redirect| redirect.range.contains(&edition)) + { + Some(redirect) => redirect.target, + None => decl, + } + }) + } + + pub(super) fn set_non_glob_decl(&mut self, decl: Decl<'ra>) { + self.non_glob_decl = Some(decl); + } + } } +pub(crate) use name_resolution::NameResolution; /// `Interned` is used because values of this type have "identity" and compare as unequal even if /// they have the same contents. pub(crate) type NameResolutionRef<'ra> = Interned<'ra, CmRefCell>>; impl<'ra> NameResolution<'ra> { - pub(crate) fn new(orig_ident_span: Span) -> Self { - NameResolution { single_imports: FxIndexSet::default(), orig_ident_span, .. } - } - /// Returns the best declaration if it is not going to change, and `None` if the best /// declaration may still change to something else. /// FIXME: this function considers `single_imports`, but not `unexpanded_invocations`, so @@ -297,8 +354,18 @@ impl<'ra> NameResolution<'ra> { /// code breakage in practice. /// FIXME: relationship between this function and similar `DeclData::determined` is unclear. pub(crate) fn determined_decl(&self) -> Option> { - if self.non_glob_decl.is_some() { - self.non_glob_decl + if let non_glob_decl @ Some(..) = self.non_glob_decl() { + non_glob_decl + } else if self.glob_decl.is_some() && self.single_imports.is_empty() { + self.glob_decl + } else { + None + } + } + + pub(crate) fn determined_decl_redir(&self, span: Span) -> Option> { + if let non_glob_decl @ Some(..) = self.non_glob_decl_redir(span) { + non_glob_decl } else if self.glob_decl.is_some() && self.single_imports.is_empty() { self.glob_decl } else { @@ -307,7 +374,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) } } @@ -659,10 +730,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(()) @@ -838,7 +909,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { match (&import.kind, resolution_kind) { ( - ImportKind::Single { target, decls, .. }, + ImportKind::Single { source, target, decls, edition_redirect, .. }, ImportResolutionKind::Single(import_decls), ) => { self.per_ns_mut(|this, ns| { @@ -857,17 +928,35 @@ 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) = 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), + range: redirect.range, + 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 && edition_redirect.is_none() { let key = BindingKey::new(IdentKey::new(*target), ns); this.update_local_resolution( import.parent_scope.module.expect_local(), @@ -922,6 +1011,8 @@ 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 { @@ -1018,7 +1109,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Report "cannot reexport" errors for exotic cases involving macros 2.0 // privacy bending or invariant-breaking code under deprecation lints. - for decl in [resolution.non_glob_decl, resolution.glob_decl] { + for decl in [resolution.non_glob_decl(), resolution.glob_decl] { if let Some(decl) = decl && let DeclKind::Import { source_decl, import } = decl.kind // FIXME: Do not check visibility-ambiguous imports for now. To check them @@ -1062,7 +1153,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } if let Some(glob_decl) = resolution.glob_decl - && resolution.non_glob_decl.is_some() + && resolution.non_glob_decl().is_some() { if binding.res() != Res::Err && glob_decl.res() != Res::Err @@ -1507,7 +1598,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } // `use _` is never valid let resolution = resolution.borrow(self); - if let Some(name_binding) = resolution.best_decl() { + if let Some(name_binding) = resolution.best_decl_redir(ident.span) { match name_binding.kind { DeclKind::Import { source_decl, .. } => { match source_decl.kind { @@ -1818,7 +1909,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .filter_map(|(key, resolution)| { let res = resolution.borrow_checked(self); - let decl = res.determined_decl()?; + let decl = res.determined_decl_redir(import.span)?; let mut key = *key; let scope = match key.ident.ctxt.update_unchecked(|ctxt| { ctxt.reverse_glob_adjust(module.expansion, import.span) @@ -1874,6 +1965,105 @@ 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_overlap = 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 the edition ranges in the group do not overlap. + indices.sort_by_key(|&index| self.local_edition_redirects[index].range.start); + for &[previous, redirect] in indices.array_windows() { + let previous = &self.local_edition_redirects[previous]; + let redirect = &self.local_edition_redirects[redirect]; + if previous.range.last >= redirect.range.start + && diagnosed_overlap.insert(redirect.span) + { + self.dcx().span_err( + redirect.span, + format!( + "edition redirect range {}..={} overlaps with another range for `{}`", + redirect.range.start, redirect.range.last, 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.range.start); + redirects + .into_iter() + .map(|redirect| MetadataEditionRedirect { + range: redirect.range, + target: redirect.import_decl.res().expect_non_local(), + }) + .collect() + } + // Miscellaneous post-processing, including recording re-exports, // reporting conflicts, and reporting unresolved imports. fn finalize_resolutions_in( @@ -1899,18 +2089,30 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { decl.vis() }; let ident = ident.orig(orig_ident_span); - let child = |reexport_chain| ModChild { ident, res, vis, reexport_chain }; if let Some((ambig_binding1, ambig_binding2)) = decl.descent_to_ambiguity() { - let main = child(ambig_binding1.reexport_chain()); + let main = ModChild { + ident, + res, + vis, + reexport_chain: ambig_binding1.reexport_chain(), + edition_redirects: Default::default(), + }; let second = ModChild { ident, res: ambig_binding2.res().expect_non_local(), vis: ambig_binding2.vis(), reexport_chain: ambig_binding2.reexport_chain(), + edition_redirects: Default::default(), }; ambig_children.push(AmbigModChild { main, second }) } else { - children.push(child(decl.reexport_chain())); + children.push(ModChild { + ident, + res, + vis, + reexport_chain: decl.reexport_chain(), + edition_redirects: this.edition_redirects_for_decl(decl), + }); } } }); diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index f5f40a641b66e..125db53d90760 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1163,10 +1163,12 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } fn lookup_doc_alias_name(&mut self, path: &[Segment], ns: Namespace) -> Option<(DefId, Ident)> { - let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item_name: Symbol| { + let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item: Ident| { for resolution in r.resolutions(m).values() { - let Some(did) = - resolution.borrow(r).best_decl().and_then(|binding| binding.res().opt_def_id()) + let Some(did) = resolution + .borrow(r) + .best_decl_redir(item.span) + .and_then(|binding| binding.res().opt_def_id()) else { continue; }; @@ -1177,7 +1179,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { continue; } if let Some(d) = hir::find_attr!(r.tcx, did, Doc(d) => d) - && d.aliases.contains_key(&item_name) + && d.aliases.contains_key(&item.name) { return Some(did); } @@ -1189,7 +1191,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { for rib in self.ribs[ns].iter().rev() { let item = path[0].ident; if let RibKind::Module(module) | RibKind::Block(Some(module)) = rib.kind - && let Some(did) = find_doc_alias_name(self.r, module.to_module(), item.name) + && let Some(did) = find_doc_alias_name(self.r, module.to_module(), item) { return Some((did, item)); } @@ -1213,7 +1215,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let Res::Def(DefKind::Mod, module) = res.expect_full_res() && let module = self.r.expect_module(module) && let item = path[idx + 1].ident - && let Some(did) = find_doc_alias_name(self.r, module, item.name) + && let Some(did) = find_doc_alias_name(self.r, module, item) { return Some((did, item)); } @@ -2930,6 +2932,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let RibKind::Block(Some(module)) = rib.kind { self.r.add_module_candidates( module.to_module(), + segment.ident.span.with_ctxt(ctxt), &mut names, &filter_fn, Some(ctxt), @@ -2962,7 +2965,13 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = self.resolve_path(mod_path, Some(TypeNS), None, PathSource::Type) { - self.r.add_module_candidates(module, &mut names, &filter_fn, None); + self.r.add_module_candidates( + module, + path[path.len() - 1].ident.span, + &mut names, + &filter_fn, + None, + ); } } @@ -3074,7 +3083,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { false } - fn find_module(&self, def_id: DefId) -> Option<(Module<'ra>, ImportSuggestion)> { + fn find_module(&self, def_id: DefId, span: Span) -> Option<(Module<'ra>, ImportSuggestion)> { let mut result = None; let mut seen_modules = FxHashSet::default(); let mut worklist = vec![(self.r.graph_root.to_module(), ThinVec::new(), true)]; @@ -3085,48 +3094,57 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { break; } - in_module.for_each_child(self.r, |r, ident, orig_ident_span, _, name_binding| { - // abort if the module is already found or if name_binding is private external - if result.is_some() || !name_binding.vis().is_visible_locally() { - return; - } - if let Some(module_def_id) = name_binding.res().module_like_def_id() { - // form the path - let mut path_segments = path_segments.clone(); - path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let doc_visible = doc_visible - && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); - if module_def_id == def_id { - let path = Path { span: name_binding.span, segments: path_segments }; - result = Some(( - r.expect_module(module_def_id), - ImportSuggestion { - did: Some(def_id), - descr: "module", - path, - accessible: true, - doc_visible, - note: None, - via_import: false, - is_stable: true, - }, - )); - } else { - // add the module to the lookup - if seen_modules.insert(module_def_id) { - let module = r.expect_module(module_def_id); - worklist.push((module, path_segments, doc_visible)); + in_module.for_each_child_redir( + self.r, + span, + |r, ident, orig_ident_span, _, name_binding| { + // abort if the module is already found or if name_binding is private external + if result.is_some() || !name_binding.vis().is_visible_locally() { + return; + } + if let Some(module_def_id) = name_binding.res().module_like_def_id() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments + .push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let doc_visible = doc_visible + && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); + if module_def_id == def_id { + let path = Path { span: name_binding.span, segments: path_segments }; + result = Some(( + r.expect_module(module_def_id), + ImportSuggestion { + did: Some(def_id), + descr: "module", + path, + accessible: true, + doc_visible, + note: None, + via_import: false, + is_stable: true, + }, + )); + } else { + // add the module to the lookup + if seen_modules.insert(module_def_id) { + let module = r.expect_module(module_def_id); + worklist.push((module, path_segments, doc_visible)); + } } } - } - }); + }, + ); } result } - fn collect_enum_ctors(&self, def_id: DefId) -> Option> { - self.find_module(def_id).map(|(enum_module, enum_import_suggestion)| { + fn collect_enum_ctors( + &self, + def_id: DefId, + span: Span, + ) -> Option> { + self.find_module(def_id, span).map(|(enum_module, enum_import_suggestion)| { let mut variants = Vec::new(); enum_module.for_each_child(self.r, |_, ident, orig_ident_span, _, name_binding| { if let Res::Def(DefKind::Ctor(CtorOf::Variant, kind), def_id) = name_binding.res() { @@ -3148,7 +3166,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { def_id: DefId, span: Span, ) { - let Some(variant_ctors) = self.collect_enum_ctors(def_id) else { + let Some(variant_ctors) = self.collect_enum_ctors(def_id, span) else { err.note("you might have meant to use one of the enum's variants"); return; }; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index d5b1457865891..65ca41dbb3d7c 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -24,6 +24,7 @@ use std::cell::RefMut; use std::collections::BTreeSet; use std::ops::ControlFlow; +use std::range::RangeInclusive; use std::sync::{Arc, OnceLock}; use std::{fmt, mem}; @@ -69,6 +70,7 @@ use rustc_middle::query::Providers; use rustc_middle::ty::{self, RegisteredTools, TyCtxt, TyCtxtFeed, Visibility}; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::{LocalModId, ModId}; +use rustc_span::edition::Edition; use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind, SyntaxContext, Transparency}; use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; use rustc_structures::CrateType; @@ -696,8 +698,13 @@ struct ModuleData<'ra> { globs: CmRefCell>>, /// Used to memoize the traits in this module for faster searches through all traits in scope. + /// + /// Redirected trait declarations can select different traits in each edition. traits: CmRefCell< - Option, Option>, bool /* lint ambiguous */)]>>, + FxHashMap< + Edition, + Box<[(Symbol, Decl<'ra>, Option>, bool /* lint ambiguous */)]>, + >, >, /// Span of the module itself. Used for error reporting. @@ -753,7 +760,7 @@ impl<'ra> ModuleData<'ra> { no_implicit_prelude, glob_importers: CmRefCell::new(Vec::new()), globs: CmRefCell::new(Vec::new()), - traits: CmRefCell::new(None), + traits: CmRefCell::new(FxHashMap::default()), span, expansion, self_decl, @@ -800,6 +807,7 @@ impl<'ra> ModuleData<'ra> { } impl<'ra> Module<'ra> { + /// Visits children and panics if any edition redirects are encountered. fn for_each_child<'tcx, R: AsRef>>( self, resolver: &R, @@ -813,25 +821,43 @@ impl<'ra> Module<'ra> { } } - fn for_each_child_mut<'tcx, R: AsMut>>( + /// Visits children after applying edition redirects for `redirect_span`. + fn for_each_child_redir<'tcx, R: AsRef>>( + self, + resolver: &R, + redirect_span: Span, + mut f: impl FnMut(&R, IdentKey, Span, Namespace, Decl<'ra>), + ) { + for (key, name_resolution) in resolver.as_ref().resolutions(self).iter() { + let name_resolution = name_resolution.borrow_checked(resolver.as_ref()); + if let Some(decl) = name_resolution.best_decl_redir(redirect_span) { + f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); + } + } + } + + /// Mutable variant of `for_each_child_redir`. + fn for_each_child_redir_mut<'tcx, R: AsMut>>( self, resolver: &mut R, + redirect_span: Span, mut f: impl FnMut(&mut R, IdentKey, Span, Namespace, Decl<'ra>), ) { for (key, name_resolution) in resolver.as_mut().resolutions(self).iter() { let name_resolution = name_resolution.borrow(resolver.as_mut()); - if let Some(decl) = name_resolution.best_decl() { + if let Some(decl) = name_resolution.best_decl_redir(redirect_span) { f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); } } } /// This modifies `self` in place. The traits will be stored in `self.traits`. - fn ensure_traits<'tcx>(self, resolver: &Resolver<'ra, 'tcx>) { + fn ensure_traits<'tcx>(self, resolver: &Resolver<'ra, 'tcx>, redirect_span: Span) { + let edition = redirect_span.edition(); let mut traits = self.traits.borrow_mut_checked(resolver); - if traits.is_none() { + traits.entry(edition).or_insert_with(|| { let mut collected_traits = Vec::new(); - self.for_each_child(resolver, |r, ident, _, ns, mut decl| { + self.for_each_child_redir(resolver, redirect_span, |r, ident, _, ns, mut decl| { if ns != TypeNS { return; } @@ -859,8 +885,8 @@ impl<'ra> Module<'ra> { decl = ambig_decl; } }); - *traits = Some(collected_traits.into_boxed_slice()); - } + collected_traits.into_boxed_slice() + }); } // `self` resolves to the first module ancestor that `is_normal`. @@ -1031,6 +1057,23 @@ struct DeclData<'ra> { parent_module: Option>, } +#[derive(Clone, Copy, Debug)] +struct EditionRedirectDecl<'ra> { + range: RangeInclusive, + 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, + range: RangeInclusive, + 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 /// they have the same contents. type Decl<'ra> = Interned<'ra, DeclData<'ra>>; @@ -1382,6 +1425,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 @@ -2116,14 +2161,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { cmr.visit_scopes(scope_set, parent_scope, ctxt, sp, None, |mut this, scope, _, _| { match scope { Scope::ModuleNonGlobs(module, _) => { - this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); + this.get_mut().traits_in_module(module, sp, assoc_item, &mut found_traits); } Scope::ModuleGlobs(..) => { // Already handled in `ModuleNonGlobs` (but see #144993). } Scope::StdLibPrelude => { if let Some(module) = this.prelude { - this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); + this.get_mut().traits_in_module(module, sp, assoc_item, &mut found_traits); } } Scope::ExternPreludeItems @@ -2141,14 +2186,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.get(&redirect_span.edition()).unwrap(); + for &(trait_name, trait_binding, trait_module, lint_ambiguous) in traits.iter() { if self.trait_may_have_item(trait_module, assoc_item) { let def_id = trait_binding.res().def_id(); let import_ids = self.find_transitive_imports(&trait_binding.kind, trait_name); @@ -2236,7 +2281,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { orig_ident_span: Span, ) -> NameResolutionRef<'ra> { *self.resolutions_mut(module).entry(key).or_insert_with(|| { - self.arenas.alloc_name_resolution(NameResolution::new(orig_ident_span)) + self.arenas.alloc_name_resolution(NameResolution::new( + None, + Box::default(), + orig_ident_span, + )) }) } diff --git a/compiler/rustc_serialize/src/serialize.rs b/compiler/rustc_serialize/src/serialize.rs index 32a301a28ae8e..6c4c40d714d19 100644 --- a/compiler/rustc_serialize/src/serialize.rs +++ b/compiler/rustc_serialize/src/serialize.rs @@ -7,6 +7,7 @@ use std::hash::{BuildHasher, Hash}; use std::marker::{PhantomData, PointeeSized}; use std::num::{NonZero, ZeroablePrimitive}; use std::path; +use std::range::RangeInclusive; use std::rc::Rc; use std::sync::Arc; @@ -425,6 +426,19 @@ impl, T2: Decodable> Decodable for Result> Encodable for RangeInclusive { + fn encode(&self, s: &mut S) { + self.start.encode(s); + self.last.encode(s); + } +} + +impl> Decodable for RangeInclusive { + fn decode(d: &mut D) -> Self { + RangeInclusive { start: T::decode(d), last: T::decode(d) } + } +} + macro_rules! peel { ($name:ident, $($other:ident,)*) => (tuple! { $($other,)* }) } diff --git a/compiler/rustc_span/src/edition.rs b/compiler/rustc_span/src/edition.rs index e24e05df113b4..235a0bb97dc8b 100644 --- a/compiler/rustc_span/src/edition.rs +++ b/compiler/rustc_span/src/edition.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use rustc_macros::{BlobDecodable, Encodable, StableHash}; /// The edition of the compiler. (See [RFC 2052](https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md).) -#[derive(Clone, Copy, Hash, PartialEq, PartialOrd, Debug, Encodable, BlobDecodable, Eq)] +#[derive(Clone, Copy, Hash, PartialEq, PartialOrd, Debug, Encodable, BlobDecodable, Eq, Ord)] #[derive(StableHash)] pub enum Edition { // When adding new editions, be sure to do the following: diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 7665df4a4e5ae..8652123f13eb9 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1806,6 +1806,7 @@ symbols! { rustc_dump_variances_of_opaques, rustc_dump_vtable, rustc_dyn_incompatible_trait, + rustc_edition_redirect, rustc_effective_visibility, rustc_eii_foreign_item, rustc_evaluate_where_clauses, diff --git a/tests/ui/README.md b/tests/ui/README.md index b80c8215c1cf8..a07ddee601307 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -511,6 +511,11 @@ The `dyn` keyword is used to highlight that calls to methods on the associated T See [`dyn` keyword](https://doc.rust-lang.org/std/keyword.dyn.html). +## `tests/ui/edition-redirect/`: Edition-dependent item resolution + +Tests for resolving external items and associated items to different definitions +depending on the edition of the use site. + ## `tests/ui/editions/`: Rust edition-specific peculiarities These tests run in specific Rust editions, such as Rust 2015 or Rust 2018, and check errors and functionality related to specific now-deprecated idioms and features. diff --git a/tests/ui/edition-redirect/ambiguity.old.stderr b/tests/ui/edition-redirect/ambiguity.old.stderr new file mode 100644 index 0000000000000..5c2caa6704b51 --- /dev/null +++ b/tests/ui/edition-redirect/ambiguity.old.stderr @@ -0,0 +1,23 @@ +error[E0659]: `Item` is ambiguous + --> $DIR/ambiguity.rs:13:17 + | +LL | fn check(_: Item) {} + | ^^^^ ambiguous name + | + = note: ambiguous because of multiple glob imports of a name in the same module +note: `Item` could refer to the struct imported here + --> $DIR/ambiguity.rs:10:9 + | +LL | use edition_redirect::ambiguity::alias_a::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding an explicit import of `Item` to disambiguate +note: `Item` could also refer to the struct imported here + --> $DIR/ambiguity.rs:11:9 + | +LL | use edition_redirect::ambiguity::alias_b::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding an explicit import of `Item` to disambiguate + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0659`. diff --git a/tests/ui/edition-redirect/ambiguity.rs b/tests/ui/edition-redirect/ambiguity.rs new file mode 100644 index 0000000000000..47a6107fb1f55 --- /dev/null +++ b/tests/ui/edition-redirect/ambiguity.rs @@ -0,0 +1,17 @@ +//@ revisions: old current +//@[old] edition: 2021 +//@[current] edition: 2024 +//@ aux-build: basic.rs +//@[current] check-pass + +extern crate basic as edition_redirect; + +mod downstream_ambiguity { + use edition_redirect::ambiguity::alias_a::*; + use edition_redirect::ambiguity::alias_b::*; + + fn check(_: Item) {} + //[old]~^ ERROR `Item` is ambiguous +} + +fn main() {} diff --git a/tests/ui/edition-redirect/auxiliary/basic.rs b/tests/ui/edition-redirect/auxiliary/basic.rs new file mode 100644 index 0000000000000..2e48902e20ce0 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/basic.rs @@ -0,0 +1,109 @@ +#![feature(rustc_attrs)] + +pub struct Oldest; +pub struct Middle; + +#[rustc_edition_redirect = "2021..=2021"] +pub use Middle as Redirected; +#[rustc_edition_redirect = "..=2018"] +pub use Oldest as Redirected; +pub struct Redirected; + +pub mod oldest_module { + pub const VALUE: usize = 1; +} + +pub mod middle_module { + pub const VALUE: usize = 2; +} + +#[rustc_edition_redirect = "..=2018"] +pub use oldest_module as redirected_module; +#[rustc_edition_redirect = "2021..=2021"] +pub use middle_module as redirected_module; +pub mod redirected_module { + pub const VALUE: usize = 3; +} + +pub mod use_targets { + pub struct OldestUse; + pub struct MiddleUse; + pub struct CurrentUse; +} + +#[rustc_edition_redirect = "..=2018"] +pub use use_targets::OldestUse as RedirectedUse; +#[rustc_edition_redirect = "2021..=2021"] +pub use use_targets::MiddleUse as RedirectedUse; +pub use use_targets::CurrentUse as RedirectedUse; + +pub mod same_redirect_a { + pub use crate::RedirectedUse as Item; +} + +pub mod same_redirect_b { + pub use crate::RedirectedUse as Item; +} + +pub mod same_redirects { + pub use crate::same_redirect_a::*; + pub use crate::same_redirect_b::*; +} + +pub mod reexport_scope { + pub struct Old; + pub struct Current; + + #[rustc_edition_redirect = "..=2021"] + pub use self::OldAlias as Redirected; + pub use self::Current as Redirected; + + pub use self::Old as OldAlias; +} + +pub use reexport_scope::Redirected as ScopedRedirected; + +#[macro_export] +macro_rules! oldest_macro { + () => { 1 }; +} + +#[macro_export] +macro_rules! middle_macro { + () => { 2 }; +} + +#[rustc_edition_redirect = "..=2018"] +pub use oldest_macro as redirected_macro; +#[rustc_edition_redirect = "2021..=2021"] +pub use middle_macro as redirected_macro; +#[macro_export] +macro_rules! redirected_macro { + () => { 3 }; +} + +pub mod ambiguity { + pub struct Shared; + pub struct OldA; + pub struct OldB; + + pub mod alias_a { + #[rustc_edition_redirect = "..=2021"] + pub use super::OldA as Item; + pub use super::Shared as Item; + } + + pub mod alias_b { + #[rustc_edition_redirect = "..=2021"] + pub use super::OldB as Item; + pub use super::Shared as Item; + } +} + +fn local_resolution_uses_default_items() { + let _: Redirected = Redirected; + let _: use_targets::CurrentUse = RedirectedUse; + let _: reexport_scope::Current = reexport_scope::Redirected; + const _: [(); 3] = [(); redirected_module::VALUE]; + const _: [(); 3] = [(); redirected_macro!()]; +} diff --git a/tests/ui/edition-redirect/auxiliary/macro-2018.rs b/tests/ui/edition-redirect/auxiliary/macro-2018.rs new file mode 100644 index 0000000000000..307240090c352 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-2018.rs @@ -0,0 +1,37 @@ +//@ edition: 2018 + +#[macro_export] +macro_rules! import_all { + () => { + use macro_source::*; + }; +} + +#[macro_export] +macro_rules! macro_use_source { + () => { + #[macro_use] + extern crate macro_source; + }; +} + +#[macro_export] +macro_rules! call_redirected_trait { + () => { + ().redirected_method() + }; +} + +#[macro_export] +macro_rules! redirected_type { + () => { + RedirectedItem + }; +} + +#[macro_export] +macro_rules! redirected_value { + () => { + RedirectedItem + }; +} diff --git a/tests/ui/edition-redirect/auxiliary/macro-2024.rs b/tests/ui/edition-redirect/auxiliary/macro-2024.rs new file mode 100644 index 0000000000000..8eebf7d35ac27 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-2024.rs @@ -0,0 +1,37 @@ +//@ edition: 2024 + +#[macro_export] +macro_rules! import_all { + () => { + use macro_source::*; + }; +} + +#[macro_export] +macro_rules! macro_use_source { + () => { + #[macro_use] + extern crate macro_source; + }; +} + +#[macro_export] +macro_rules! call_redirected_trait { + () => { + ().redirected_method() + }; +} + +#[macro_export] +macro_rules! redirected_type { + () => { + RedirectedItem + }; +} + +#[macro_export] +macro_rules! redirected_value { + () => { + RedirectedItem + }; +} diff --git a/tests/ui/edition-redirect/auxiliary/macro-source.rs b/tests/ui/edition-redirect/auxiliary/macro-source.rs new file mode 100644 index 0000000000000..ad0561cc82d3c --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/macro-source.rs @@ -0,0 +1,96 @@ +//@ edition: 2024 + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +pub struct Old; +pub struct Current; + +#[rustc_edition_redirect = "..=2018"] +pub use Old as Name; +pub use Current as Name; + +mod diagnostic_targets { + #[doc(alias = "OldAlias")] + pub struct AliasCarrier; + + pub trait Candidate {} + + pub mod diagnostic_module { + pub enum DiagnosticEnum { + Variant(u8), + } + } +} + +#[rustc_edition_redirect = "..=2018"] +pub use diagnostic_targets::AliasCarrier as AliasCarrier; +pub struct AliasCarrier; + +#[rustc_edition_redirect = "..=2018"] +pub use diagnostic_targets::Candidate as Candidate; +pub struct Candidate; + +#[rustc_edition_redirect = "..=2018"] +pub use diagnostic_targets::diagnostic_module as diagnostic_module; +pub mod diagnostic_module { + pub enum DiagnosticEnum { + CurrentVariant(u8), + } +} + +pub mod trait_prelude { + pub struct OldItem; + pub struct CurrentItem; + + #[rustc_edition_redirect = "..=2018"] + pub use OldItem as RedirectedItem; + pub use CurrentItem as RedirectedItem; + + pub struct OldMarker; + pub struct CurrentMarker; + + mod old { + pub trait RedirectedTrait { + fn redirected_method(&self) -> super::OldMarker; + } + + impl RedirectedTrait for () { + fn redirected_method(&self) -> super::OldMarker { + super::OldMarker + } + } + } + + #[rustc_edition_redirect = "..=2018"] + pub use old::RedirectedTrait as RedirectedTrait; + + pub trait RedirectedTrait { + fn redirected_method(&self) -> CurrentMarker; + } + + impl RedirectedTrait for () { + fn redirected_method(&self) -> CurrentMarker { + CurrentMarker + } + } +} + +#[macro_export] +macro_rules! old_macro { + () => { + pub type Selected = $crate::Old; + }; +} + +#[rustc_edition_redirect = "..=2018"] +pub use old_macro as redirected_macro; + +#[macro_export] +macro_rules! redirected_macro { + () => { + pub type Selected = $crate::Current; + }; +} + +pub mod nested {} diff --git a/tests/ui/edition-redirect/auxiliary/reexport-current.rs b/tests/ui/edition-redirect/auxiliary/reexport-current.rs new file mode 100644 index 0000000000000..c65a8196383ac --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-current.rs @@ -0,0 +1,5 @@ +//@ edition: 2024 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-old.rs b/tests/ui/edition-redirect/auxiliary/reexport-old.rs new file mode 100644 index 0000000000000..9df0165313ea0 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-old.rs @@ -0,0 +1,5 @@ +//@ edition: 2021 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs b/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs new file mode 100644 index 0000000000000..f27156a68a3be --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-preserving.rs @@ -0,0 +1,7 @@ +//@ edition: 2021 +//@ aux-crate: reexport_source=reexport-source.rs + +pub use reexport_source::Current as Item; +// Redirects are selected when this crate first imports the external name, so +// both re-exports are fixed according to this crate's edition. +pub use reexport_source::redirected_module::Child; diff --git a/tests/ui/edition-redirect/auxiliary/reexport-source.rs b/tests/ui/edition-redirect/auxiliary/reexport-source.rs new file mode 100644 index 0000000000000..f2717a1e90b0c --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/reexport-source.rs @@ -0,0 +1,35 @@ +//@ edition: 2024 + +#![feature(rustc_attrs)] + +pub struct Old; + +#[rustc_edition_redirect = "..=2021"] +pub use Old as Current; +pub struct Current; + +pub fn old() -> Old { + Old +} + +pub fn current() -> Current { + Current +} + +pub mod old_module { + pub struct Child; +} + +#[rustc_edition_redirect = "..=2021"] +pub use old_module as redirected_module; +pub mod redirected_module { + pub struct Child; +} + +pub fn old_child() -> old_module::Child { + old_module::Child +} + +pub fn current_child() -> redirected_module::Child { + redirected_module::Child +} diff --git a/tests/ui/edition-redirect/auxiliary/stability.rs b/tests/ui/edition-redirect/auxiliary/stability.rs new file mode 100644 index 0000000000000..8eb6dc0070ab6 --- /dev/null +++ b/tests/ui/edition-redirect/auxiliary/stability.rs @@ -0,0 +1,20 @@ +#![feature(allow_internal_unstable, rustc_attrs, staged_api)] +#![stable(feature = "edition_redirect_stability", since = "1.0.0")] + +#[doc(hidden)] +#[unstable(feature = "edition_redirect_old", issue = "none")] +#[macro_export] +macro_rules! old_macro { + () => { 1 }; +} + +#[rustc_edition_redirect = "..=2021"] +#[stable(feature = "edition_redirect_stability", since = "1.0.0")] +pub use old_macro as redirected_macro; + +#[stable(feature = "edition_redirect_stability", since = "1.0.0")] +#[allow_internal_unstable(edition_redirect_old)] +#[macro_export] +macro_rules! redirected_macro { + () => { 2 }; +} diff --git a/tests/ui/edition-redirect/basic.rs b/tests/ui/edition-redirect/basic.rs new file mode 100644 index 0000000000000..726332cd21350 --- /dev/null +++ b/tests/ui/edition-redirect/basic.rs @@ -0,0 +1,65 @@ +//@ revisions: edition2018 edition2021 edition2024 +//@[edition2018] edition: 2018 +//@[edition2021] edition: 2021 +//@[edition2024] edition: 2024 +//@ aux-build: basic.rs +//@ check-pass + +#[macro_use] +extern crate basic as edition_redirect; + +use edition_redirect::{ + Redirected as ImportedRedirected, redirected_macro as imported_redirected_macro, +}; +use edition_redirect::{ + reexport_scope::Current as ExpectedScopedRedirected, + use_targets::CurrentUse as ExpectedReexportedUse, +}; + +#[cfg(edition2018)] +use edition_redirect::{ + Oldest as ExpectedRedirected, use_targets::OldestUse as ExpectedRedirectedUse, +}; +#[cfg(edition2021)] +use edition_redirect::{ + Middle as ExpectedRedirected, use_targets::MiddleUse as ExpectedRedirectedUse, +}; +#[cfg(edition2024)] +use edition_redirect::{ + Redirected as ExpectedRedirected, use_targets::CurrentUse as ExpectedRedirectedUse, +}; + +#[cfg(edition2018)] +const EXPECTED_VALUE: usize = 1; +#[cfg(edition2021)] +const EXPECTED_VALUE: usize = 2; +#[cfg(edition2024)] +const EXPECTED_VALUE: usize = 3; + +fn explicit() { + let _: ExpectedRedirected = edition_redirect::Redirected; + let _: ExpectedRedirectedUse = edition_redirect::RedirectedUse; + let _: ExpectedScopedRedirected = edition_redirect::ScopedRedirected; + let _: edition_redirect::same_redirects::Item = ExpectedReexportedUse; + const _: [(); EXPECTED_VALUE] = [(); edition_redirect::redirected_module::VALUE]; + const _: [(); EXPECTED_VALUE] = [(); edition_redirect::redirected_macro!()]; + const _: [(); EXPECTED_VALUE] = [(); redirected_macro!()]; + let _: ImportedRedirected = ExpectedRedirected; + const _: [(); EXPECTED_VALUE] = [(); imported_redirected_macro!()]; +} + +mod glob { + use super::{EXPECTED_VALUE, ExpectedRedirected, ExpectedRedirectedUse}; + use edition_redirect::*; + + fn check() { + let _: Redirected = ExpectedRedirected; + let _: RedirectedUse = ExpectedRedirectedUse; + const _: [(); EXPECTED_VALUE] = [(); redirected_module::VALUE]; + const _: [(); EXPECTED_VALUE] = [(); redirected_macro!()]; + } +} + +fn main() { + explicit(); +} diff --git a/tests/ui/edition-redirect/diagnostic.rs b/tests/ui/edition-redirect/diagnostic.rs new file mode 100644 index 0000000000000..89254cf83c498 --- /dev/null +++ b/tests/ui/edition-redirect/diagnostic.rs @@ -0,0 +1,51 @@ +//@ edition: 2018 +//@ aux-build: macro-source.rs +//@ check-fail + +extern crate macro_source; + +// A macro exported at the crate root should still be suggested when it is +// incorrectly imported through a module, even when the root binding has an +// edition redirect. +use macro_source::nested::redirected_macro; +//~^ ERROR unresolved import `macro_source::nested::redirected_macro` +//~| HELP a macro with this name exists at the root of the crate +//~| SUGGESTION macro_source::redirected_macro +//~| HELP consider importing this trait +//~| SUGGESTION use macro_source::Candidate; + +// A missing import from a module that also contains redirected names should +// produce the usual unresolved-import diagnostic. +use macro_source::NoSuchImport; +//~^ ERROR unresolved import `macro_source::NoSuchImport` + +// In edition 2018, `Candidate` redirects to a trait, so it should be suggested +// as an import for a missing unqualified trait. The default `Candidate` is a +// struct. +fn import_candidate() {} +//~^ ERROR cannot find trait `Candidate` in this scope + +// A misspelled qualified trait name should likewise suggest the trait selected +// in edition 2018. +fn typo_candidate() {} +//~^ ERROR cannot find trait `Canddate` in crate `macro_source` +//~| HELP a trait with a similar name exists +//~| SUGGESTION Candidate + +// Doc aliases from the edition-selected target should be available in typo +// suggestions. The default `AliasCarrier` does not have this alias. +fn doc_alias(_: macro_source::OldAlias) {} +//~^ ERROR cannot find type `OldAlias` in crate `macro_source` +//~| HELP has a name defined in the doc alias attribute as `OldAlias` +//~| SUGGESTION AliasCarrier + +// An enum reached through a redirected module should still produce a suggestion +// using a variant from the selected module. +fn enum_variant() -> macro_source::diagnostic_module::DiagnosticEnum { + macro_source::diagnostic_module::DiagnosticEnum(0) + //~^ ERROR cannot find function, tuple struct or tuple variant `DiagnosticEnum` + //~| HELP try to construct the enum's variant + //~| SUGGESTION macro_source::diagnostic_module::DiagnosticEnum::Variant +} + +fn main() {} diff --git a/tests/ui/edition-redirect/diagnostic.stderr b/tests/ui/edition-redirect/diagnostic.stderr new file mode 100644 index 0000000000000..925f4900801e8 --- /dev/null +++ b/tests/ui/edition-redirect/diagnostic.stderr @@ -0,0 +1,78 @@ +error[E0432]: unresolved import `macro_source::nested::redirected_macro` + --> $DIR/diagnostic.rs:10:5 + | +LL | use macro_source::nested::redirected_macro; + | ^^^^^^^^^^^^^^^^^^^^^^---------------- + | | + | no `redirected_macro` in `nested` + | + = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined +help: a macro with this name exists at the root of the crate + | +LL - use macro_source::nested::redirected_macro; +LL + use macro_source::redirected_macro; + | + +error[E0432]: unresolved import `macro_source::NoSuchImport` + --> $DIR/diagnostic.rs:19:5 + | +LL | use macro_source::NoSuchImport; + | ^^^^^^^^^^^^^^------------ + | | + | no `NoSuchImport` in the root + +error[E0405]: cannot find trait `Candidate` in this scope + --> $DIR/diagnostic.rs:25:24 + | +LL | fn import_candidate() {} + | ^^^^^^^^^ not found in this scope + | +help: consider importing this trait + | +LL + use macro_source::Candidate; + | + +error[E0405]: cannot find trait `Canddate` in crate `macro_source` + --> $DIR/diagnostic.rs:30:36 + | +LL | fn typo_candidate() {} + | ^^^^^^^^ not found in `macro_source` + | +note: similarly named trait `Candidate` defined here + --> $DIR/auxiliary/macro-source.rs:17:5 + | +LL | pub trait Candidate {} + | ^^^^^^^^^^^^^^^^^^^ +help: a trait with a similar name exists + | +LL | fn typo_candidate() {} + | + + +error[E0425]: cannot find type `OldAlias` in crate `macro_source` + --> $DIR/diagnostic.rs:37:31 + | +LL | fn doc_alias(_: macro_source::OldAlias) {} + | ^^^^^^^^ + | +help: `AliasCarrier` has a name defined in the doc alias attribute as `OldAlias` + | +LL - fn doc_alias(_: macro_source::OldAlias) {} +LL + fn doc_alias(_: macro_source::AliasCarrier) {} + | + +error[E0423]: cannot find function, tuple struct or tuple variant `DiagnosticEnum` in module `macro_source::diagnostic_module` + --> $DIR/diagnostic.rs:45:38 + | +LL | macro_source::diagnostic_module::DiagnosticEnum(0) + | ^^^^^^^^^^^^^^ + | + = note: an enum named `macro_source::diagnostic_module::DiagnosticEnum` exists in another namespace +help: try to construct the enum's variant + | +LL | macro_source::diagnostic_module::DiagnosticEnum::Variant(0) + | +++++++++ + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0405, E0423, E0425, E0432. +For more information about an error, try `rustc --explain E0405`. diff --git a/tests/ui/edition-redirect/invalid.rs b/tests/ui/edition-redirect/invalid.rs new file mode 100644 index 0000000000000..51d570e45ee1d --- /dev/null +++ b/tests/ui/edition-redirect/invalid.rs @@ -0,0 +1,79 @@ +#![feature(rustc_attrs)] + +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; +} + +#[rustfmt::skip] +#[rustc_edition_redirect = "..=2024"] +//~^ ERROR `#[rustc_edition_redirect]` can only be applied to a single import +pub use source::{Current}; +#[rustc_edition_redirect = "..=2024"] +//~^ 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 OverlapTargetA = (); +pub type OverlapTargetB = (); + +#[rustc_edition_redirect = "..=2021"] +pub use OverlapTargetA as Overlap; +#[rustc_edition_redirect = "2021..=2024"] +//~^ ERROR edition redirect range 2021..=2024 overlaps with another range for `Overlap` +pub use OverlapTargetB as Overlap; + +pub type Overlap = (); + +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 range in edition redirect +pub use source::Old as InvalidEdition; +#[rustc_edition_redirect = "2024"] +//~^ ERROR invalid edition range in edition redirect +pub use source::Old as MissingRangeOperator; +#[rustc_edition_redirect = "2021.."] +//~^ ERROR invalid edition range in edition redirect +pub use source::Old as MissingInclusiveEnd; +#[rustc_edition_redirect = "..2021"] +//~^ ERROR invalid edition range in edition redirect +pub use source::Old as ExclusiveRange; +#[rustc_edition_redirect = "2024..=2021"] +//~^ ERROR invalid edition range in edition redirect +pub use source::Old as InvertedRange; + +fn main() {} diff --git a/tests/ui/edition-redirect/invalid.stderr b/tests/ui/edition-redirect/invalid.stderr new file mode 100644 index 0000000000000..ff50ccb8a66eb --- /dev/null +++ b/tests/ui/edition-redirect/invalid.stderr @@ -0,0 +1,102 @@ +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: edition redirect range 2021..=2024 overlaps with another range for `Overlap` + --> $DIR/invalid.rs:44:1 + | +LL | #[rustc_edition_redirect = "2021..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: edition redirect for `Restricted` must have the same visibility as its default item + --> $DIR/invalid.rs:52:1 + | +LL | #[rustc_edition_redirect = "..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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 range in edition redirect + --> $DIR/invalid.rs:63:1 + | +LL | #[rustc_edition_redirect = "not an edition"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:66:1 + | +LL | #[rustc_edition_redirect = "2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:69:1 + | +LL | #[rustc_edition_redirect = "2021.."] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:72:1 + | +LL | #[rustc_edition_redirect = "..2021"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid edition range in edition redirect + --> $DIR/invalid.rs:75:1 + | +LL | #[rustc_edition_redirect = "2024..=2021"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[rustc_edition_redirect]` can only be applied to a single import + --> $DIR/invalid.rs:15:1 + | +LL | #[rustc_edition_redirect = "..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a separate, non-braced `use` item + +error: `#[rustc_edition_redirect]` can only be applied to a single import + --> $DIR/invalid.rs:18:1 + | +LL | #[rustc_edition_redirect = "..=2024"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use a separate, non-braced `use` item + +error: aborting due to 14 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() {}