Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 116 additions & 2 deletions compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ use std::path::PathBuf;

use rustc_ast::{LitIntType, LitKind, MetaItemLit};
use rustc_feature::AttributeStability;
use rustc_hir::LangItem;
use rustc_hir::attrs::{
BorrowckGraphvizFormatKind, CguFields, CguKind, DivergingBlockBehavior,
DivergingFallbackBehavior, RustcCleanAttribute, RustcCleanQueries, RustcMirKind,
DivergingFallbackBehavior, EditionRedirect, RustcCleanAttribute, RustcCleanQueries,
RustcMirKind,
};
use rustc_hir::target::GenericParamKind;
use rustc_hir::{LangItem, find_attr};
use rustc_span::Symbol;
use rustc_span::edition::Edition;

use super::prelude::*;
use super::util::parse_single_integer;
Expand Down Expand Up @@ -329,6 +331,118 @@ impl AttributeParser for RustcCguTestAttributeParser {
}
}

pub(crate) struct RustcEditionRedirectParser;

impl CombineAttributeParser for RustcEditionRedirectParser {
const PATH: &[Symbol] = &[sym::rustc_edition_redirect];
type Item = EditionRedirect;
const CONVERT: ConvertFn<Self::Item> = |items, _| AttributeKind::RustcEditionRedirect(items);
const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[
Allow(Target::Const),
Allow(Target::Enum),
Allow(Target::ExternCrate),
Allow(Target::Fn),
Allow(Target::MacroDef),
Allow(Target::Mod),
Allow(Target::Static),
Allow(Target::Struct),
Allow(Target::Trait),
Allow(Target::TraitAlias),
Allow(Target::TyAlias),
Allow(Target::Union),
Allow(Target::Use),
]);
const TEMPLATE: AttributeTemplate =
template!(List: &[r#"before = "2024", target(path::to::item)"#]);
const STABILITY: AttributeStability = unstable!(edition_redirect);

fn extend(
cx: &mut AcceptContext<'_, '_>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let list = cx.expect_list(args, cx.attr_span)?;
let mut before = None;
let mut target = None;

for item in list.mixed() {
let Some(meta) = item.meta_item() else {
cx.adcx().expected_identifier(item.span());
return None;
};
let Some(name) = meta.path().word() else {
cx.adcx()
.expected_specific_argument(meta.path().span(), &[sym::before, sym::target]);
return None;
};
match name.name {
sym::before if before.is_none() => {
let Some(value) =
cx.expect_name_value(meta.args(), item.span(), Some(sym::before))
else {
return None;
};
let Some(value) = cx.expect_string_literal(value) else { return None };
before = Some(value);
}
sym::target if target.is_none() => {
let Some(single) = cx.expect_single_element_list(meta.args(), item.span())
else {
return None;
};
let Some(target_path) = single.meta_item_no_args() else {
cx.adcx().expected_not_literal(single.span());
return None;
};
target = Some(target_path.path().0.clone());
}
sym::before | sym::target => {
cx.adcx().duplicate_key(item.span(), name.name);
return None;
}
_ => {
cx.adcx().expected_specific_argument(item.span(), &[sym::before, sym::target]);
return None;
}
}
}

let Some(before) = before else {
cx.dcx().span_err(cx.attr_span, "missing `before` argument");
return None;
};
let Some(target) = target else {
cx.dcx().span_err(cx.attr_span, "missing `target` argument");
return None;
};
let before = match before.as_str().parse::<Edition>() {
Ok(before) => before,
Err(()) => {
cx.dcx().span_err(cx.attr_span, "invalid edition in edition redirect");
return None;
}
};
Comment on lines +417 to +423

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the behavior of before = "future"? Should it be supported or be an error? Either way it looks like there are no tests for it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before = "future" should be supported just like any other edition.


Some(EditionRedirect { before, target, span: cx.attr_span })
}

fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, _attr_span: Span) {
let redirects = find_attr!(
cx.parsed_attrs,
RustcEditionRedirect(redirects) => redirects
)
.unwrap();

for (index, redirect) in redirects.iter().enumerate() {
if redirects[..index].iter().any(|existing| existing.before == redirect.before) {
cx.emit_err(diagnostics::MultipleEditionRedirects {
span: redirect.span,
edition: redirect.before.to_string(),
});
}
}
}
}

pub(crate) struct RustcDeprecatedSafe2024Parser;

impl SingleAttributeParser for RustcDeprecatedSafe2024Parser {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ attribute_parsers!(
Combine<RustcAllowConstFnUnstableParser>,
Combine<RustcCleanParser>,
Combine<RustcDumpLayoutParser>,
Combine<RustcEditionRedirectParser>,
Combine<RustcMirParser>,
Combine<RustcThenThisWouldNeedParser>,
Combine<TargetFeatureParser>,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_attr_parsing/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ pub(crate) struct UnreachableCfgSelectPredicateWildcard {
pub wildcard_span: Span,
}

#[derive(Diagnostic)]
#[diag("multiple edition redirects before edition {$edition}")]
pub(crate) struct MultipleEditionRedirects {
#[primary_span]
pub span: Span,
pub edition: String,
}

#[derive(Diagnostic)]
#[diag("must be a name of an associated function")]
pub(crate) struct MustBeNameOfAssociatedFunction {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[
sym::rustc_eii_foreign_item,
sym::rustc_allowed_through_unstable_modules,
sym::rustc_deprecated_safe_2024,
sym::rustc_edition_redirect,
sym::rustc_pub_transparent,

// ==========================================================================
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ declare_features! (
(internal, const_param_ty_unchecked, "1.97.0", None),
/// Allows writing custom MIR
(internal, custom_mir, "1.65.0", None),
/// Allows defining edition redirects and preserving redirects on re-exports.
(internal, edition_redirect, "CURRENT_RUSTC_VERSION", None),
/// Implementation details of externally implementable items
(internal, eii_internals, "1.94.0", None),
/// Implementation details of field representing types.
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use rustc_data_structures::fx::FxIndexMap;
use rustc_error_messages::{DiagArgValue, IntoDiagArg};
use rustc_macros::{Decodable, Encodable, PrintAttribute, StableHash};
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition;
use rustc_span::hygiene::Transparency;
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
pub use rustc_target::spec::SanitizerSet;
Expand Down Expand Up @@ -152,6 +153,13 @@ pub enum InstrumentFnAttr {
Off,
}

#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
pub struct EditionRedirect {
pub before: Edition,
pub target: Path,
pub span: Span,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, PrintAttribute)]
#[derive(Encodable, Decodable, StableHash)]
pub enum OptimizeAttr {
Expand Down Expand Up @@ -1477,6 +1485,9 @@ pub enum AttributeKind {
/// Represents `#[rustc_dyn_incompatible_trait]`.
RustcDynIncompatibleTrait(Span),

/// Represents `#[rustc_edition_redirect(before = "...", target(...))]`.
RustcEditionRedirect(ThinVec<EditionRedirect>),

/// Represents `#[rustc_effective_visibility]`.
RustcEffectiveVisibility,

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ impl AttributeKind {
RustcDumpVariancesOfOpaques => No,
RustcDumpVtable(..) => No,
RustcDynIncompatibleTrait(..) => No,
RustcEditionRedirect(..) => No,
RustcEffectiveVisibility => Yes,
RustcEiiForeignItem => No,
RustcEvaluateWhereClauses => Yes,
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_hir/src/attrs/pretty_printing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rustc_ast_pretty::pp::Printer;
use rustc_data_structures::Limit;
use rustc_data_structures::fx::FxIndexMap;
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition;
use rustc_span::hygiene::Transparency;
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
use rustc_target::spec::SanitizerSet;
Expand Down Expand Up @@ -191,7 +192,7 @@ macro_rules! print_tup {

print_tup!(A B C D E F G H);
print_skip!(Span, (), ErrorGuaranteed, AttrId);
print_disp!(u8, u16, u32, u128, usize, bool, NonZero<u32>, Limit);
print_disp!(u8, u16, u32, u128, usize, bool, NonZero<u32>, Edition, Limit);
print_debug!(
Symbol,
Ident,
Expand Down
9 changes: 8 additions & 1 deletion compiler/rustc_metadata/src/rmeta/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1736,11 +1736,13 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
let module_children = tcx.module_children_local(local_def_id);

record_array!(self.tables.module_children_non_reexports[def_id] <-
module_children.iter().filter(|child| child.reexport_chain.is_empty())
module_children.iter().filter(|child| child.reexport_chain.is_empty()
&& child.edition_redirects.is_empty())
.map(|child| child.res.def_id().index));

record_defaulted_array!(self.tables.module_children_reexports[def_id] <-
module_children.iter().filter(|child| !child.reexport_chain.is_empty()));
module_children.iter().filter(|child| !child.reexport_chain.is_empty()
|| !child.edition_redirects.is_empty()));

let ambig_module_children = tcx
.resolutions(())
Expand Down
7 changes: 3 additions & 4 deletions compiler/rustc_metadata/src/rmeta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,9 @@ define_tables! {
explicit_implied_const_bounds: Table<DefIndex, LazyArray<(ty::PolyTraitRef<'static>, Span)>>,
inherent_impls: Table<DefIndex, LazyArray<DefIndex>>,
opt_rpitit_info: Table<DefIndex, Option<LazyValue<ty::ImplTraitInTraitData>>>,
// 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<DefIndex, LazyArray<ModChild>>,
ambig_module_children: Table<DefIndex, LazyArray<AmbigModChild>>,
cross_crate_inlinable: Table<DefIndex, bool>,
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_middle/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use rustc_hir::def::Res;
use rustc_macros::{StableHash, TyDecodable, TyEncodable};
use rustc_span::Ident;
use rustc_span::def_id::{DefId, ModId};
use rustc_span::edition::Edition;
use smallvec::SmallVec;

use crate::ty;
Expand All @@ -26,6 +27,13 @@ impl Reexport {
}
}

/// A different item that a module child resolves to before an edition boundary.
#[derive(Clone, Copy, Debug, TyEncodable, TyDecodable, StableHash)]
pub struct EditionRedirect {
pub before: Edition,
pub target: Res<!>,
}

/// This structure is supposed to keep enough data to re-create `Decl`s for other crates
/// during name resolution. Right now the bindings are not recreated entirely precisely so we may
/// need to add more data in the future to correctly support macros 2.0, for example.
Expand All @@ -43,6 +51,8 @@ pub struct ModChild {
/// Reexport chain linking this module child to its original reexported item.
/// Empty if the module child is a proper item.
pub reexport_chain: SmallVec<[Reexport; 2]>,
/// Edition-dependent alternatives, sorted from the earliest boundary to the latest.
pub edition_redirects: SmallVec<[EditionRedirect; 1]>,
}

/// Same as `ModChild`, however, it includes ambiguity error.
Expand Down
21 changes: 19 additions & 2 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, msg};
use rustc_feature::BUILTIN_ATTRIBUTE_MAP;
use rustc_hir::attrs::diagnostic::Directive;
use rustc_hir::attrs::{
AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr,
OptimizeAttr, ReprAttr,
AttributeKind, DocAttribute, DocInline, EditionRedirect, EiiDecl, EiiImpl, EiiImplResolution,
InlineAttr, OptimizeAttr, ReprAttr,
};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalModId;
Expand Down Expand Up @@ -230,6 +230,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
AttributeKind::Linkage(_linkage, span) => {
self.check_linkage(*span, hir_id, target, item)
}
AttributeKind::RustcEditionRedirect(redirects) => {
self.check_rustc_edition_redirect(item, redirects)
}

// All of the following attributes have no specific checks.
// tidy-alphabetical-start
Expand Down Expand Up @@ -411,6 +414,20 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}

/// Rejects the use of edition redirect on non-single use statements.
fn check_rustc_edition_redirect(&self, item: Option<&Item<'_>>, redirects: &[EditionRedirect]) {
let Some(Item { kind: ItemKind::Use(_, use_kind), .. }) = item else {
return;
};
if matches!(use_kind, hir::UseKind::Single(_)) {
return;
}
if let Some(redirect) = redirects.first() {
self.dcx()
.emit_err(diagnostics::EditionRedirectNonSingleUse { attr_span: redirect.span });
}
}

fn check_rustc_must_implement_one_of(
&self,
attr_span: Span,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_passes/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ use rustc_span::{DUMMY_SP, Ident, Span, Symbol};
use crate::check_attr::ProcMacroKind;
use crate::lang_items::Duplicate;

#[derive(Diagnostic)]
#[diag("`#[rustc_edition_redirect]` can only be applied to a single import")]
#[help("use a separate, non-braced `use` item")]
pub(crate) struct EditionRedirectNonSingleUse {
#[primary_span]
pub attr_span: Span,
}

#[derive(Diagnostic)]
#[diag("`{$no_mangle_attr}` attribute may not be used in combination with `{$export_name_attr}`")]
pub(crate) struct MixedExportNameAndNoMangle {
Expand Down
Loading
Loading