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