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