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