about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2025-01-01 19:09:01 +0100
committerRalf Jung <post@ralfj.de>2025-01-15 09:41:33 +0100
commitcf0ab86251f1ab3e11132332695eee73ff746e27 (patch)
tree6b9380ad1a5179aad154516d2b54d0bd3b9bb20f
parent561a097b653c53d264fb60b417ba07c19ee14f4a (diff)
downloadrust-cf0ab86251f1ab3e11132332695eee73ff746e27.tar.gz
rust-cf0ab86251f1ab3e11132332695eee73ff746e27.zip
allowed_through_unstable_modules: support showing a deprecation message when the unstable module name is used
-rw-r--r--compiler/rustc_ast/src/attr/mod.rs2
-rw-r--r--compiler/rustc_attr_data_structures/src/stability.rs15
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/stability.rs21
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs2
-rw-r--r--compiler/rustc_passes/src/stability.rs136
-rw-r--r--src/librustdoc/clean/types.rs2
-rw-r--r--src/librustdoc/formats/cache.rs2
-rw-r--r--src/librustdoc/passes/propagate_stability.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/std_instead_of_core.rs2
-rw-r--r--tests/ui/stability-attribute/allowed-through-unstable.rs1
-rw-r--r--tests/ui/stability-attribute/allowed-through-unstable.stderr12
-rw-r--r--tests/ui/stability-attribute/auxiliary/allowed-through-unstable-core.rs4
12 files changed, 146 insertions, 59 deletions
diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs
index 97385b2eaab..51f18580013 100644
--- a/compiler/rustc_ast/src/attr/mod.rs
+++ b/compiler/rustc_ast/src/attr/mod.rs
@@ -723,6 +723,8 @@ impl MetaItemLit {
 pub trait AttributeExt: Debug {
     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 {
         self.ident().unwrap_or_else(Ident::empty).name
     }
diff --git a/compiler/rustc_attr_data_structures/src/stability.rs b/compiler/rustc_attr_data_structures/src/stability.rs
index 3c77d4c766c..dfda04387ec 100644
--- a/compiler/rustc_attr_data_structures/src/stability.rs
+++ b/compiler/rustc_attr_data_structures/src/stability.rs
@@ -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.
 #[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
 #[derive(HashStable_Generic)]
@@ -137,9 +147,8 @@ pub enum StabilityLevel {
     Stable {
         /// Rust release which stabilized this feature.
         since: StableSince,
-        /// Is this item allowed to be referred to on stable, despite being contained in unstable
-        /// modules?
-        allowed_through_unstable_modules: bool,
+        /// This is `Some` if this item allowed to be referred to on stable via unstable modules.
+        allowed_through_unstable_modules: Option<AllowedThroughUnstableModules>,
     },
 }
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs
index 89937e1c593..bfbe51b27d8 100644
--- a/compiler/rustc_attr_parsing/src/attributes/stability.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs
@@ -6,8 +6,8 @@ use rustc_ast::MetaItem;
 use rustc_ast::attr::AttributeExt;
 use rustc_ast_pretty::pprust;
 use rustc_attr_data_structures::{
-    ConstStability, DefaultBodyStability, Stability, StabilityLevel, StableSince, UnstableReason,
-    VERSION_PLACEHOLDER,
+    AllowedThroughUnstableModules, ConstStability, DefaultBodyStability, Stability, StabilityLevel,
+    StableSince, UnstableReason, VERSION_PLACEHOLDER,
 };
 use rustc_errors::ErrorGuaranteed;
 use rustc_session::Session;
@@ -24,11 +24,16 @@ pub fn find_stability(
     item_sp: Span,
 ) -> Option<(Stability, Span)> {
     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 {
         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 => {
                 if stab.is_some() {
                     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 {
             Some((
                 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()
                     .emit_err(session_diagnostics::RustcAllowedUnstablePairing { span: item_sp });
@@ -283,7 +288,7 @@ fn parse_stability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol,
 
     match 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))
         }
         Err(ErrorGuaranteed { .. }) => None,
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index c28a4360f6f..5510e7e09e5 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -623,7 +623,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         EncodeCrossCrate::No, "allow_internal_unsafe side-steps the unsafe_code lint",
     ),
     rustc_attr!(
-        rustc_allowed_through_unstable_modules, Normal, template!(Word),
+        rustc_allowed_through_unstable_modules, Normal, template!(Word, NameValueStr: "deprecation message"),
         WarnFollowing, EncodeCrossCrate::No,
         "rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \
         through unstable paths"
diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs
index 30f9e698521..f6c892203f9 100644
--- a/compiler/rustc_passes/src/stability.rs
+++ b/compiler/rustc_passes/src/stability.rs
@@ -5,8 +5,8 @@ use std::mem::replace;
 use std::num::NonZero;
 
 use rustc_attr_parsing::{
-    self as attr, ConstStability, DeprecatedSince, Stability, StabilityLevel, StableSince,
-    UnstableReason, VERSION_PLACEHOLDER,
+    self as attr, AllowedThroughUnstableModules, ConstStability, DeprecatedSince, Stability,
+    StabilityLevel, StableSince, UnstableReason, VERSION_PLACEHOLDER,
 };
 use rustc_data_structures::fx::FxIndexMap;
 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::middle::lib_features::{FeatureStability, LibFeatures};
 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::ty::TyCtxt;
+use rustc_middle::ty::print::with_no_trimmed_paths;
 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 tracing::{debug, info};
 
@@ -844,42 +849,95 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
                 },
             );
 
-            let is_allowed_through_unstable_modules = |def_id| {
-                self.tcx.lookup_stability(def_id).is_some_and(|stab| match stab.level {
-                    StabilityLevel::Stable { allowed_through_unstable_modules, .. } => {
-                        allowed_through_unstable_modules
+            if item_is_allowed {
+                // The item itself is allowed; check whether the path there is also allowed.
+                let is_allowed_through_unstable_modules: Option<AllowedThroughUnstableModules> =
+                    self.tcx.lookup_stability(def_id).and_then(|stab| match stab.level {
+                        StabilityLevel::Stable { allowed_through_unstable_modules, .. } => {
+                            allowed_through_unstable_modules
+                        }
+                        _ => None,
+                    });
+
+                if is_allowed_through_unstable_modules.is_none() {
+                    // 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
+                    // 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.
+                    //
+                    // We check here rather than in `visit_path_segment` to prevent visiting the last
+                    // path segment twice
+                    //
+                    // We include special cases via #[rustc_allowed_through_unstable_modules] for items
+                    // that were accidentally stabilized through unstable paths before this check was
+                    // added, such as `core::intrinsics::transmute`
+                    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
+                            self.tcx.check_stability_allow_unstable(
+                                def_id,
+                                None,
+                                path.span,
+                                None,
+                                if is_unstable_reexport(self.tcx, id) {
+                                    AllowUnstable::Yes
+                                } else {
+                                    AllowUnstable::No
+                                },
+                            );
+                        }
                     }
-                    _ => false,
-                })
-            };
-
-            if item_is_allowed && !is_allowed_through_unstable_modules(def_id) {
-                // 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
-                // 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.
-                //
-                // We check here rather than in `visit_path_segment` to prevent visiting the last
-                // path segment twice
-                //
-                // We include special cases via #[rustc_allowed_through_unstable_modules] for items
-                // that were accidentally stabilized through unstable paths before this check was
-                // added, such as `core::intrinsics::transmute`
-                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
-                        self.tcx.check_stability_allow_unstable(
-                            def_id,
-                            None,
-                            path.span,
-                            None,
-                            if is_unstable_reexport(self.tcx, id) {
-                                AllowUnstable::Yes
-                            } else {
-                                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,
+                                );
+                            }
+                        }
                     }
                 }
             }
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index dcee96978d2..5e7c1756579 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -406,7 +406,7 @@ impl Item {
             // were never supposed to work at all.
             let stab = self.stability(tcx)?;
             if let rustc_attr_parsing::StabilityLevel::Stable {
-                allowed_through_unstable_modules: true,
+                allowed_through_unstable_modules: Some(_),
                 ..
             } = stab.level
             {
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index cbb3ce6abe6..4760e579199 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -316,7 +316,7 @@ impl DocFolder for CacheBuilder<'_, '_> {
 
                 let skip_because_unstable = matches!(
                     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 {
diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs
index febb52a3b00..9c958710c42 100644
--- a/src/librustdoc/passes/propagate_stability.rs
+++ b/src/librustdoc/passes/propagate_stability.rs
@@ -119,7 +119,7 @@ fn merge_stability(
     parent_stability: Option<Stability>,
 ) -> Option<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
         && let Some(parent_stab) = parent_stability
         && (parent_stab.is_unstable()
@@ -127,12 +127,12 @@ fn merge_stability(
     {
         parent_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
         && parent_stability.is_some_and(|stab| stab.is_stable())
     {
         // 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)
     } else {
         own_stability
diff --git a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
index 82ff13a5aff..8ec7bfe9edd 100644
--- a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
+++ b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
@@ -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)
             && let StabilityLevel::Stable {
                 since,
-                allowed_through_unstable_modules: false,
+                allowed_through_unstable_modules: None,
             } = stability.level
         {
             let stable = match since {
diff --git a/tests/ui/stability-attribute/allowed-through-unstable.rs b/tests/ui/stability-attribute/allowed-through-unstable.rs
index 29911a70be9..e03417a4dae 100644
--- a/tests/ui/stability-attribute/allowed-through-unstable.rs
+++ b/tests/ui/stability-attribute/allowed-through-unstable.rs
@@ -6,4 +6,5 @@
 extern crate allowed_through_unstable_core;
 
 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`
diff --git a/tests/ui/stability-attribute/allowed-through-unstable.stderr b/tests/ui/stability-attribute/allowed-through-unstable.stderr
index 00eea9f730d..8d07b0cf9e8 100644
--- a/tests/ui/stability-attribute/allowed-through-unstable.stderr
+++ b/tests/ui/stability-attribute/allowed-through-unstable.stderr
@@ -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`
-  --> $DIR/allowed-through-unstable.rs:9:5
+  --> $DIR/allowed-through-unstable.rs:10:5
    |
 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
    = 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`.
diff --git a/tests/ui/stability-attribute/auxiliary/allowed-through-unstable-core.rs b/tests/ui/stability-attribute/auxiliary/allowed-through-unstable-core.rs
index b597009a309..9dfbb451d04 100644
--- a/tests/ui/stability-attribute/auxiliary/allowed-through-unstable-core.rs
+++ b/tests/ui/stability-attribute/auxiliary/allowed-through-unstable-core.rs
@@ -10,5 +10,9 @@ pub mod unstable_module {
     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")]
     pub trait NewStableTraitNotAllowedThroughUnstable {}
 }