1 //! A pass that annotates every item and method with its stability level,
2 //! propagating default levels lexically from parent to children ast nodes.
4 pub use self::StabilityLevel::*;
6 use crate::ty::{self, DefIdTree, TyCtxt};
8 use rustc_attr::{self as attr, ConstStability, DefaultBodyStability, Deprecation, Stability};
9 use rustc_data_structures::fx::FxHashMap;
10 use rustc_errors::{Applicability, Diagnostic};
11 use rustc_feature::GateIssue;
12 use rustc_hir::def::DefKind;
13 use rustc_hir::def_id::{DefId, LocalDefId};
14 use rustc_hir::{self as hir, HirId};
15 use rustc_middle::ty::print::with_no_trimmed_paths;
16 use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
17 use rustc_session::lint::{BuiltinLintDiagnostics, Level, Lint, LintBuffer};
18 use rustc_session::parse::feature_err_issue;
19 use rustc_session::Session;
20 use rustc_span::symbol::{sym, Symbol};
22 use std::num::NonZeroU32;
24 #[derive(PartialEq, Clone, Copy, Debug)]
25 pub enum StabilityLevel {
30 /// An entry in the `depr_map`.
31 #[derive(Copy, Clone, HashStable, Debug, Encodable, Decodable)]
32 pub struct DeprecationEntry {
33 /// The metadata of the attribute associated with this entry.
34 pub attr: Deprecation,
35 /// The `DefId` where the attr was originally attached. `None` for non-local
37 origin: Option<LocalDefId>,
40 impl DeprecationEntry {
41 pub fn local(attr: Deprecation, def_id: LocalDefId) -> DeprecationEntry {
42 DeprecationEntry { attr, origin: Some(def_id) }
45 pub fn external(attr: Deprecation) -> DeprecationEntry {
46 DeprecationEntry { attr, origin: None }
49 pub fn same_origin(&self, other: &DeprecationEntry) -> bool {
50 match (self.origin, other.origin) {
51 (Some(o1), Some(o2)) => o1 == o2,
57 /// A stability index, giving the stability level for items and methods.
58 #[derive(HashStable, Debug)]
60 /// This is mostly a cache, except the stabilities of local items
61 /// are filled by the annotator.
62 pub stab_map: FxHashMap<LocalDefId, Stability>,
63 pub const_stab_map: FxHashMap<LocalDefId, ConstStability>,
64 pub default_body_stab_map: FxHashMap<LocalDefId, DefaultBodyStability>,
65 pub depr_map: FxHashMap<LocalDefId, DeprecationEntry>,
66 /// Mapping from feature name to feature name based on the `implied_by` field of `#[unstable]`
67 /// attributes. If a `#[unstable(feature = "implier", implied_by = "impliee")]` attribute
68 /// exists, then this map will have a `impliee -> implier` entry.
70 /// This mapping is necessary unless both the `#[stable]` and `#[unstable]` attributes should
71 /// specify their implications (both `implies` and `implied_by`). If only one of the two
72 /// attributes do (as in the current implementation, `implied_by` in `#[unstable]`), then this
73 /// mapping is necessary for diagnostics. When a "unnecessary feature attribute" error is
74 /// reported, only the `#[stable]` attribute information is available, so the map is necessary
75 /// to know that the feature implies another feature. If it were reversed, and the `#[stable]`
76 /// attribute had an `implies` meta item, then a map would be necessary when avoiding a "use of
77 /// unstable feature" error for a feature that was implied.
78 pub implications: FxHashMap<Symbol, Symbol>,
82 pub fn local_stability(&self, def_id: LocalDefId) -> Option<Stability> {
83 self.stab_map.get(&def_id).copied()
86 pub fn local_const_stability(&self, def_id: LocalDefId) -> Option<ConstStability> {
87 self.const_stab_map.get(&def_id).copied()
90 pub fn local_default_body_stability(&self, def_id: LocalDefId) -> Option<DefaultBodyStability> {
91 self.default_body_stab_map.get(&def_id).copied()
94 pub fn local_deprecation_entry(&self, def_id: LocalDefId) -> Option<DeprecationEntry> {
95 self.depr_map.get(&def_id).cloned()
99 pub fn report_unstable(
102 reason: Option<Symbol>,
103 issue: Option<NonZeroU32>,
104 suggestion: Option<(Span, String, String, Applicability)>,
107 soft_handler: impl FnOnce(&'static Lint, Span, &str),
109 let msg = match reason {
110 Some(r) => format!("use of unstable library feature '{}': {}", feature, r),
111 None => format!("use of unstable library feature '{}'", &feature),
115 soft_handler(SOFT_UNSTABLE, span, &msg)
118 feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg);
119 if let Some((inner_types, ref msg, sugg, applicability)) = suggestion {
120 err.span_suggestion(inner_types, msg, sugg, applicability);
126 /// Checks whether an item marked with `deprecated(since="X")` is currently
127 /// deprecated (i.e., whether X is not greater than the current rustc version).
128 pub fn deprecation_in_effect(depr: &Deprecation) -> bool {
129 let is_since_rustc_version = depr.is_since_rustc_version;
130 let since = depr.since.as_ref().map(Symbol::as_str);
132 fn parse_version(ver: &str) -> Vec<u32> {
133 // We ignore non-integer components of the version (e.g., "nightly").
134 ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect()
137 if !is_since_rustc_version {
138 // The `since` field doesn't have semantic purpose without `#![staged_api]`.
142 if let Some(since) = since {
147 if let Some(rustc) = option_env!("CFG_RELEASE") {
148 let since: Vec<u32> = parse_version(&since);
149 let rustc: Vec<u32> = parse_version(rustc);
150 // We simply treat invalid `since` attributes as relating to a previous
151 // Rust version, thus always displaying the warning.
152 if since.len() != 3 {
155 return since <= rustc;
159 // Assume deprecation is in effect if "since" field is missing
160 // or if we can't determine the current Rust version.
164 pub fn deprecation_suggestion(
165 diag: &mut Diagnostic,
167 suggestion: Option<Symbol>,
170 if let Some(suggestion) = suggestion {
171 diag.span_suggestion_verbose(
173 &format!("replace the use of the deprecated {}", kind),
175 Applicability::MachineApplicable,
180 fn deprecation_lint(is_in_effect: bool) -> &'static Lint {
181 if is_in_effect { DEPRECATED } else { DEPRECATED_IN_FUTURE }
184 fn deprecation_message(
186 since: Option<Symbol>,
187 note: Option<Symbol>,
191 let message = if is_in_effect {
192 format!("use of deprecated {} `{}`", kind, path)
194 let since = since.as_ref().map(Symbol::as_str);
196 if since == Some("TBD") {
197 format!("use of {} `{}` that will be deprecated in a future Rust version", kind, path)
200 "use of {} `{}` that will be deprecated in future version {}",
209 Some(reason) => format!("{}: {}", message, reason),
214 pub fn deprecation_message_and_lint(
218 ) -> (String, &'static Lint) {
219 let is_in_effect = deprecation_in_effect(depr);
221 deprecation_message(is_in_effect, depr.since, depr.note, kind, path),
222 deprecation_lint(is_in_effect),
226 pub fn early_report_deprecation<'a>(
227 lint_buffer: &'a mut LintBuffer,
229 suggestion: Option<Symbol>,
234 if span.in_derive_expansion() {
238 let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span);
239 lint_buffer.buffer_lint_with_diagnostic(lint, node_id, span, message, diag);
242 fn late_report_deprecation(
245 suggestion: Option<Symbol>,
248 method_span: Option<Span>,
252 if span.in_derive_expansion() {
255 let method_span = method_span.unwrap_or(span);
256 tcx.struct_span_lint_hir(lint, hir_id, method_span, |lint| {
257 let mut diag = lint.build(message);
258 if let hir::Node::Expr(_) = tcx.hir().get(hir_id) {
259 let kind = tcx.def_kind(def_id).descr(def_id);
260 deprecation_suggestion(&mut diag, kind, suggestion, method_span);
266 /// Result of `TyCtxt::eval_stability`.
267 pub enum EvalResult {
268 /// We can use the item because it is stable or we provided the
269 /// corresponding feature gate.
271 /// We cannot use the item because it is unstable and we did not provide the
272 /// corresponding feature gate.
275 reason: Option<Symbol>,
276 issue: Option<NonZeroU32>,
277 suggestion: Option<(Span, String, String, Applicability)>,
280 /// The item does not have the `#[stable]` or `#[unstable]` marker assigned.
285 fn skip_stability_check_due_to_privacy(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
286 if tcx.def_kind(def_id) == DefKind::TyParam {
287 // Have no visibility, considered public for the purpose of this check.
290 match tcx.visibility(def_id) {
291 // Must check stability for `pub` items.
292 ty::Visibility::Public => false,
294 // These are not visible outside crate; therefore
295 // stability markers are irrelevant, if even present.
296 ty::Visibility::Restricted(..) => true,
301 fn suggestion_for_allocator_api(
306 ) -> Option<(Span, String, String, Applicability)> {
307 if feature == sym::allocator_api {
308 if let Some(trait_) = tcx.opt_parent(def_id) {
309 if tcx.is_diagnostic_item(sym::Vec, trait_) {
310 let sm = tcx.sess.parse_sess.source_map();
311 let inner_types = sm.span_extend_to_prev_char(span, '<', true);
312 if let Ok(snippet) = sm.span_to_snippet(inner_types) {
315 "consider wrapping the inner types in tuple".to_string(),
316 format!("({})", snippet),
317 Applicability::MaybeIncorrect,
326 /// An override option for eval_stability.
327 pub enum AllowUnstable {
328 /// Don't emit an unstable error for the item
330 /// Handle the item normally
334 impl<'tcx> TyCtxt<'tcx> {
335 /// Evaluates the stability of an item.
337 /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
338 /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
339 /// unstable feature otherwise.
341 /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
342 /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
344 pub fn eval_stability(
349 method_span: Option<Span>,
351 self.eval_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No)
354 /// Evaluates the stability of an item.
356 /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
357 /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
358 /// unstable feature otherwise.
360 /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
361 /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
364 /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
365 pub fn eval_stability_allow_unstable(
370 method_span: Option<Span>,
371 allow_unstable: AllowUnstable,
373 // Deprecated attributes apply in-crate and cross-crate.
374 if let Some(id) = id {
375 if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) {
376 let parent_def_id = self.hir().get_parent_item(id);
378 .lookup_deprecation_entry(parent_def_id.to_def_id())
379 .map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry));
381 // #[deprecated] doesn't emit a notice if we're not on the
382 // topmost deprecation. For example, if a struct is deprecated,
383 // the use of a field won't be linted.
385 // With #![staged_api], we want to emit down the whole
387 let depr_attr = &depr_entry.attr;
388 if !skip || depr_attr.is_since_rustc_version {
389 // Calculating message for lint involves calling `self.def_path_str`.
390 // Which by default to calculate visible path will invoke expensive `visible_parent_map` query.
391 // So we skip message calculation altogether, if lint is allowed.
392 let is_in_effect = deprecation_in_effect(depr_attr);
393 let lint = deprecation_lint(is_in_effect);
394 if self.lint_level_at_node(lint, id).0 != Level::Allow {
395 let def_path = with_no_trimmed_paths!(self.def_path_str(def_id));
396 let def_kind = self.def_kind(def_id).descr(def_id);
398 late_report_deprecation(
400 &deprecation_message(
407 depr_attr.suggestion,
419 let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some();
421 return EvalResult::Allow;
424 // Only the cross-crate scenario matters when checking unstable APIs
425 let cross_crate = !def_id.is_local();
427 return EvalResult::Allow;
430 let stability = self.lookup_stability(def_id);
433 inspecting def_id={:?} span={:?} of stability={:?}",
434 def_id, span, stability
437 // Issue #38412: private items lack stability markers.
438 if skip_stability_check_due_to_privacy(self, def_id) {
439 return EvalResult::Allow;
444 level: attr::Unstable { reason, issue, is_soft, implied_by },
448 if span.allows_unstable(feature) {
449 debug!("stability: skipping span={:?} since it is internal", span);
450 return EvalResult::Allow;
452 if self.features().active(feature) {
453 return EvalResult::Allow;
456 // If this item was previously part of a now-stabilized feature which is still
457 // active (i.e. the user hasn't removed the attribute for the stabilized feature
458 // yet) then allow use of this item.
459 if let Some(implied_by) = implied_by && self.features().active(implied_by) {
460 return EvalResult::Allow;
463 // When we're compiling the compiler itself we may pull in
464 // crates from crates.io, but those crates may depend on other
465 // crates also pulled in from crates.io. We want to ideally be
466 // able to compile everything without requiring upstream
467 // modifications, so in the case that this looks like a
468 // `rustc_private` crate (e.g., a compiler crate) and we also have
469 // the `-Z force-unstable-if-unmarked` flag present (we're
470 // compiling a compiler crate), then let this missing feature
472 if feature == sym::rustc_private && issue == NonZeroU32::new(27812) {
473 if self.sess.opts.unstable_opts.force_unstable_if_unmarked {
474 return EvalResult::Allow;
478 if matches!(allow_unstable, AllowUnstable::Yes) {
479 return EvalResult::Allow;
482 let suggestion = suggestion_for_allocator_api(self, def_id, span, feature);
485 reason: reason.to_opt_reason(),
492 // Stable APIs are always ok to call and deprecated APIs are
493 // handled by the lint emitting logic above.
496 None => EvalResult::Unmarked,
500 /// Evaluates the default-impl stability of an item.
502 /// Returns `EvalResult::Allow` if the item's default implementation is stable, or unstable but the corresponding
503 /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
504 /// unstable feature otherwise.
505 pub fn eval_default_body_stability(self, def_id: DefId, span: Span) -> EvalResult {
506 let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some();
508 return EvalResult::Allow;
511 // Only the cross-crate scenario matters when checking unstable APIs
512 let cross_crate = !def_id.is_local();
514 return EvalResult::Allow;
517 let stability = self.lookup_default_body_stability(def_id);
519 "body stability: inspecting def_id={def_id:?} span={span:?} of stability={stability:?}"
522 // Issue #38412: private items lack stability markers.
523 if skip_stability_check_due_to_privacy(self, def_id) {
524 return EvalResult::Allow;
528 Some(DefaultBodyStability {
529 level: attr::Unstable { reason, issue, is_soft, .. },
532 if span.allows_unstable(feature) {
533 debug!("body stability: skipping span={:?} since it is internal", span);
534 return EvalResult::Allow;
536 if self.features().active(feature) {
537 return EvalResult::Allow;
542 reason: reason.to_opt_reason(),
549 // Stable APIs are always ok to call
552 None => EvalResult::Unmarked,
556 /// Checks if an item is stable or error out.
558 /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
559 /// exist, emits an error.
561 /// This function will also check if the item is deprecated.
562 /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted.
564 /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature.
565 pub fn check_stability(
570 method_span: Option<Span>,
572 self.check_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No)
575 /// Checks if an item is stable or error out.
577 /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
578 /// exist, emits an error.
580 /// This function will also check if the item is deprecated.
581 /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted.
583 /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
585 /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature.
586 pub fn check_stability_allow_unstable(
591 method_span: Option<Span>,
592 allow_unstable: AllowUnstable,
594 self.check_optional_stability(
601 // The API could be uncallable for other reasons, for example when a private module
603 self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id));
608 /// Like `check_stability`, except that we permit items to have custom behaviour for
609 /// missing stability attributes (not necessarily just emit a `bug!`). This is necessary
610 /// for default generic parameters, which only have stability attributes if they were
611 /// added after the type on which they're defined.
613 /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature.
614 pub fn check_optional_stability(
619 method_span: Option<Span>,
620 allow_unstable: AllowUnstable,
621 unmarked: impl FnOnce(Span, DefId),
623 let soft_handler = |lint, span, msg: &_| {
624 self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
625 lint.build(msg).emit();
629 self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable);
630 let is_allowed = matches!(eval_result, EvalResult::Allow);
632 EvalResult::Allow => {}
633 EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => report_unstable(
643 EvalResult::Unmarked => unmarked(span, def_id),
649 pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> {
650 self.lookup_deprecation_entry(id).map(|depr| depr.attr)