about summary refs log tree commit diff
path: root/compiler
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 /compiler
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
Diffstat (limited to 'compiler')
-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
5 files changed, 125 insertions, 51 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,
+                                );
+                            }
+                        }
                     }
                 }
             }