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