| //! A pass that annotates every item and method with its stability level, | |
| //! propagating default levels lexically from parent to children ast nodes. | |
| use std::num::NonZero; | |
| use rustc_ast::NodeId; | |
| use rustc_errors::{Applicability, Diag, Diagnostic, EmissionGuarantee, LintBuffer, msg}; | |
| use rustc_feature::GateIssue; | |
| use rustc_hir::attrs::{DeprecatedSince, Deprecation}; | |
| use rustc_hir::def_id::{DefId, LocalDefId}; | |
| use rustc_hir::{self as hir, ConstStability, DefaultBodyStability, HirId, Stability}; | |
| use rustc_macros::{Decodable, Encodable, HashStable, Subdiagnostic}; | |
| use rustc_session::Session; | |
| use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE}; | |
| use rustc_session::lint::{DeprecatedSinceKind, Level, Lint}; | |
| use rustc_session::parse::feature_err_issue; | |
| use rustc_span::{Span, Symbol, sym}; | |
| use tracing::debug; | |
| pub use self::StabilityLevel::*; | |
| use crate::ty::TyCtxt; | |
| use crate::ty::print::with_no_trimmed_paths; | |
| #[derive(PartialEq, Clone, Copy, Debug)] | |
| pub enum StabilityLevel { | |
| Unstable, | |
| Stable, | |
| } | |
| #[derive(Copy, Clone)] | |
| pub enum UnstableKind { | |
| /// Enforcing regular stability of an item | |
| Regular, | |
| /// Enforcing const stability of an item | |
| Const(Span), | |
| } | |
| /// An entry in the `depr_map`. | |
| #[derive(Copy, Clone, HashStable, Debug, Encodable, Decodable)] | |
| pub struct DeprecationEntry { | |
| /// The metadata of the attribute associated with this entry. | |
| pub attr: Deprecation, | |
| /// The `DefId` where the attr was originally attached. `None` for non-local | |
| /// `DefId`'s. | |
| origin: Option<LocalDefId>, | |
| } | |
| impl DeprecationEntry { | |
| pub fn local(attr: Deprecation, def_id: LocalDefId) -> DeprecationEntry { | |
| DeprecationEntry { attr, origin: Some(def_id) } | |
| } | |
| pub fn external(attr: Deprecation) -> DeprecationEntry { | |
| DeprecationEntry { attr, origin: None } | |
| } | |
| pub fn same_origin(&self, other: &DeprecationEntry) -> bool { | |
| match (self.origin, other.origin) { | |
| (Some(o1), Some(o2)) => o1 == o2, | |
| _ => false, | |
| } | |
| } | |
| } | |
| pub fn report_unstable( | |
| sess: &Session, | |
| feature: Symbol, | |
| reason: Option<Symbol>, | |
| issue: Option<NonZero<u32>>, | |
| suggestion: Option<(Span, String, String, Applicability)>, | |
| span: Span, | |
| kind: UnstableKind, | |
| ) { | |
| let qual = match kind { | |
| UnstableKind::Regular => "", | |
| UnstableKind::Const(_) => " const", | |
| }; | |
| let msg = match reason { | |
| Some(r) => format!("use of unstable{qual} library feature `{feature}`: {r}"), | |
| None => format!("use of unstable{qual} library feature `{feature}`"), | |
| }; | |
| let mut err = feature_err_issue(sess, feature, span, GateIssue::Library(issue), msg); | |
| if let Some((inner_types, msg, sugg, applicability)) = suggestion { | |
| err.span_suggestion(inner_types, msg, sugg, applicability); | |
| } | |
| if let UnstableKind::Const(kw) = kind { | |
| err.span_label(kw, "trait is not stable as const yet"); | |
| } | |
| err.emit(); | |
| } | |
| fn deprecation_lint(is_in_effect: bool) -> &'static Lint { | |
| if is_in_effect { DEPRECATED } else { DEPRECATED_IN_FUTURE } | |
| } | |
| #[derive(Subdiagnostic)] | |
| #[suggestion( | |
| "replace the use of the deprecated {$kind}", | |
| code = "{suggestion}", | |
| style = "verbose", | |
| applicability = "machine-applicable" | |
| )] | |
| pub struct DeprecationSuggestion { | |
| #[primary_span] | |
| pub span: Span, | |
| pub kind: String, | |
| pub suggestion: Symbol, | |
| } | |
| pub struct Deprecated { | |
| pub sub: Option<DeprecationSuggestion>, | |
| pub kind: String, | |
| pub path: String, | |
| pub note: Option<Symbol>, | |
| pub since_kind: DeprecatedSinceKind, | |
| } | |
| impl<'a, G: EmissionGuarantee> rustc_errors::Diagnostic<'a, G> for Deprecated { | |
| fn into_diag( | |
| self, | |
| dcx: rustc_errors::DiagCtxtHandle<'a>, | |
| level: rustc_errors::Level, | |
| ) -> Diag<'a, G> { | |
| let Self { sub, kind, path, note, since_kind } = self; | |
| let mut diag = Diag::new(dcx, level, match &since_kind { | |
| DeprecatedSinceKind::InEffect => msg!( | |
| "use of deprecated {$kind} `{$path}`{$has_note -> | |
| [true] : {$note} | |
| *[other] {\"\"} | |
| }" | |
| ), | |
| DeprecatedSinceKind::InFuture => msg!( | |
| "use of {$kind} `{$path}` that will be deprecated in a future Rust version{$has_note -> | |
| [true] : {$note} | |
| *[other] {\"\"} | |
| }" | |
| ), | |
| DeprecatedSinceKind::InVersion(_) => { | |
| msg!( | |
| "use of {$kind} `{$path}` that will be deprecated in future version {$version}{$has_note -> | |
| [true] : {$note} | |
| *[other] {\"\"} | |
| }" | |
| ) | |
| } | |
| }) | |
| .with_arg("kind", kind) | |
| .with_arg("path", path); | |
| if let DeprecatedSinceKind::InVersion(version) = since_kind { | |
| diag.arg("version", version); | |
| } | |
| if let Some(note) = note { | |
| diag.arg("has_note", true); | |
| diag.arg("note", note); | |
| } else { | |
| diag.arg("has_note", false); | |
| } | |
| if let Some(sub) = sub { | |
| diag.subdiagnostic(sub); | |
| } | |
| diag | |
| } | |
| } | |
| fn deprecated_since_kind(is_in_effect: bool, since: DeprecatedSince) -> DeprecatedSinceKind { | |
| if is_in_effect { | |
| DeprecatedSinceKind::InEffect | |
| } else { | |
| match since { | |
| DeprecatedSince::RustcVersion(version) => { | |
| DeprecatedSinceKind::InVersion(version.to_string()) | |
| } | |
| DeprecatedSince::Future => DeprecatedSinceKind::InFuture, | |
| DeprecatedSince::NonStandard(_) | |
| | DeprecatedSince::Unspecified | |
| | DeprecatedSince::Err => { | |
| unreachable!("this deprecation is always in effect; {since:?}") | |
| } | |
| } | |
| } | |
| } | |
| pub fn early_report_macro_deprecation( | |
| lint_buffer: &mut LintBuffer, | |
| depr: &Deprecation, | |
| suggestion_span: Span, | |
| node_id: NodeId, | |
| path: String, | |
| ) { | |
| if suggestion_span.in_derive_expansion() { | |
| return; | |
| } | |
| let is_in_effect = depr.is_in_effect(); | |
| let suggestion = depr.suggestion; | |
| let note = depr.note.map(|ident| ident.name); | |
| let since_kind = deprecated_since_kind(is_in_effect, depr.since); | |
| lint_buffer.dyn_buffer_lint( | |
| deprecation_lint(is_in_effect), | |
| node_id, | |
| suggestion_span, | |
| move |dcx, level| { | |
| let sub = suggestion.map(|suggestion| DeprecationSuggestion { | |
| span: suggestion_span, | |
| kind: "macro".to_owned(), | |
| suggestion, | |
| }); | |
| Deprecated { sub, kind: "macro".to_owned(), path, note, since_kind } | |
| .into_diag(dcx, level) | |
| }, | |
| ); | |
| } | |
| fn late_report_deprecation( | |
| tcx: TyCtxt<'_>, | |
| depr: &Deprecation, | |
| span: Span, | |
| method_span: Option<Span>, | |
| hir_id: HirId, | |
| def_id: DefId, | |
| ) { | |
| if span.in_derive_expansion() { | |
| return; | |
| } | |
| let is_in_effect = depr.is_in_effect(); | |
| let lint = deprecation_lint(is_in_effect); | |
| // 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 tcx.lint_level_at_node(lint, hir_id).level == Level::Allow { | |
| return; | |
| } | |
| let def_path = with_no_trimmed_paths!(tcx.def_path_str(def_id)); | |
| let def_kind = tcx.def_descr(def_id); | |
| let method_span = method_span.unwrap_or(span); | |
| let suggestion = | |
| if let hir::Node::Expr(_) = tcx.hir_node(hir_id) { depr.suggestion } else { None }; | |
| let diag = Deprecated { | |
| sub: suggestion.map(|suggestion| DeprecationSuggestion { | |
| span: method_span, | |
| kind: def_kind.to_owned(), | |
| suggestion, | |
| }), | |
| kind: def_kind.to_owned(), | |
| path: def_path, | |
| note: depr.note.map(|ident| ident.name), | |
| since_kind: deprecated_since_kind(is_in_effect, depr.since), | |
| }; | |
| tcx.emit_node_span_lint(lint, hir_id, method_span, diag); | |
| } | |
| /// Result of `TyCtxt::eval_stability`. | |
| pub enum EvalResult { | |
| /// We can use the item because it is stable or we provided the | |
| /// corresponding feature gate. | |
| Allow, | |
| /// We cannot use the item because it is unstable and we did not provide the | |
| /// corresponding feature gate. | |
| Deny { | |
| feature: Symbol, | |
| reason: Option<Symbol>, | |
| issue: Option<NonZero<u32>>, | |
| suggestion: Option<(Span, String, String, Applicability)>, | |
| }, | |
| /// The item does not have the `#[stable]` or `#[unstable]` marker assigned. | |
| Unmarked, | |
| } | |
| // See issue #83250. | |
| fn suggestion_for_allocator_api( | |
| tcx: TyCtxt<'_>, | |
| def_id: DefId, | |
| span: Span, | |
| feature: Symbol, | |
| ) -> Option<(Span, String, String, Applicability)> { | |
| if feature == sym::allocator_api { | |
| if let Some(trait_) = tcx.opt_parent(def_id) { | |
| if tcx.is_diagnostic_item(sym::Vec, trait_) { | |
| let sm = tcx.sess.psess.source_map(); | |
| let inner_types = sm.span_extend_to_prev_char(span, '<', true); | |
| if let Ok(snippet) = sm.span_to_snippet(inner_types) { | |
| return Some(( | |
| inner_types, | |
| "consider wrapping the inner types in tuple".to_string(), | |
| format!("({snippet})"), | |
| Applicability::MaybeIncorrect, | |
| )); | |
| } | |
| } | |
| } | |
| } | |
| None | |
| } | |
| /// An override option for eval_stability. | |
| pub enum AllowUnstable { | |
| /// Don't emit an unstable error for the item | |
| Yes, | |
| /// Handle the item normally | |
| No, | |
| } | |
| impl<'tcx> TyCtxt<'tcx> { | |
| /// Evaluates the stability of an item. | |
| /// | |
| /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding | |
| /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending | |
| /// unstable feature otherwise. | |
| /// | |
| /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been | |
| /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to | |
| /// `id`. | |
| pub fn eval_stability( | |
| self, | |
| def_id: DefId, | |
| id: Option<HirId>, | |
| span: Span, | |
| method_span: Option<Span>, | |
| ) -> EvalResult { | |
| self.eval_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No) | |
| } | |
| /// Evaluates the stability of an item. | |
| /// | |
| /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding | |
| /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending | |
| /// unstable feature otherwise. | |
| /// | |
| /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been | |
| /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to | |
| /// `id`. | |
| /// | |
| /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally. | |
| pub fn eval_stability_allow_unstable( | |
| self, | |
| def_id: DefId, | |
| id: Option<HirId>, | |
| span: Span, | |
| method_span: Option<Span>, | |
| allow_unstable: AllowUnstable, | |
| ) -> EvalResult { | |
| // Deprecated attributes apply in-crate and cross-crate. | |
| if let Some(id) = id { | |
| if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) { | |
| let parent_def_id = self.hir_get_parent_item(id); | |
| let skip = self | |
| .lookup_deprecation_entry(parent_def_id.to_def_id()) | |
| .is_some_and(|parent_depr| parent_depr.same_origin(&depr_entry)); | |
| // #[deprecated] doesn't emit a notice if we're not on the | |
| // topmost deprecation. For example, if a struct is deprecated, | |
| // the use of a field won't be linted. | |
| // | |
| // With #![staged_api], we want to emit down the whole | |
| // hierarchy. | |
| let depr_attr = &depr_entry.attr; | |
| if !skip || depr_attr.is_since_rustc_version() { | |
| late_report_deprecation(self, depr_attr, span, method_span, id, def_id); | |
| } | |
| }; | |
| } | |
| let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some(); | |
| if !is_staged_api { | |
| return EvalResult::Allow; | |
| } | |
| // Only the cross-crate scenario matters when checking unstable APIs | |
| let cross_crate = !def_id.is_local(); | |
| if !cross_crate { | |
| return EvalResult::Allow; | |
| } | |
| let stability = self.lookup_stability(def_id); | |
| debug!( | |
| "stability: \ | |
| inspecting def_id={:?} span={:?} of stability={:?}", | |
| def_id, span, stability | |
| ); | |
| match stability { | |
| Some(Stability { | |
| level: hir::StabilityLevel::Unstable { reason, issue, implied_by, .. }, | |
| feature, | |
| .. | |
| }) => { | |
| if span.allows_unstable(feature) { | |
| debug!("stability: skipping span={:?} since it is internal", span); | |
| return EvalResult::Allow; | |
| } | |
| if self.features().enabled(feature) { | |
| return EvalResult::Allow; | |
| } | |
| // If this item was previously part of a now-stabilized feature which is still | |
| // enabled (i.e. the user hasn't removed the attribute for the stabilized feature | |
| // yet) then allow use of this item. | |
| if let Some(implied_by) = implied_by | |
| && self.features().enabled(implied_by) | |
| { | |
| return EvalResult::Allow; | |
| } | |
| // When we're compiling the compiler itself we may pull in | |
| // crates from crates.io, but those crates may depend on other | |
| // crates also pulled in from crates.io. We want to ideally be | |
| // able to compile everything without requiring upstream | |
| // modifications, so in the case that this looks like a | |
| // `rustc_private` crate (e.g., a compiler crate) and we also have | |
| // the `-Z force-unstable-if-unmarked` flag present (we're | |
| // compiling a compiler crate), then let this missing feature | |
| // annotation slide. | |
| if feature == sym::rustc_private | |
| && issue == NonZero::new(27812) | |
| && self.sess.opts.unstable_opts.force_unstable_if_unmarked | |
| { | |
| return EvalResult::Allow; | |
| } | |
| if matches!(allow_unstable, AllowUnstable::Yes) { | |
| return EvalResult::Allow; | |
| } | |
| let suggestion = suggestion_for_allocator_api(self, def_id, span, feature); | |
| EvalResult::Deny { feature, reason: reason.to_opt_reason(), issue, suggestion } | |
| } | |
| Some(_) => { | |
| // Stable APIs are always ok to call and deprecated APIs are | |
| // handled by the lint emitting logic above. | |
| EvalResult::Allow | |
| } | |
| None => EvalResult::Unmarked, | |
| } | |
| } | |
| /// Evaluates the default-impl stability of an item. | |
| /// | |
| /// Returns `EvalResult::Allow` if the item's default implementation is stable, or unstable but the corresponding | |
| /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending | |
| /// unstable feature otherwise. | |
| pub fn eval_default_body_stability(self, def_id: DefId, span: Span) -> EvalResult { | |
| let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some(); | |
| if !is_staged_api { | |
| return EvalResult::Allow; | |
| } | |
| // Only the cross-crate scenario matters when checking unstable APIs | |
| let cross_crate = !def_id.is_local(); | |
| if !cross_crate { | |
| return EvalResult::Allow; | |
| } | |
| let stability = self.lookup_default_body_stability(def_id); | |
| debug!( | |
| "body stability: inspecting def_id={def_id:?} span={span:?} of stability={stability:?}" | |
| ); | |
| match stability { | |
| Some(DefaultBodyStability { | |
| level: hir::StabilityLevel::Unstable { reason, issue, .. }, | |
| feature, | |
| }) => { | |
| if span.allows_unstable(feature) { | |
| debug!("body stability: skipping span={:?} since it is internal", span); | |
| return EvalResult::Allow; | |
| } | |
| if self.features().enabled(feature) { | |
| return EvalResult::Allow; | |
| } | |
| EvalResult::Deny { | |
| feature, | |
| reason: reason.to_opt_reason(), | |
| issue, | |
| suggestion: None, | |
| } | |
| } | |
| Some(_) => { | |
| // Stable APIs are always ok to call | |
| EvalResult::Allow | |
| } | |
| None => EvalResult::Unmarked, | |
| } | |
| } | |
| /// Checks if an item is stable or error out. | |
| /// | |
| /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not | |
| /// exist, emits an error. | |
| /// | |
| /// This function will also check if the item is deprecated. | |
| /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted. | |
| /// | |
| /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature. | |
| pub fn check_stability( | |
| self, | |
| def_id: DefId, | |
| id: Option<HirId>, | |
| span: Span, | |
| method_span: Option<Span>, | |
| ) -> bool { | |
| self.check_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No) | |
| } | |
| /// Checks if an item is stable or error out. | |
| /// | |
| /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not | |
| /// exist, emits an error. | |
| /// | |
| /// This function will also check if the item is deprecated. | |
| /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted. | |
| /// | |
| /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally. | |
| /// | |
| /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature. | |
| pub fn check_stability_allow_unstable( | |
| self, | |
| def_id: DefId, | |
| id: Option<HirId>, | |
| span: Span, | |
| method_span: Option<Span>, | |
| allow_unstable: AllowUnstable, | |
| ) -> bool { | |
| self.check_optional_stability( | |
| def_id, | |
| id, | |
| span, | |
| method_span, | |
| allow_unstable, | |
| |span, def_id| { | |
| // The API could be uncallable for other reasons, for example when a private module | |
| // was referenced. | |
| self.dcx().span_delayed_bug(span, format!("encountered unmarked API: {def_id:?}")); | |
| }, | |
| ) | |
| } | |
| /// Like `check_stability`, except that we permit items to have custom behaviour for | |
| /// missing stability attributes (not necessarily just emit a `bug!`). This is necessary | |
| /// for default generic parameters, which only have stability attributes if they were | |
| /// added after the type on which they're defined. | |
| /// | |
| /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature. | |
| pub fn check_optional_stability( | |
| self, | |
| def_id: DefId, | |
| id: Option<HirId>, | |
| span: Span, | |
| method_span: Option<Span>, | |
| allow_unstable: AllowUnstable, | |
| unmarked: impl FnOnce(Span, DefId), | |
| ) -> bool { | |
| let eval_result = | |
| self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable); | |
| let is_allowed = matches!(eval_result, EvalResult::Allow); | |
| match eval_result { | |
| EvalResult::Allow => {} | |
| EvalResult::Deny { feature, reason, issue, suggestion } => report_unstable( | |
| self.sess, | |
| feature, | |
| reason, | |
| issue, | |
| suggestion, | |
| span, | |
| UnstableKind::Regular, | |
| ), | |
| EvalResult::Unmarked => unmarked(span, def_id), | |
| } | |
| is_allowed | |
| } | |
| /// This function is analogous to `check_optional_stability` but with the logic in | |
| /// `eval_stability_allow_unstable` inlined, and which operating on const stability | |
| /// instead of regular stability. | |
| /// | |
| /// This enforces *syntactical* const stability of const traits. In other words, | |
| /// it enforces the ability to name `[const]`/`const` traits in trait bounds in various | |
| /// syntax positions in HIR (including in the trait of an impl header). | |
| pub fn check_const_stability(self, def_id: DefId, span: Span, const_kw_span: Span) { | |
| let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some(); | |
| if !is_staged_api { | |
| return; | |
| } | |
| // Only the cross-crate scenario matters when checking unstable APIs | |
| let cross_crate = !def_id.is_local(); | |
| if !cross_crate { | |
| return; | |
| } | |
| let stability = self.lookup_const_stability(def_id); | |
| debug!( | |
| "stability: \ | |
| inspecting def_id={:?} span={:?} of stability={:?}", | |
| def_id, span, stability | |
| ); | |
| match stability { | |
| Some(ConstStability { | |
| level: hir::StabilityLevel::Unstable { reason, issue, implied_by, .. }, | |
| feature, | |
| .. | |
| }) => { | |
| if span.allows_unstable(feature) { | |
| debug!("body stability: skipping span={:?} since it is internal", span); | |
| return; | |
| } | |
| if self.features().enabled(feature) { | |
| return; | |
| } | |
| // If this item was previously part of a now-stabilized feature which is still | |
| // enabled (i.e. the user hasn't removed the attribute for the stabilized feature | |
| // yet) then allow use of this item. | |
| if let Some(implied_by) = implied_by | |
| && self.features().enabled(implied_by) | |
| { | |
| return; | |
| } | |
| report_unstable( | |
| self.sess, | |
| feature, | |
| reason.to_opt_reason(), | |
| issue, | |
| None, | |
| span, | |
| UnstableKind::Const(const_kw_span), | |
| ); | |
| } | |
| Some(_) | None => {} | |
| } | |
| } | |
| pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> { | |
| self.lookup_deprecation_entry(id).map(|depr| depr.attr) | |
| } | |
| } |