rustc_middle/middle/
stability.rs

1//! A pass that annotates every item and method with its stability level,
2//! propagating default levels lexically from parent to children ast nodes.
3
4use std::num::NonZero;
5
6use rustc_ast::NodeId;
7use rustc_attr_data_structures::{
8    self as attrs, ConstStability, DefaultBodyStability, DeprecatedSince, Deprecation, Stability,
9};
10use rustc_errors::{Applicability, Diag, EmissionGuarantee};
11use rustc_feature::GateIssue;
12use rustc_hir::def_id::{DefId, LocalDefId};
13use rustc_hir::{self as hir, HirId};
14use rustc_macros::{Decodable, Encodable, HashStable, Subdiagnostic};
15use rustc_session::Session;
16use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
17use rustc_session::lint::{BuiltinLintDiag, DeprecatedSinceKind, Level, Lint, LintBuffer};
18use rustc_session::parse::feature_err_issue;
19use rustc_span::{Span, Symbol, sym};
20use tracing::debug;
21
22pub use self::StabilityLevel::*;
23use crate::ty::TyCtxt;
24use crate::ty::print::with_no_trimmed_paths;
25
26#[derive(PartialEq, Clone, Copy, Debug)]
27pub enum StabilityLevel {
28    Unstable,
29    Stable,
30}
31
32#[derive(Copy, Clone)]
33pub enum UnstableKind {
34    /// Enforcing regular stability of an item
35    Regular,
36    /// Enforcing const stability of an item
37    Const(Span),
38}
39
40/// An entry in the `depr_map`.
41#[derive(Copy, Clone, HashStable, Debug, Encodable, Decodable)]
42pub struct DeprecationEntry {
43    /// The metadata of the attribute associated with this entry.
44    pub attr: Deprecation,
45    /// The `DefId` where the attr was originally attached. `None` for non-local
46    /// `DefId`'s.
47    origin: Option<LocalDefId>,
48}
49
50impl DeprecationEntry {
51    pub fn local(attr: Deprecation, def_id: LocalDefId) -> DeprecationEntry {
52        DeprecationEntry { attr, origin: Some(def_id) }
53    }
54
55    pub fn external(attr: Deprecation) -> DeprecationEntry {
56        DeprecationEntry { attr, origin: None }
57    }
58
59    pub fn same_origin(&self, other: &DeprecationEntry) -> bool {
60        match (self.origin, other.origin) {
61            (Some(o1), Some(o2)) => o1 == o2,
62            _ => false,
63        }
64    }
65}
66
67pub fn report_unstable(
68    sess: &Session,
69    feature: Symbol,
70    reason: Option<Symbol>,
71    issue: Option<NonZero<u32>>,
72    suggestion: Option<(Span, String, String, Applicability)>,
73    is_soft: bool,
74    span: Span,
75    soft_handler: impl FnOnce(&'static Lint, Span, String),
76    kind: UnstableKind,
77) {
78    let qual = match kind {
79        UnstableKind::Regular => "",
80        UnstableKind::Const(_) => " const",
81    };
82
83    let msg = match reason {
84        Some(r) => format!("use of unstable{qual} library feature `{feature}`: {r}"),
85        None => format!("use of unstable{qual} library feature `{feature}`"),
86    };
87
88    if is_soft {
89        soft_handler(SOFT_UNSTABLE, span, msg)
90    } else {
91        let mut err = feature_err_issue(sess, feature, span, GateIssue::Library(issue), msg);
92        if let Some((inner_types, msg, sugg, applicability)) = suggestion {
93            err.span_suggestion(inner_types, msg, sugg, applicability);
94        }
95        if let UnstableKind::Const(kw) = kind {
96            err.span_label(kw, "trait is not stable as const yet");
97        }
98        err.emit();
99    }
100}
101
102fn deprecation_lint(is_in_effect: bool) -> &'static Lint {
103    if is_in_effect { DEPRECATED } else { DEPRECATED_IN_FUTURE }
104}
105
106#[derive(Subdiagnostic)]
107#[suggestion(
108    middle_deprecated_suggestion,
109    code = "{suggestion}",
110    style = "verbose",
111    applicability = "machine-applicable"
112)]
113pub struct DeprecationSuggestion {
114    #[primary_span]
115    pub span: Span,
116
117    pub kind: String,
118    pub suggestion: Symbol,
119}
120
121pub struct Deprecated {
122    pub sub: Option<DeprecationSuggestion>,
123
124    // FIXME: make this translatable
125    pub kind: String,
126    pub path: String,
127    pub note: Option<Symbol>,
128    pub since_kind: DeprecatedSinceKind,
129}
130
131impl<'a, G: EmissionGuarantee> rustc_errors::LintDiagnostic<'a, G> for Deprecated {
132    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) {
133        diag.primary_message(match &self.since_kind {
134            DeprecatedSinceKind::InEffect => crate::fluent_generated::middle_deprecated,
135            DeprecatedSinceKind::InFuture => crate::fluent_generated::middle_deprecated_in_future,
136            DeprecatedSinceKind::InVersion(_) => {
137                crate::fluent_generated::middle_deprecated_in_version
138            }
139        });
140        diag.arg("kind", self.kind);
141        diag.arg("path", self.path);
142        if let DeprecatedSinceKind::InVersion(version) = self.since_kind {
143            diag.arg("version", version);
144        }
145        if let Some(note) = self.note {
146            diag.arg("has_note", true);
147            diag.arg("note", note);
148        } else {
149            diag.arg("has_note", false);
150        }
151        if let Some(sub) = self.sub {
152            diag.subdiagnostic(sub);
153        }
154    }
155}
156
157fn deprecated_since_kind(is_in_effect: bool, since: DeprecatedSince) -> DeprecatedSinceKind {
158    if is_in_effect {
159        DeprecatedSinceKind::InEffect
160    } else {
161        match since {
162            DeprecatedSince::RustcVersion(version) => {
163                DeprecatedSinceKind::InVersion(version.to_string())
164            }
165            DeprecatedSince::Future => DeprecatedSinceKind::InFuture,
166            DeprecatedSince::NonStandard(_)
167            | DeprecatedSince::Unspecified
168            | DeprecatedSince::Err => {
169                unreachable!("this deprecation is always in effect; {since:?}")
170            }
171        }
172    }
173}
174
175pub fn early_report_macro_deprecation(
176    lint_buffer: &mut LintBuffer,
177    depr: &Deprecation,
178    span: Span,
179    node_id: NodeId,
180    path: String,
181) {
182    if span.in_derive_expansion() {
183        return;
184    }
185
186    let is_in_effect = depr.is_in_effect();
187    let diag = BuiltinLintDiag::DeprecatedMacro {
188        suggestion: depr.suggestion,
189        suggestion_span: span,
190        note: depr.note,
191        path,
192        since_kind: deprecated_since_kind(is_in_effect, depr.since),
193    };
194    lint_buffer.buffer_lint(deprecation_lint(is_in_effect), node_id, span, diag);
195}
196
197fn late_report_deprecation(
198    tcx: TyCtxt<'_>,
199    depr: &Deprecation,
200    span: Span,
201    method_span: Option<Span>,
202    hir_id: HirId,
203    def_id: DefId,
204) {
205    if span.in_derive_expansion() {
206        return;
207    }
208
209    let is_in_effect = depr.is_in_effect();
210    let lint = deprecation_lint(is_in_effect);
211
212    // Calculating message for lint involves calling `self.def_path_str`,
213    // which will by default invoke the expensive `visible_parent_map` query.
214    // Skip all that work if the lint is allowed anyway.
215    if tcx.lint_level_at_node(lint, hir_id).level == Level::Allow {
216        return;
217    }
218
219    let def_path = with_no_trimmed_paths!(tcx.def_path_str(def_id));
220    let def_kind = tcx.def_descr(def_id);
221
222    let method_span = method_span.unwrap_or(span);
223    let suggestion =
224        if let hir::Node::Expr(_) = tcx.hir_node(hir_id) { depr.suggestion } else { None };
225    let diag = Deprecated {
226        sub: suggestion.map(|suggestion| DeprecationSuggestion {
227            span: method_span,
228            kind: def_kind.to_owned(),
229            suggestion,
230        }),
231        kind: def_kind.to_owned(),
232        path: def_path,
233        note: depr.note,
234        since_kind: deprecated_since_kind(is_in_effect, depr.since),
235    };
236    tcx.emit_node_span_lint(lint, hir_id, method_span, diag);
237}
238
239/// Result of `TyCtxt::eval_stability`.
240pub enum EvalResult {
241    /// We can use the item because it is stable or we provided the
242    /// corresponding feature gate.
243    Allow,
244    /// We cannot use the item because it is unstable and we did not provide the
245    /// corresponding feature gate.
246    Deny {
247        feature: Symbol,
248        reason: Option<Symbol>,
249        issue: Option<NonZero<u32>>,
250        suggestion: Option<(Span, String, String, Applicability)>,
251        is_soft: bool,
252    },
253    /// The item does not have the `#[stable]` or `#[unstable]` marker assigned.
254    Unmarked,
255}
256
257// See issue #83250.
258fn suggestion_for_allocator_api(
259    tcx: TyCtxt<'_>,
260    def_id: DefId,
261    span: Span,
262    feature: Symbol,
263) -> Option<(Span, String, String, Applicability)> {
264    if feature == sym::allocator_api {
265        if let Some(trait_) = tcx.opt_parent(def_id) {
266            if tcx.is_diagnostic_item(sym::Vec, trait_) {
267                let sm = tcx.sess.psess.source_map();
268                let inner_types = sm.span_extend_to_prev_char(span, '<', true);
269                if let Ok(snippet) = sm.span_to_snippet(inner_types) {
270                    return Some((
271                        inner_types,
272                        "consider wrapping the inner types in tuple".to_string(),
273                        format!("({snippet})"),
274                        Applicability::MaybeIncorrect,
275                    ));
276                }
277            }
278        }
279    }
280    None
281}
282
283/// An override option for eval_stability.
284pub enum AllowUnstable {
285    /// Don't emit an unstable error for the item
286    Yes,
287    /// Handle the item normally
288    No,
289}
290
291impl<'tcx> TyCtxt<'tcx> {
292    /// Evaluates the stability of an item.
293    ///
294    /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
295    /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
296    /// unstable feature otherwise.
297    ///
298    /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
299    /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
300    /// `id`.
301    pub fn eval_stability(
302        self,
303        def_id: DefId,
304        id: Option<HirId>,
305        span: Span,
306        method_span: Option<Span>,
307    ) -> EvalResult {
308        self.eval_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No)
309    }
310
311    /// Evaluates the stability of an item.
312    ///
313    /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
314    /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
315    /// unstable feature otherwise.
316    ///
317    /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
318    /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
319    /// `id`.
320    ///
321    /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
322    pub fn eval_stability_allow_unstable(
323        self,
324        def_id: DefId,
325        id: Option<HirId>,
326        span: Span,
327        method_span: Option<Span>,
328        allow_unstable: AllowUnstable,
329    ) -> EvalResult {
330        // Deprecated attributes apply in-crate and cross-crate.
331        if let Some(id) = id {
332            if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) {
333                let parent_def_id = self.hir_get_parent_item(id);
334                let skip = self
335                    .lookup_deprecation_entry(parent_def_id.to_def_id())
336                    .is_some_and(|parent_depr| parent_depr.same_origin(&depr_entry));
337
338                // #[deprecated] doesn't emit a notice if we're not on the
339                // topmost deprecation. For example, if a struct is deprecated,
340                // the use of a field won't be linted.
341                //
342                // With #![staged_api], we want to emit down the whole
343                // hierarchy.
344                let depr_attr = &depr_entry.attr;
345                if !skip || depr_attr.is_since_rustc_version() {
346                    late_report_deprecation(self, depr_attr, span, method_span, id, def_id);
347                }
348            };
349        }
350
351        let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some();
352        if !is_staged_api {
353            return EvalResult::Allow;
354        }
355
356        // Only the cross-crate scenario matters when checking unstable APIs
357        let cross_crate = !def_id.is_local();
358        if !cross_crate {
359            return EvalResult::Allow;
360        }
361
362        let stability = self.lookup_stability(def_id);
363        debug!(
364            "stability: \
365                inspecting def_id={:?} span={:?} of stability={:?}",
366            def_id, span, stability
367        );
368
369        match stability {
370            Some(Stability {
371                level: attrs::StabilityLevel::Unstable { reason, issue, is_soft, implied_by, .. },
372                feature,
373                ..
374            }) => {
375                if span.allows_unstable(feature) {
376                    debug!("stability: skipping span={:?} since it is internal", span);
377                    return EvalResult::Allow;
378                }
379                if self.features().enabled(feature) {
380                    return EvalResult::Allow;
381                }
382
383                // If this item was previously part of a now-stabilized feature which is still
384                // enabled (i.e. the user hasn't removed the attribute for the stabilized feature
385                // yet) then allow use of this item.
386                if let Some(implied_by) = implied_by
387                    && self.features().enabled(implied_by)
388                {
389                    return EvalResult::Allow;
390                }
391
392                // When we're compiling the compiler itself we may pull in
393                // crates from crates.io, but those crates may depend on other
394                // crates also pulled in from crates.io. We want to ideally be
395                // able to compile everything without requiring upstream
396                // modifications, so in the case that this looks like a
397                // `rustc_private` crate (e.g., a compiler crate) and we also have
398                // the `-Z force-unstable-if-unmarked` flag present (we're
399                // compiling a compiler crate), then let this missing feature
400                // annotation slide.
401                if feature == sym::rustc_private
402                    && issue == NonZero::new(27812)
403                    && self.sess.opts.unstable_opts.force_unstable_if_unmarked
404                {
405                    return EvalResult::Allow;
406                }
407
408                if matches!(allow_unstable, AllowUnstable::Yes) {
409                    return EvalResult::Allow;
410                }
411
412                let suggestion = suggestion_for_allocator_api(self, def_id, span, feature);
413                EvalResult::Deny {
414                    feature,
415                    reason: reason.to_opt_reason(),
416                    issue,
417                    suggestion,
418                    is_soft,
419                }
420            }
421            Some(_) => {
422                // Stable APIs are always ok to call and deprecated APIs are
423                // handled by the lint emitting logic above.
424                EvalResult::Allow
425            }
426            None => EvalResult::Unmarked,
427        }
428    }
429
430    /// Evaluates the default-impl stability of an item.
431    ///
432    /// Returns `EvalResult::Allow` if the item's default implementation is stable, or unstable but the corresponding
433    /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
434    /// unstable feature otherwise.
435    pub fn eval_default_body_stability(self, def_id: DefId, span: Span) -> EvalResult {
436        let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some();
437        if !is_staged_api {
438            return EvalResult::Allow;
439        }
440
441        // Only the cross-crate scenario matters when checking unstable APIs
442        let cross_crate = !def_id.is_local();
443        if !cross_crate {
444            return EvalResult::Allow;
445        }
446
447        let stability = self.lookup_default_body_stability(def_id);
448        debug!(
449            "body stability: inspecting def_id={def_id:?} span={span:?} of stability={stability:?}"
450        );
451
452        match stability {
453            Some(DefaultBodyStability {
454                level: attrs::StabilityLevel::Unstable { reason, issue, is_soft, .. },
455                feature,
456            }) => {
457                if span.allows_unstable(feature) {
458                    debug!("body stability: skipping span={:?} since it is internal", span);
459                    return EvalResult::Allow;
460                }
461                if self.features().enabled(feature) {
462                    return EvalResult::Allow;
463                }
464
465                EvalResult::Deny {
466                    feature,
467                    reason: reason.to_opt_reason(),
468                    issue,
469                    suggestion: None,
470                    is_soft,
471                }
472            }
473            Some(_) => {
474                // Stable APIs are always ok to call
475                EvalResult::Allow
476            }
477            None => EvalResult::Unmarked,
478        }
479    }
480
481    /// Checks if an item is stable or error out.
482    ///
483    /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
484    /// exist, emits an error.
485    ///
486    /// This function will also check if the item is deprecated.
487    /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted.
488    ///
489    /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature.
490    pub fn check_stability(
491        self,
492        def_id: DefId,
493        id: Option<HirId>,
494        span: Span,
495        method_span: Option<Span>,
496    ) -> bool {
497        self.check_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No)
498    }
499
500    /// Checks if an item is stable or error out.
501    ///
502    /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
503    /// exist, emits an error.
504    ///
505    /// This function will also check if the item is deprecated.
506    /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted.
507    ///
508    /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
509    ///
510    /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature.
511    pub fn check_stability_allow_unstable(
512        self,
513        def_id: DefId,
514        id: Option<HirId>,
515        span: Span,
516        method_span: Option<Span>,
517        allow_unstable: AllowUnstable,
518    ) -> bool {
519        self.check_optional_stability(
520            def_id,
521            id,
522            span,
523            method_span,
524            allow_unstable,
525            |span, def_id| {
526                // The API could be uncallable for other reasons, for example when a private module
527                // was referenced.
528                self.dcx().span_delayed_bug(span, format!("encountered unmarked API: {def_id:?}"));
529            },
530        )
531    }
532
533    /// Like `check_stability`, except that we permit items to have custom behaviour for
534    /// missing stability attributes (not necessarily just emit a `bug!`). This is necessary
535    /// for default generic parameters, which only have stability attributes if they were
536    /// added after the type on which they're defined.
537    ///
538    /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature.
539    pub fn check_optional_stability(
540        self,
541        def_id: DefId,
542        id: Option<HirId>,
543        span: Span,
544        method_span: Option<Span>,
545        allow_unstable: AllowUnstable,
546        unmarked: impl FnOnce(Span, DefId),
547    ) -> bool {
548        let soft_handler = |lint, span, msg: String| {
549            self.node_span_lint(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
550                lint.primary_message(msg);
551            })
552        };
553        let eval_result =
554            self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable);
555        let is_allowed = matches!(eval_result, EvalResult::Allow);
556        match eval_result {
557            EvalResult::Allow => {}
558            EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => report_unstable(
559                self.sess,
560                feature,
561                reason,
562                issue,
563                suggestion,
564                is_soft,
565                span,
566                soft_handler,
567                UnstableKind::Regular,
568            ),
569            EvalResult::Unmarked => unmarked(span, def_id),
570        }
571
572        is_allowed
573    }
574
575    /// This function is analogous to `check_optional_stability` but with the logic in
576    /// `eval_stability_allow_unstable` inlined, and which operating on const stability
577    /// instead of regular stability.
578    ///
579    /// This enforces *syntactical* const stability of const traits. In other words,
580    /// it enforces the ability to name `[const]`/`const` traits in trait bounds in various
581    /// syntax positions in HIR (including in the trait of an impl header).
582    pub fn check_const_stability(self, def_id: DefId, span: Span, const_kw_span: Span) {
583        let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some();
584        if !is_staged_api {
585            return;
586        }
587
588        // Only the cross-crate scenario matters when checking unstable APIs
589        let cross_crate = !def_id.is_local();
590        if !cross_crate {
591            return;
592        }
593
594        let stability = self.lookup_const_stability(def_id);
595        debug!(
596            "stability: \
597                inspecting def_id={:?} span={:?} of stability={:?}",
598            def_id, span, stability
599        );
600
601        match stability {
602            Some(ConstStability {
603                level: attrs::StabilityLevel::Unstable { reason, issue, is_soft, implied_by, .. },
604                feature,
605                ..
606            }) => {
607                assert!(!is_soft);
608
609                if span.allows_unstable(feature) {
610                    debug!("body stability: skipping span={:?} since it is internal", span);
611                    return;
612                }
613                if self.features().enabled(feature) {
614                    return;
615                }
616
617                // If this item was previously part of a now-stabilized feature which is still
618                // enabled (i.e. the user hasn't removed the attribute for the stabilized feature
619                // yet) then allow use of this item.
620                if let Some(implied_by) = implied_by
621                    && self.features().enabled(implied_by)
622                {
623                    return;
624                }
625
626                report_unstable(
627                    self.sess,
628                    feature,
629                    reason.to_opt_reason(),
630                    issue,
631                    None,
632                    false,
633                    span,
634                    |_, _, _| {},
635                    UnstableKind::Const(const_kw_span),
636                );
637            }
638            Some(_) | None => {}
639        }
640    }
641
642    pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> {
643        self.lookup_deprecation_entry(id).map(|depr| depr.attr)
644    }
645}