Skip to content

Commit cf0ab86

Browse files
committed
allowed_through_unstable_modules: support showing a deprecation message when the unstable module name is used
1 parent 561a097 commit cf0ab86

File tree

12 files changed

+146
-59
lines changed

12 files changed

+146
-59
lines changed

compiler/rustc_ast/src/attr/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,8 @@ impl MetaItemLit {
723723
pub trait AttributeExt: Debug {
724724
fn id(&self) -> AttrId;
725725

726+
/// For a single-segment attribute (i.e., `#[attr]` and not `#[path::atrr]`),
727+
/// return the name of the attribute, else return the empty identifier.
726728
fn name_or_empty(&self) -> Symbol {
727729
self.ident().unwrap_or_else(Ident::empty).name
728730
}

compiler/rustc_attr_data_structures/src/stability.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ impl PartialConstStability {
101101
}
102102
}
103103

104+
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
105+
#[derive(HashStable_Generic)]
106+
pub enum AllowedThroughUnstableModules {
107+
/// This does not get a deprecation warning. We still generally would prefer people to use the
108+
/// fully stable path, and a warning will likely be emitted in the future.
109+
WithoutDeprecation,
110+
/// Emit the given deprecation warning.
111+
WithDeprecation(Symbol),
112+
}
113+
104114
/// The available stability levels.
105115
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
106116
#[derive(HashStable_Generic)]
@@ -137,9 +147,8 @@ pub enum StabilityLevel {
137147
Stable {
138148
/// Rust release which stabilized this feature.
139149
since: StableSince,
140-
/// Is this item allowed to be referred to on stable, despite being contained in unstable
141-
/// modules?
142-
allowed_through_unstable_modules: bool,
150+
/// This is `Some` if this item allowed to be referred to on stable via unstable modules.
151+
allowed_through_unstable_modules: Option<AllowedThroughUnstableModules>,
143152
},
144153
}
145154

compiler/rustc_attr_parsing/src/attributes/stability.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use rustc_ast::MetaItem;
66
use rustc_ast::attr::AttributeExt;
77
use rustc_ast_pretty::pprust;
88
use rustc_attr_data_structures::{
9-
ConstStability, DefaultBodyStability, Stability, StabilityLevel, StableSince, UnstableReason,
10-
VERSION_PLACEHOLDER,
9+
AllowedThroughUnstableModules, ConstStability, DefaultBodyStability, Stability, StabilityLevel,
10+
StableSince, UnstableReason, VERSION_PLACEHOLDER,
1111
};
1212
use rustc_errors::ErrorGuaranteed;
1313
use rustc_session::Session;
@@ -24,11 +24,16 @@ pub fn find_stability(
2424
item_sp: Span,
2525
) -> Option<(Stability, Span)> {
2626
let mut stab: Option<(Stability, Span)> = None;
27-
let mut allowed_through_unstable_modules = false;
27+
let mut allowed_through_unstable_modules = None;
2828

2929
for attr in attrs {
3030
match attr.name_or_empty() {
31-
sym::rustc_allowed_through_unstable_modules => allowed_through_unstable_modules = true,
31+
sym::rustc_allowed_through_unstable_modules => {
32+
allowed_through_unstable_modules = Some(match attr.value_str() {
33+
Some(msg) => AllowedThroughUnstableModules::WithDeprecation(msg),
34+
None => AllowedThroughUnstableModules::WithoutDeprecation,
35+
})
36+
}
3237
sym::unstable => {
3338
if stab.is_some() {
3439
sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels {
@@ -56,15 +61,15 @@ pub fn find_stability(
5661
}
5762
}
5863

59-
if allowed_through_unstable_modules {
64+
if let Some(allowed_through_unstable_modules) = allowed_through_unstable_modules {
6065
match &mut stab {
6166
Some((
6267
Stability {
63-
level: StabilityLevel::Stable { allowed_through_unstable_modules, .. },
68+
level: StabilityLevel::Stable { allowed_through_unstable_modules: in_stab, .. },
6469
..
6570
},
6671
_,
67-
)) => *allowed_through_unstable_modules = true,
72+
)) => *in_stab = Some(allowed_through_unstable_modules),
6873
_ => {
6974
sess.dcx()
7075
.emit_err(session_diagnostics::RustcAllowedUnstablePairing { span: item_sp });
@@ -283,7 +288,7 @@ fn parse_stability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol,
283288

284289
match feature {
285290
Ok(feature) => {
286-
let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: false };
291+
let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
287292
Some((feature, level))
288293
}
289294
Err(ErrorGuaranteed { .. }) => None,

compiler/rustc_feature/src/builtin_attrs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
623623
EncodeCrossCrate::No, "allow_internal_unsafe side-steps the unsafe_code lint",
624624
),
625625
rustc_attr!(
626-
rustc_allowed_through_unstable_modules, Normal, template!(Word),
626+
rustc_allowed_through_unstable_modules, Normal, template!(Word, NameValueStr: "deprecation message"),
627627
WarnFollowing, EncodeCrossCrate::No,
628628
"rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \
629629
through unstable paths"

compiler/rustc_passes/src/stability.rs

+97-39
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use std::mem::replace;
55
use std::num::NonZero;
66

77
use rustc_attr_parsing::{
8-
self as attr, ConstStability, DeprecatedSince, Stability, StabilityLevel, StableSince,
9-
UnstableReason, VERSION_PLACEHOLDER,
8+
self as attr, AllowedThroughUnstableModules, ConstStability, DeprecatedSince, Stability,
9+
StabilityLevel, StableSince, UnstableReason, VERSION_PLACEHOLDER,
1010
};
1111
use rustc_data_structures::fx::FxIndexMap;
1212
use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet};
@@ -20,11 +20,16 @@ use rustc_hir::{FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
2020
use rustc_middle::hir::nested_filter;
2121
use rustc_middle::middle::lib_features::{FeatureStability, LibFeatures};
2222
use rustc_middle::middle::privacy::EffectiveVisibilities;
23-
use rustc_middle::middle::stability::{AllowUnstable, DeprecationEntry, Index};
23+
use rustc_middle::middle::stability::{
24+
AllowUnstable, Deprecated, DeprecationEntry, EvalResult, Index,
25+
};
2426
use rustc_middle::query::Providers;
2527
use rustc_middle::ty::TyCtxt;
28+
use rustc_middle::ty::print::with_no_trimmed_paths;
2629
use rustc_session::lint;
27-
use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED};
30+
use rustc_session::lint::builtin::{
31+
DEPRECATED, INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED,
32+
};
2833
use rustc_span::{Span, Symbol, sym};
2934
use tracing::{debug, info};
3035

@@ -844,42 +849,95 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
844849
},
845850
);
846851

847-
let is_allowed_through_unstable_modules = |def_id| {
848-
self.tcx.lookup_stability(def_id).is_some_and(|stab| match stab.level {
849-
StabilityLevel::Stable { allowed_through_unstable_modules, .. } => {
850-
allowed_through_unstable_modules
852+
if item_is_allowed {
853+
// The item itself is allowed; check whether the path there is also allowed.
854+
let is_allowed_through_unstable_modules: Option<AllowedThroughUnstableModules> =
855+
self.tcx.lookup_stability(def_id).and_then(|stab| match stab.level {
856+
StabilityLevel::Stable { allowed_through_unstable_modules, .. } => {
857+
allowed_through_unstable_modules
858+
}
859+
_ => None,
860+
});
861+
862+
if is_allowed_through_unstable_modules.is_none() {
863+
// Check parent modules stability as well if the item the path refers to is itself
864+
// stable. We only emit warnings for unstable path segments if the item is stable
865+
// or allowed because stability is often inherited, so the most common case is that
866+
// both the segments and the item are unstable behind the same feature flag.
867+
//
868+
// We check here rather than in `visit_path_segment` to prevent visiting the last
869+
// path segment twice
870+
//
871+
// We include special cases via #[rustc_allowed_through_unstable_modules] for items
872+
// that were accidentally stabilized through unstable paths before this check was
873+
// added, such as `core::intrinsics::transmute`
874+
let parents = path.segments.iter().rev().skip(1);
875+
for path_segment in parents {
876+
if let Some(def_id) = path_segment.res.opt_def_id() {
877+
// use `None` for id to prevent deprecation check
878+
self.tcx.check_stability_allow_unstable(
879+
def_id,
880+
None,
881+
path.span,
882+
None,
883+
if is_unstable_reexport(self.tcx, id) {
884+
AllowUnstable::Yes
885+
} else {
886+
AllowUnstable::No
887+
},
888+
);
889+
}
851890
}
852-
_ => false,
853-
})
854-
};
855-
856-
if item_is_allowed && !is_allowed_through_unstable_modules(def_id) {
857-
// Check parent modules stability as well if the item the path refers to is itself
858-
// stable. We only emit warnings for unstable path segments if the item is stable
859-
// or allowed because stability is often inherited, so the most common case is that
860-
// both the segments and the item are unstable behind the same feature flag.
861-
//
862-
// We check here rather than in `visit_path_segment` to prevent visiting the last
863-
// path segment twice
864-
//
865-
// We include special cases via #[rustc_allowed_through_unstable_modules] for items
866-
// that were accidentally stabilized through unstable paths before this check was
867-
// added, such as `core::intrinsics::transmute`
868-
let parents = path.segments.iter().rev().skip(1);
869-
for path_segment in parents {
870-
if let Some(def_id) = path_segment.res.opt_def_id() {
871-
// use `None` for id to prevent deprecation check
872-
self.tcx.check_stability_allow_unstable(
873-
def_id,
874-
None,
875-
path.span,
876-
None,
877-
if is_unstable_reexport(self.tcx, id) {
878-
AllowUnstable::Yes
879-
} else {
880-
AllowUnstable::No
881-
},
882-
);
891+
} else if let Some(AllowedThroughUnstableModules::WithDeprecation(deprecation)) =
892+
is_allowed_through_unstable_modules
893+
{
894+
// Similar to above, but we cannot use `check_stability_allow_unstable` as that would
895+
// immediately show the stability error. We just want to know the result and disaplay
896+
// our own kind of error.
897+
let parents = path.segments.iter().rev().skip(1);
898+
for path_segment in parents {
899+
if let Some(def_id) = path_segment.res.opt_def_id() {
900+
// use `None` for id to prevent deprecation check
901+
let eval_result = self.tcx.eval_stability_allow_unstable(
902+
def_id,
903+
None,
904+
path.span,
905+
None,
906+
if is_unstable_reexport(self.tcx, id) {
907+
AllowUnstable::Yes
908+
} else {
909+
AllowUnstable::No
910+
},
911+
);
912+
let is_allowed = matches!(eval_result, EvalResult::Allow);
913+
if !is_allowed {
914+
// Calculating message for lint involves calling `self.def_path_str`,
915+
// which will by default invoke the expensive `visible_parent_map` query.
916+
// Skip all that work if the lint is allowed anyway.
917+
if self.tcx.lint_level_at_node(DEPRECATED, id).0
918+
== lint::Level::Allow
919+
{
920+
return;
921+
}
922+
// Show a deprecation message.
923+
let def_path =
924+
with_no_trimmed_paths!(self.tcx.def_path_str(def_id));
925+
let def_kind = self.tcx.def_descr(def_id);
926+
let diag = Deprecated {
927+
sub: None,
928+
kind: def_kind.to_owned(),
929+
path: def_path,
930+
note: Some(deprecation),
931+
since_kind: lint::DeprecatedSinceKind::InEffect,
932+
};
933+
self.tcx.emit_node_span_lint(
934+
DEPRECATED,
935+
id,
936+
method_span.unwrap_or(path.span),
937+
diag,
938+
);
939+
}
940+
}
883941
}
884942
}
885943
}

src/librustdoc/clean/types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ impl Item {
406406
// were never supposed to work at all.
407407
let stab = self.stability(tcx)?;
408408
if let rustc_attr_parsing::StabilityLevel::Stable {
409-
allowed_through_unstable_modules: true,
409+
allowed_through_unstable_modules: Some(_),
410410
..
411411
} = stab.level
412412
{

src/librustdoc/formats/cache.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ impl DocFolder for CacheBuilder<'_, '_> {
316316

317317
let skip_because_unstable = matches!(
318318
item.stability.map(|stab| stab.level),
319-
Some(StabilityLevel::Stable { allowed_through_unstable_modules: true, .. })
319+
Some(StabilityLevel::Stable { allowed_through_unstable_modules: Some(_), .. })
320320
);
321321

322322
if (!self.cache.stripped_mod && !skip_because_unstable) || self.is_json_output {

src/librustdoc/passes/propagate_stability.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -119,20 +119,20 @@ fn merge_stability(
119119
parent_stability: Option<Stability>,
120120
) -> Option<Stability> {
121121
if let Some(own_stab) = own_stability
122-
&& let StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: false } =
122+
&& let StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: None } =
123123
own_stab.level
124124
&& let Some(parent_stab) = parent_stability
125125
&& (parent_stab.is_unstable()
126126
|| parent_stab.stable_since().is_some_and(|parent_since| parent_since > own_since))
127127
{
128128
parent_stability
129129
} else if let Some(mut own_stab) = own_stability
130-
&& let StabilityLevel::Stable { since, allowed_through_unstable_modules: true } =
130+
&& let StabilityLevel::Stable { since, allowed_through_unstable_modules: Some(_) } =
131131
own_stab.level
132132
&& parent_stability.is_some_and(|stab| stab.is_stable())
133133
{
134134
// this property does not apply transitively through re-exports
135-
own_stab.level = StabilityLevel::Stable { since, allowed_through_unstable_modules: false };
135+
own_stab.level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
136136
Some(own_stab)
137137
} else {
138138
own_stability

src/tools/clippy/clippy_lints/src/std_instead_of_core.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: &Msrv) -> bool {
180180
if let Some(stability) = cx.tcx.lookup_stability(def_id)
181181
&& let StabilityLevel::Stable {
182182
since,
183-
allowed_through_unstable_modules: false,
183+
allowed_through_unstable_modules: None,
184184
} = stability.level
185185
{
186186
let stable = match since {

tests/ui/stability-attribute/allowed-through-unstable.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
extern crate allowed_through_unstable_core;
77

88
use allowed_through_unstable_core::unstable_module::OldStableTraitAllowedThoughUnstable;
9+
use allowed_through_unstable_core::unstable_module::OldStableTraitAllowedThoughUnstableWithDeprecation; //~WARN use of deprecated module `allowed_through_unstable_core::unstable_module`: use the new path instead
910
use allowed_through_unstable_core::unstable_module::NewStableTraitNotAllowedThroughUnstable; //~ ERROR use of unstable library feature `unstable_test_feature`
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
warning: use of deprecated module `allowed_through_unstable_core::unstable_module`: use the new path instead
2+
--> $DIR/allowed-through-unstable.rs:9:53
3+
|
4+
LL | use allowed_through_unstable_core::unstable_module::OldStableTraitAllowedThoughUnstableWithDeprecation;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(deprecated)]` on by default
8+
19
error[E0658]: use of unstable library feature `unstable_test_feature`
2-
--> $DIR/allowed-through-unstable.rs:9:5
10+
--> $DIR/allowed-through-unstable.rs:10:5
311
|
412
LL | use allowed_through_unstable_core::unstable_module::NewStableTraitNotAllowedThroughUnstable;
513
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -8,6 +16,6 @@ LL | use allowed_through_unstable_core::unstable_module::NewStableTraitNotAllowe
816
= help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable
917
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
1018

11-
error: aborting due to 1 previous error
19+
error: aborting due to 1 previous error; 1 warning emitted
1220

1321
For more information about this error, try `rustc --explain E0658`.

tests/ui/stability-attribute/auxiliary/allowed-through-unstable-core.rs

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ pub mod unstable_module {
99
#[rustc_allowed_through_unstable_modules]
1010
pub trait OldStableTraitAllowedThoughUnstable {}
1111

12+
#[stable(feature = "stable_test_feature", since = "1.2.0")]
13+
#[rustc_allowed_through_unstable_modules = "use the new path instead"]
14+
pub trait OldStableTraitAllowedThoughUnstableWithDeprecation {}
15+
1216
#[stable(feature = "stable_test_feature", since = "1.2.0")]
1317
pub trait NewStableTraitNotAllowedThroughUnstable {}
1418
}

0 commit comments

Comments
 (0)