]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_middle/src/middle/stability.rs
Rollup merge of #98533 - jyn514:drop-tracking-debugging, r=eholk
[rust.git] / compiler / rustc_middle / src / 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
4 pub use self::StabilityLevel::*;
5
6 use crate::ty::{self, DefIdTree, TyCtxt};
7 use rustc_ast::NodeId;
8 use rustc_attr::{self as attr, ConstStability, 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};
21 use rustc_span::Span;
22 use std::num::NonZeroU32;
23
24 #[derive(PartialEq, Clone, Copy, Debug)]
25 pub enum StabilityLevel {
26     Unstable,
27     Stable,
28 }
29
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
36     /// `DefId`'s.
37     origin: Option<LocalDefId>,
38 }
39
40 impl DeprecationEntry {
41     pub fn local(attr: Deprecation, def_id: LocalDefId) -> DeprecationEntry {
42         DeprecationEntry { attr, origin: Some(def_id) }
43     }
44
45     pub fn external(attr: Deprecation) -> DeprecationEntry {
46         DeprecationEntry { attr, origin: None }
47     }
48
49     pub fn same_origin(&self, other: &DeprecationEntry) -> bool {
50         match (self.origin, other.origin) {
51             (Some(o1), Some(o2)) => o1 == o2,
52             _ => false,
53         }
54     }
55 }
56
57 /// A stability index, giving the stability level for items and methods.
58 #[derive(HashStable, Debug)]
59 pub struct Index {
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 depr_map: FxHashMap<LocalDefId, DeprecationEntry>,
65 }
66
67 impl Index {
68     pub fn local_stability(&self, def_id: LocalDefId) -> Option<Stability> {
69         self.stab_map.get(&def_id).copied()
70     }
71
72     pub fn local_const_stability(&self, def_id: LocalDefId) -> Option<ConstStability> {
73         self.const_stab_map.get(&def_id).copied()
74     }
75
76     pub fn local_deprecation_entry(&self, def_id: LocalDefId) -> Option<DeprecationEntry> {
77         self.depr_map.get(&def_id).cloned()
78     }
79 }
80
81 pub fn report_unstable(
82     sess: &Session,
83     feature: Symbol,
84     reason: Option<Symbol>,
85     issue: Option<NonZeroU32>,
86     suggestion: Option<(Span, String, String, Applicability)>,
87     is_soft: bool,
88     span: Span,
89     soft_handler: impl FnOnce(&'static Lint, Span, &str),
90 ) {
91     let msg = match reason {
92         Some(r) => format!("use of unstable library feature '{}': {}", feature, r),
93         None => format!("use of unstable library feature '{}'", &feature),
94     };
95
96     if is_soft {
97         soft_handler(SOFT_UNSTABLE, span, &msg)
98     } else {
99         let mut err =
100             feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg);
101         if let Some((inner_types, ref msg, sugg, applicability)) = suggestion {
102             err.span_suggestion(inner_types, msg, sugg, applicability);
103         }
104         err.emit();
105     }
106 }
107
108 /// Checks whether an item marked with `deprecated(since="X")` is currently
109 /// deprecated (i.e., whether X is not greater than the current rustc version).
110 pub fn deprecation_in_effect(depr: &Deprecation) -> bool {
111     let is_since_rustc_version = depr.is_since_rustc_version;
112     let since = depr.since.as_ref().map(Symbol::as_str);
113
114     fn parse_version(ver: &str) -> Vec<u32> {
115         // We ignore non-integer components of the version (e.g., "nightly").
116         ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect()
117     }
118
119     if !is_since_rustc_version {
120         // The `since` field doesn't have semantic purpose without `#![staged_api]`.
121         return true;
122     }
123
124     if let Some(since) = since {
125         if since == "TBD" {
126             return false;
127         }
128
129         if let Some(rustc) = option_env!("CFG_RELEASE") {
130             let since: Vec<u32> = parse_version(&since);
131             let rustc: Vec<u32> = parse_version(rustc);
132             // We simply treat invalid `since` attributes as relating to a previous
133             // Rust version, thus always displaying the warning.
134             if since.len() != 3 {
135                 return true;
136             }
137             return since <= rustc;
138         }
139     };
140
141     // Assume deprecation is in effect if "since" field is missing
142     // or if we can't determine the current Rust version.
143     true
144 }
145
146 pub fn deprecation_suggestion(
147     diag: &mut Diagnostic,
148     kind: &str,
149     suggestion: Option<Symbol>,
150     span: Span,
151 ) {
152     if let Some(suggestion) = suggestion {
153         diag.span_suggestion_verbose(
154             span,
155             &format!("replace the use of the deprecated {}", kind),
156             suggestion,
157             Applicability::MachineApplicable,
158         );
159     }
160 }
161
162 fn deprecation_lint(is_in_effect: bool) -> &'static Lint {
163     if is_in_effect { DEPRECATED } else { DEPRECATED_IN_FUTURE }
164 }
165
166 fn deprecation_message(
167     is_in_effect: bool,
168     since: Option<Symbol>,
169     note: Option<Symbol>,
170     kind: &str,
171     path: &str,
172 ) -> String {
173     let message = if is_in_effect {
174         format!("use of deprecated {} `{}`", kind, path)
175     } else {
176         let since = since.as_ref().map(Symbol::as_str);
177
178         if since == Some("TBD") {
179             format!("use of {} `{}` that will be deprecated in a future Rust version", kind, path)
180         } else {
181             format!(
182                 "use of {} `{}` that will be deprecated in future version {}",
183                 kind,
184                 path,
185                 since.unwrap()
186             )
187         }
188     };
189
190     match note {
191         Some(reason) => format!("{}: {}", message, reason),
192         None => message,
193     }
194 }
195
196 pub fn deprecation_message_and_lint(
197     depr: &Deprecation,
198     kind: &str,
199     path: &str,
200 ) -> (String, &'static Lint) {
201     let is_in_effect = deprecation_in_effect(depr);
202     (
203         deprecation_message(is_in_effect, depr.since, depr.note, kind, path),
204         deprecation_lint(is_in_effect),
205     )
206 }
207
208 pub fn early_report_deprecation<'a>(
209     lint_buffer: &'a mut LintBuffer,
210     message: &str,
211     suggestion: Option<Symbol>,
212     lint: &'static Lint,
213     span: Span,
214     node_id: NodeId,
215 ) {
216     if span.in_derive_expansion() {
217         return;
218     }
219
220     let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span);
221     lint_buffer.buffer_lint_with_diagnostic(lint, node_id, span, message, diag);
222 }
223
224 fn late_report_deprecation(
225     tcx: TyCtxt<'_>,
226     message: &str,
227     suggestion: Option<Symbol>,
228     lint: &'static Lint,
229     span: Span,
230     method_span: Option<Span>,
231     hir_id: HirId,
232     def_id: DefId,
233 ) {
234     if span.in_derive_expansion() {
235         return;
236     }
237     let method_span = method_span.unwrap_or(span);
238     tcx.struct_span_lint_hir(lint, hir_id, method_span, |lint| {
239         let mut diag = lint.build(message);
240         if let hir::Node::Expr(_) = tcx.hir().get(hir_id) {
241             let kind = tcx.def_kind(def_id).descr(def_id);
242             deprecation_suggestion(&mut diag, kind, suggestion, method_span);
243         }
244         diag.emit();
245     });
246 }
247
248 /// Result of `TyCtxt::eval_stability`.
249 pub enum EvalResult {
250     /// We can use the item because it is stable or we provided the
251     /// corresponding feature gate.
252     Allow,
253     /// We cannot use the item because it is unstable and we did not provide the
254     /// corresponding feature gate.
255     Deny {
256         feature: Symbol,
257         reason: Option<Symbol>,
258         issue: Option<NonZeroU32>,
259         suggestion: Option<(Span, String, String, Applicability)>,
260         is_soft: bool,
261     },
262     /// The item does not have the `#[stable]` or `#[unstable]` marker assigned.
263     Unmarked,
264 }
265
266 // See issue #38412.
267 fn skip_stability_check_due_to_privacy(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
268     if tcx.def_kind(def_id) == DefKind::TyParam {
269         // Have no visibility, considered public for the purpose of this check.
270         return false;
271     }
272     match tcx.visibility(def_id) {
273         // Must check stability for `pub` items.
274         ty::Visibility::Public => false,
275
276         // These are not visible outside crate; therefore
277         // stability markers are irrelevant, if even present.
278         ty::Visibility::Restricted(..) | ty::Visibility::Invisible => true,
279     }
280 }
281
282 // See issue #83250.
283 fn suggestion_for_allocator_api(
284     tcx: TyCtxt<'_>,
285     def_id: DefId,
286     span: Span,
287     feature: Symbol,
288 ) -> Option<(Span, String, String, Applicability)> {
289     if feature == sym::allocator_api {
290         if let Some(trait_) = tcx.opt_parent(def_id) {
291             if tcx.is_diagnostic_item(sym::Vec, trait_) {
292                 let sm = tcx.sess.parse_sess.source_map();
293                 let inner_types = sm.span_extend_to_prev_char(span, '<', true);
294                 if let Ok(snippet) = sm.span_to_snippet(inner_types) {
295                     return Some((
296                         inner_types,
297                         "consider wrapping the inner types in tuple".to_string(),
298                         format!("({})", snippet),
299                         Applicability::MaybeIncorrect,
300                     ));
301                 }
302             }
303         }
304     }
305     None
306 }
307
308 /// An override option for eval_stability.
309 pub enum AllowUnstable {
310     /// Don't emit an unstable error for the item
311     Yes,
312     /// Handle the item normally
313     No,
314 }
315
316 impl<'tcx> TyCtxt<'tcx> {
317     /// Evaluates the stability of an item.
318     ///
319     /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
320     /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
321     /// unstable feature otherwise.
322     ///
323     /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
324     /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
325     /// `id`.
326     pub fn eval_stability(
327         self,
328         def_id: DefId,
329         id: Option<HirId>,
330         span: Span,
331         method_span: Option<Span>,
332     ) -> EvalResult {
333         self.eval_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No)
334     }
335
336     /// Evaluates the stability of an item.
337     ///
338     /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
339     /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
340     /// unstable feature otherwise.
341     ///
342     /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
343     /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
344     /// `id`.
345     ///
346     /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
347     pub fn eval_stability_allow_unstable(
348         self,
349         def_id: DefId,
350         id: Option<HirId>,
351         span: Span,
352         method_span: Option<Span>,
353         allow_unstable: AllowUnstable,
354     ) -> EvalResult {
355         // Deprecated attributes apply in-crate and cross-crate.
356         if let Some(id) = id {
357             if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) {
358                 let parent_def_id = self.hir().get_parent_item(id);
359                 let skip = self
360                     .lookup_deprecation_entry(parent_def_id.to_def_id())
361                     .map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry));
362
363                 // #[deprecated] doesn't emit a notice if we're not on the
364                 // topmost deprecation. For example, if a struct is deprecated,
365                 // the use of a field won't be linted.
366                 //
367                 // With #![staged_api], we want to emit down the whole
368                 // hierarchy.
369                 let depr_attr = &depr_entry.attr;
370                 if !skip || depr_attr.is_since_rustc_version {
371                     // Calculating message for lint involves calling `self.def_path_str`.
372                     // Which by default to calculate visible path will invoke expensive `visible_parent_map` query.
373                     // So we skip message calculation altogether, if lint is allowed.
374                     let is_in_effect = deprecation_in_effect(depr_attr);
375                     let lint = deprecation_lint(is_in_effect);
376                     if self.lint_level_at_node(lint, id).0 != Level::Allow {
377                         let def_path = with_no_trimmed_paths!(self.def_path_str(def_id));
378                         let def_kind = self.def_kind(def_id).descr(def_id);
379
380                         late_report_deprecation(
381                             self,
382                             &deprecation_message(
383                                 is_in_effect,
384                                 depr_attr.since,
385                                 depr_attr.note,
386                                 def_kind,
387                                 &def_path,
388                             ),
389                             depr_attr.suggestion,
390                             lint,
391                             span,
392                             method_span,
393                             id,
394                             def_id,
395                         );
396                     }
397                 }
398             };
399         }
400
401         let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some();
402         if !is_staged_api {
403             return EvalResult::Allow;
404         }
405
406         let stability = self.lookup_stability(def_id);
407         debug!(
408             "stability: \
409                 inspecting def_id={:?} span={:?} of stability={:?}",
410             def_id, span, stability
411         );
412
413         // Only the cross-crate scenario matters when checking unstable APIs
414         let cross_crate = !def_id.is_local();
415         if !cross_crate {
416             return EvalResult::Allow;
417         }
418
419         // Issue #38412: private items lack stability markers.
420         if skip_stability_check_due_to_privacy(self, def_id) {
421             return EvalResult::Allow;
422         }
423
424         match stability {
425             Some(Stability {
426                 level: attr::Unstable { reason, issue, is_soft }, feature, ..
427             }) => {
428                 if span.allows_unstable(feature) {
429                     debug!("stability: skipping span={:?} since it is internal", span);
430                     return EvalResult::Allow;
431                 }
432                 if self.features().active(feature) {
433                     return EvalResult::Allow;
434                 }
435
436                 // When we're compiling the compiler itself we may pull in
437                 // crates from crates.io, but those crates may depend on other
438                 // crates also pulled in from crates.io. We want to ideally be
439                 // able to compile everything without requiring upstream
440                 // modifications, so in the case that this looks like a
441                 // `rustc_private` crate (e.g., a compiler crate) and we also have
442                 // the `-Z force-unstable-if-unmarked` flag present (we're
443                 // compiling a compiler crate), then let this missing feature
444                 // annotation slide.
445                 if feature == sym::rustc_private && issue == NonZeroU32::new(27812) {
446                     if self.sess.opts.debugging_opts.force_unstable_if_unmarked {
447                         return EvalResult::Allow;
448                     }
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 { feature, reason, issue, suggestion, is_soft }
457             }
458             Some(_) => {
459                 // Stable APIs are always ok to call and deprecated APIs are
460                 // handled by the lint emitting logic above.
461                 EvalResult::Allow
462             }
463             None => EvalResult::Unmarked,
464         }
465     }
466
467     /// Checks if an item is stable or error out.
468     ///
469     /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
470     /// exist, emits an error.
471     ///
472     /// This function will also check if the item is deprecated.
473     /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted.
474     pub fn check_stability(
475         self,
476         def_id: DefId,
477         id: Option<HirId>,
478         span: Span,
479         method_span: Option<Span>,
480     ) {
481         self.check_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No)
482     }
483
484     /// Checks if an item is stable or error out.
485     ///
486     /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
487     /// exist, emits an error.
488     ///
489     /// This function will also check if the item is deprecated.
490     /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted.
491     ///
492     /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
493     pub fn check_stability_allow_unstable(
494         self,
495         def_id: DefId,
496         id: Option<HirId>,
497         span: Span,
498         method_span: Option<Span>,
499         allow_unstable: AllowUnstable,
500     ) {
501         self.check_optional_stability(
502             def_id,
503             id,
504             span,
505             method_span,
506             allow_unstable,
507             |span, def_id| {
508                 // The API could be uncallable for other reasons, for example when a private module
509                 // was referenced.
510                 self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id));
511             },
512         )
513     }
514
515     /// Like `check_stability`, except that we permit items to have custom behaviour for
516     /// missing stability attributes (not necessarily just emit a `bug!`). This is necessary
517     /// for default generic parameters, which only have stability attributes if they were
518     /// added after the type on which they're defined.
519     pub fn check_optional_stability(
520         self,
521         def_id: DefId,
522         id: Option<HirId>,
523         span: Span,
524         method_span: Option<Span>,
525         allow_unstable: AllowUnstable,
526         unmarked: impl FnOnce(Span, DefId),
527     ) {
528         let soft_handler = |lint, span, msg: &_| {
529             self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
530                 lint.build(msg).emit();
531             })
532         };
533         match self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable) {
534             EvalResult::Allow => {}
535             EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => report_unstable(
536                 self.sess,
537                 feature,
538                 reason,
539                 issue,
540                 suggestion,
541                 is_soft,
542                 span,
543                 soft_handler,
544             ),
545             EvalResult::Unmarked => unmarked(span, def_id),
546         }
547     }
548
549     pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> {
550         self.lookup_deprecation_entry(id).map(|depr| depr.attr)
551     }
552 }