1
Fork 0

allowed_through_unstable_modules: support showing a deprecation message when the unstable module name is used

This commit is contained in:
Ralf Jung 2025-01-01 19:09:01 +01:00
parent 561a097b65
commit cf0ab86251
12 changed files with 146 additions and 59 deletions

View file

@ -723,6 +723,8 @@ impl MetaItemLit {
pub trait AttributeExt: Debug { pub trait AttributeExt: Debug {
fn id(&self) -> AttrId; fn id(&self) -> AttrId;
/// For a single-segment attribute (i.e., `#[attr]` and not `#[path::atrr]`),
/// return the name of the attribute, else return the empty identifier.
fn name_or_empty(&self) -> Symbol { fn name_or_empty(&self) -> Symbol {
self.ident().unwrap_or_else(Ident::empty).name self.ident().unwrap_or_else(Ident::empty).name
} }

View file

@ -101,6 +101,16 @@ impl PartialConstStability {
} }
} }
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
#[derive(HashStable_Generic)]
pub enum AllowedThroughUnstableModules {
/// This does not get a deprecation warning. We still generally would prefer people to use the
/// fully stable path, and a warning will likely be emitted in the future.
WithoutDeprecation,
/// Emit the given deprecation warning.
WithDeprecation(Symbol),
}
/// The available stability levels. /// The available stability levels.
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)] #[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
#[derive(HashStable_Generic)] #[derive(HashStable_Generic)]
@ -137,9 +147,8 @@ pub enum StabilityLevel {
Stable { Stable {
/// Rust release which stabilized this feature. /// Rust release which stabilized this feature.
since: StableSince, since: StableSince,
/// Is this item allowed to be referred to on stable, despite being contained in unstable /// This is `Some` if this item allowed to be referred to on stable via unstable modules.
/// modules? allowed_through_unstable_modules: Option<AllowedThroughUnstableModules>,
allowed_through_unstable_modules: bool,
}, },
} }

View file

@ -6,8 +6,8 @@ use rustc_ast::MetaItem;
use rustc_ast::attr::AttributeExt; use rustc_ast::attr::AttributeExt;
use rustc_ast_pretty::pprust; use rustc_ast_pretty::pprust;
use rustc_attr_data_structures::{ use rustc_attr_data_structures::{
ConstStability, DefaultBodyStability, Stability, StabilityLevel, StableSince, UnstableReason, AllowedThroughUnstableModules, ConstStability, DefaultBodyStability, Stability, StabilityLevel,
VERSION_PLACEHOLDER, StableSince, UnstableReason, VERSION_PLACEHOLDER,
}; };
use rustc_errors::ErrorGuaranteed; use rustc_errors::ErrorGuaranteed;
use rustc_session::Session; use rustc_session::Session;
@ -24,11 +24,16 @@ pub fn find_stability(
item_sp: Span, item_sp: Span,
) -> Option<(Stability, Span)> { ) -> Option<(Stability, Span)> {
let mut stab: Option<(Stability, Span)> = None; let mut stab: Option<(Stability, Span)> = None;
let mut allowed_through_unstable_modules = false; let mut allowed_through_unstable_modules = None;
for attr in attrs { for attr in attrs {
match attr.name_or_empty() { match attr.name_or_empty() {
sym::rustc_allowed_through_unstable_modules => allowed_through_unstable_modules = true, sym::rustc_allowed_through_unstable_modules => {
allowed_through_unstable_modules = Some(match attr.value_str() {
Some(msg) => AllowedThroughUnstableModules::WithDeprecation(msg),
None => AllowedThroughUnstableModules::WithoutDeprecation,
})
}
sym::unstable => { sym::unstable => {
if stab.is_some() { if stab.is_some() {
sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels {
@ -56,15 +61,15 @@ pub fn find_stability(
} }
} }
if allowed_through_unstable_modules { if let Some(allowed_through_unstable_modules) = allowed_through_unstable_modules {
match &mut stab { match &mut stab {
Some(( Some((
Stability { Stability {
level: StabilityLevel::Stable { allowed_through_unstable_modules, .. }, level: StabilityLevel::Stable { allowed_through_unstable_modules: in_stab, .. },
.. ..
}, },
_, _,
)) => *allowed_through_unstable_modules = true, )) => *in_stab = Some(allowed_through_unstable_modules),
_ => { _ => {
sess.dcx() sess.dcx()
.emit_err(session_diagnostics::RustcAllowedUnstablePairing { span: item_sp }); .emit_err(session_diagnostics::RustcAllowedUnstablePairing { span: item_sp });
@ -283,7 +288,7 @@ fn parse_stability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol,
match feature { match feature {
Ok(feature) => { Ok(feature) => {
let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: false }; let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
Some((feature, level)) Some((feature, level))
} }
Err(ErrorGuaranteed { .. }) => None, Err(ErrorGuaranteed { .. }) => None,

View file

@ -623,7 +623,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
EncodeCrossCrate::No, "allow_internal_unsafe side-steps the unsafe_code lint", EncodeCrossCrate::No, "allow_internal_unsafe side-steps the unsafe_code lint",
), ),
rustc_attr!( rustc_attr!(
rustc_allowed_through_unstable_modules, Normal, template!(Word), rustc_allowed_through_unstable_modules, Normal, template!(Word, NameValueStr: "deprecation message"),
WarnFollowing, EncodeCrossCrate::No, WarnFollowing, EncodeCrossCrate::No,
"rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \ "rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \
through unstable paths" through unstable paths"

View file

@ -5,8 +5,8 @@ use std::mem::replace;
use std::num::NonZero; use std::num::NonZero;
use rustc_attr_parsing::{ use rustc_attr_parsing::{
self as attr, ConstStability, DeprecatedSince, Stability, StabilityLevel, StableSince, self as attr, AllowedThroughUnstableModules, ConstStability, DeprecatedSince, Stability,
UnstableReason, VERSION_PLACEHOLDER, StabilityLevel, StableSince, UnstableReason, VERSION_PLACEHOLDER,
}; };
use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet}; use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet};
@ -20,11 +20,16 @@ use rustc_hir::{FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
use rustc_middle::hir::nested_filter; use rustc_middle::hir::nested_filter;
use rustc_middle::middle::lib_features::{FeatureStability, LibFeatures}; use rustc_middle::middle::lib_features::{FeatureStability, LibFeatures};
use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::middle::privacy::EffectiveVisibilities;
use rustc_middle::middle::stability::{AllowUnstable, DeprecationEntry, Index}; use rustc_middle::middle::stability::{
AllowUnstable, Deprecated, DeprecationEntry, EvalResult, Index,
};
use rustc_middle::query::Providers; use rustc_middle::query::Providers;
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_session::lint; use rustc_session::lint;
use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED}; use rustc_session::lint::builtin::{
DEPRECATED, INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED,
};
use rustc_span::{Span, Symbol, sym}; use rustc_span::{Span, Symbol, sym};
use tracing::{debug, info}; use tracing::{debug, info};
@ -844,42 +849,95 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
}, },
); );
let is_allowed_through_unstable_modules = |def_id| { if item_is_allowed {
self.tcx.lookup_stability(def_id).is_some_and(|stab| match stab.level { // The item itself is allowed; check whether the path there is also allowed.
StabilityLevel::Stable { allowed_through_unstable_modules, .. } => { let is_allowed_through_unstable_modules: Option<AllowedThroughUnstableModules> =
allowed_through_unstable_modules self.tcx.lookup_stability(def_id).and_then(|stab| match stab.level {
} StabilityLevel::Stable { allowed_through_unstable_modules, .. } => {
_ => false, allowed_through_unstable_modules
}) }
}; _ => None,
});
if item_is_allowed && !is_allowed_through_unstable_modules(def_id) { if is_allowed_through_unstable_modules.is_none() {
// Check parent modules stability as well if the item the path refers to is itself // Check parent modules stability as well if the item the path refers to is itself
// stable. We only emit warnings for unstable path segments if the item is stable // stable. We only emit warnings for unstable path segments if the item is stable
// or allowed because stability is often inherited, so the most common case is that // or allowed because stability is often inherited, so the most common case is that
// both the segments and the item are unstable behind the same feature flag. // both the segments and the item are unstable behind the same feature flag.
// //
// We check here rather than in `visit_path_segment` to prevent visiting the last // We check here rather than in `visit_path_segment` to prevent visiting the last
// path segment twice // path segment twice
// //
// We include special cases via #[rustc_allowed_through_unstable_modules] for items // We include special cases via #[rustc_allowed_through_unstable_modules] for items
// that were accidentally stabilized through unstable paths before this check was // that were accidentally stabilized through unstable paths before this check was
// added, such as `core::intrinsics::transmute` // added, such as `core::intrinsics::transmute`
let parents = path.segments.iter().rev().skip(1); let parents = path.segments.iter().rev().skip(1);
for path_segment in parents { for path_segment in parents {
if let Some(def_id) = path_segment.res.opt_def_id() { if let Some(def_id) = path_segment.res.opt_def_id() {
// use `None` for id to prevent deprecation check // use `None` for id to prevent deprecation check
self.tcx.check_stability_allow_unstable( self.tcx.check_stability_allow_unstable(
def_id, def_id,
None, None,
path.span, path.span,
None, None,
if is_unstable_reexport(self.tcx, id) { if is_unstable_reexport(self.tcx, id) {
AllowUnstable::Yes AllowUnstable::Yes
} else { } else {
AllowUnstable::No AllowUnstable::No
}, },
); );
}
}
} else if let Some(AllowedThroughUnstableModules::WithDeprecation(deprecation)) =
is_allowed_through_unstable_modules
{
// Similar to above, but we cannot use `check_stability_allow_unstable` as that would
// immediately show the stability error. We just want to know the result and disaplay
// our own kind of error.
let parents = path.segments.iter().rev().skip(1);
for path_segment in parents {
if let Some(def_id) = path_segment.res.opt_def_id() {
// use `None` for id to prevent deprecation check
let eval_result = self.tcx.eval_stability_allow_unstable(
def_id,
None,
path.span,
None,
if is_unstable_reexport(self.tcx, id) {
AllowUnstable::Yes
} else {
AllowUnstable::No
},
);
let is_allowed = matches!(eval_result, EvalResult::Allow);
if !is_allowed {
// Calculating message for lint involves calling `self.def_path_str`,
// which will by default invoke the expensive `visible_parent_map` query.
// Skip all that work if the lint is allowed anyway.
if self.tcx.lint_level_at_node(DEPRECATED, id).0
== lint::Level::Allow
{
return;
}
// Show a deprecation message.
let def_path =
with_no_trimmed_paths!(self.tcx.def_path_str(def_id));
let def_kind = self.tcx.def_descr(def_id);
let diag = Deprecated {
sub: None,
kind: def_kind.to_owned(),
path: def_path,
note: Some(deprecation),
since_kind: lint::DeprecatedSinceKind::InEffect,
};
self.tcx.emit_node_span_lint(
DEPRECATED,
id,
method_span.unwrap_or(path.span),
diag,
);
}
}
} }
} }
} }

View file

@ -406,7 +406,7 @@ impl Item {
// were never supposed to work at all. // were never supposed to work at all.
let stab = self.stability(tcx)?; let stab = self.stability(tcx)?;
if let rustc_attr_parsing::StabilityLevel::Stable { if let rustc_attr_parsing::StabilityLevel::Stable {
allowed_through_unstable_modules: true, allowed_through_unstable_modules: Some(_),
.. ..
} = stab.level } = stab.level
{ {

View file

@ -316,7 +316,7 @@ impl DocFolder for CacheBuilder<'_, '_> {
let skip_because_unstable = matches!( let skip_because_unstable = matches!(
item.stability.map(|stab| stab.level), item.stability.map(|stab| stab.level),
Some(StabilityLevel::Stable { allowed_through_unstable_modules: true, .. }) Some(StabilityLevel::Stable { allowed_through_unstable_modules: Some(_), .. })
); );
if (!self.cache.stripped_mod && !skip_because_unstable) || self.is_json_output { if (!self.cache.stripped_mod && !skip_because_unstable) || self.is_json_output {

View file

@ -119,7 +119,7 @@ fn merge_stability(
parent_stability: Option<Stability>, parent_stability: Option<Stability>,
) -> Option<Stability> { ) -> Option<Stability> {
if let Some(own_stab) = own_stability if let Some(own_stab) = own_stability
&& let StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: false } = && let StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: None } =
own_stab.level own_stab.level
&& let Some(parent_stab) = parent_stability && let Some(parent_stab) = parent_stability
&& (parent_stab.is_unstable() && (parent_stab.is_unstable()
@ -127,12 +127,12 @@ fn merge_stability(
{ {
parent_stability parent_stability
} else if let Some(mut own_stab) = own_stability } else if let Some(mut own_stab) = own_stability
&& let StabilityLevel::Stable { since, allowed_through_unstable_modules: true } = && let StabilityLevel::Stable { since, allowed_through_unstable_modules: Some(_) } =
own_stab.level own_stab.level
&& parent_stability.is_some_and(|stab| stab.is_stable()) && parent_stability.is_some_and(|stab| stab.is_stable())
{ {
// this property does not apply transitively through re-exports // this property does not apply transitively through re-exports
own_stab.level = StabilityLevel::Stable { since, allowed_through_unstable_modules: false }; own_stab.level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
Some(own_stab) Some(own_stab)
} else { } else {
own_stability own_stability

View file

@ -180,7 +180,7 @@ fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: &Msrv) -> bool {
if let Some(stability) = cx.tcx.lookup_stability(def_id) if let Some(stability) = cx.tcx.lookup_stability(def_id)
&& let StabilityLevel::Stable { && let StabilityLevel::Stable {
since, since,
allowed_through_unstable_modules: false, allowed_through_unstable_modules: None,
} = stability.level } = stability.level
{ {
let stable = match since { let stable = match since {

View file

@ -6,4 +6,5 @@
extern crate allowed_through_unstable_core; extern crate allowed_through_unstable_core;
use allowed_through_unstable_core::unstable_module::OldStableTraitAllowedThoughUnstable; use allowed_through_unstable_core::unstable_module::OldStableTraitAllowedThoughUnstable;
use allowed_through_unstable_core::unstable_module::OldStableTraitAllowedThoughUnstableWithDeprecation; //~WARN use of deprecated module `allowed_through_unstable_core::unstable_module`: use the new path instead
use allowed_through_unstable_core::unstable_module::NewStableTraitNotAllowedThroughUnstable; //~ ERROR use of unstable library feature `unstable_test_feature` use allowed_through_unstable_core::unstable_module::NewStableTraitNotAllowedThroughUnstable; //~ ERROR use of unstable library feature `unstable_test_feature`

View file

@ -1,5 +1,13 @@
warning: use of deprecated module `allowed_through_unstable_core::unstable_module`: use the new path instead
--> $DIR/allowed-through-unstable.rs:9:53
|
LL | use allowed_through_unstable_core::unstable_module::OldStableTraitAllowedThoughUnstableWithDeprecation;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(deprecated)]` on by default
error[E0658]: use of unstable library feature `unstable_test_feature` error[E0658]: use of unstable library feature `unstable_test_feature`
--> $DIR/allowed-through-unstable.rs:9:5 --> $DIR/allowed-through-unstable.rs:10:5
| |
LL | use allowed_through_unstable_core::unstable_module::NewStableTraitNotAllowedThroughUnstable; LL | use allowed_through_unstable_core::unstable_module::NewStableTraitNotAllowedThroughUnstable;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -8,6 +16,6 @@ LL | use allowed_through_unstable_core::unstable_module::NewStableTraitNotAllowe
= help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date = 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 error: aborting due to 1 previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0658`. For more information about this error, try `rustc --explain E0658`.

View file

@ -9,6 +9,10 @@ pub mod unstable_module {
#[rustc_allowed_through_unstable_modules] #[rustc_allowed_through_unstable_modules]
pub trait OldStableTraitAllowedThoughUnstable {} pub trait OldStableTraitAllowedThoughUnstable {}
#[stable(feature = "stable_test_feature", since = "1.2.0")]
#[rustc_allowed_through_unstable_modules = "use the new path instead"]
pub trait OldStableTraitAllowedThoughUnstableWithDeprecation {}
#[stable(feature = "stable_test_feature", since = "1.2.0")] #[stable(feature = "stable_test_feature", since = "1.2.0")]
pub trait NewStableTraitNotAllowedThroughUnstable {} pub trait NewStableTraitNotAllowedThroughUnstable {}
} }