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