]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_middle/src/middle/stability.rs
Rollup merge of #88202 - azdavis:master, r=jyn514
[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, 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, 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
24 use std::num::NonZeroU32;
25
26 #[derive(PartialEq, Clone, Copy, Debug)]
27 pub enum StabilityLevel {
28     Unstable,
29     Stable,
30 }
31
32 /// An entry in the `depr_map`.
33 #[derive(Clone, HashStable, Debug)]
34 pub struct DeprecationEntry {
35     /// The metadata of the attribute associated with this entry.
36     pub attr: Deprecation,
37     /// The `DefId` where the attr was originally attached. `None` for non-local
38     /// `DefId`'s.
39     origin: Option<LocalDefId>,
40 }
41
42 impl DeprecationEntry {
43     pub fn local(attr: Deprecation, def_id: LocalDefId) -> DeprecationEntry {
44         DeprecationEntry { attr, origin: Some(def_id) }
45     }
46
47     pub fn external(attr: Deprecation) -> DeprecationEntry {
48         DeprecationEntry { attr, origin: None }
49     }
50
51     pub fn same_origin(&self, other: &DeprecationEntry) -> bool {
52         match (self.origin, other.origin) {
53             (Some(o1), Some(o2)) => o1 == o2,
54             _ => false,
55         }
56     }
57 }
58
59 /// A stability index, giving the stability level for items and methods.
60 #[derive(HashStable, Debug)]
61 pub struct Index<'tcx> {
62     /// This is mostly a cache, except the stabilities of local items
63     /// are filled by the annotator.
64     pub stab_map: FxHashMap<LocalDefId, &'tcx Stability>,
65     pub const_stab_map: FxHashMap<LocalDefId, &'tcx ConstStability>,
66     pub depr_map: FxHashMap<LocalDefId, DeprecationEntry>,
67
68     /// Maps for each crate whether it is part of the staged API.
69     pub staged_api: FxHashMap<CrateNum, bool>,
70
71     /// Features enabled for this crate.
72     pub active_features: FxHashSet<Symbol>,
73 }
74
75 impl<'tcx> Index<'tcx> {
76     pub fn local_stability(&self, def_id: LocalDefId) -> Option<&'tcx Stability> {
77         self.stab_map.get(&def_id).copied()
78     }
79
80     pub fn local_const_stability(&self, def_id: LocalDefId) -> Option<&'tcx ConstStability> {
81         self.const_stab_map.get(&def_id).copied()
82     }
83
84     pub fn local_deprecation_entry(&self, def_id: LocalDefId) -> Option<DeprecationEntry> {
85         self.depr_map.get(&def_id).cloned()
86     }
87 }
88
89 pub fn report_unstable(
90     sess: &Session,
91     feature: Symbol,
92     reason: Option<Symbol>,
93     issue: Option<NonZeroU32>,
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             feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg)
121                 .emit();
122         }
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(is_since_rustc_version: bool, since: Option<&str>) -> bool {
129     fn parse_version(ver: &str) -> Vec<u32> {
130         // We ignore non-integer components of the version (e.g., "nightly").
131         ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect()
132     }
133
134     if !is_since_rustc_version {
135         // The `since` field doesn't have semantic purpose in the stable `deprecated`
136         // attribute, only in `rustc_deprecated`.
137         return true;
138     }
139
140     if let Some(since) = since {
141         if since == "TBD" {
142             return false;
143         }
144
145         if let Some(rustc) = option_env!("CFG_RELEASE") {
146             let since: Vec<u32> = parse_version(&since);
147             let rustc: Vec<u32> = parse_version(rustc);
148             // We simply treat invalid `since` attributes as relating to a previous
149             // Rust version, thus always displaying the warning.
150             if since.len() != 3 {
151                 return true;
152             }
153             return since <= rustc;
154         }
155     };
156
157     // Assume deprecation is in effect if "since" field is missing
158     // or if we can't determine the current Rust version.
159     true
160 }
161
162 pub fn deprecation_suggestion(
163     diag: &mut DiagnosticBuilder<'_>,
164     kind: &str,
165     suggestion: Option<Symbol>,
166     span: Span,
167 ) {
168     if let Some(suggestion) = suggestion {
169         diag.span_suggestion(
170             span,
171             &format!("replace the use of the deprecated {}", kind),
172             suggestion.to_string(),
173             Applicability::MachineApplicable,
174         );
175     }
176 }
177
178 pub fn deprecation_message(depr: &Deprecation, kind: &str, path: &str) -> (String, &'static Lint) {
179     let since = depr.since.map(Symbol::as_str);
180     let (message, lint) = if deprecation_in_effect(depr.is_since_rustc_version, since.as_deref()) {
181         (format!("use of deprecated {} `{}`", kind, path), DEPRECATED)
182     } else {
183         (
184             if since.as_deref() == Some("TBD") {
185                 format!(
186                     "use of {} `{}` that will be deprecated in a future Rust version",
187                     kind, path
188                 )
189             } else {
190                 format!(
191                     "use of {} `{}` that will be deprecated in future version {}",
192                     kind,
193                     path,
194                     since.unwrap()
195                 )
196             },
197             DEPRECATED_IN_FUTURE,
198         )
199     };
200     let message = match depr.note {
201         Some(reason) => format!("{}: {}", message, reason),
202         None => message,
203     };
204     (message, lint)
205 }
206
207 pub fn early_report_deprecation(
208     lint_buffer: &'a mut LintBuffer,
209     message: &str,
210     suggestion: Option<Symbol>,
211     lint: &'static Lint,
212     span: Span,
213     node_id: NodeId,
214 ) {
215     if span.in_derive_expansion() {
216         return;
217     }
218
219     let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span);
220     lint_buffer.buffer_lint_with_diagnostic(lint, node_id, span, message, diag);
221 }
222
223 fn late_report_deprecation(
224     tcx: TyCtxt<'_>,
225     message: &str,
226     suggestion: Option<Symbol>,
227     lint: &'static Lint,
228     span: Span,
229     method_span: Option<Span>,
230     hir_id: HirId,
231     def_id: DefId,
232 ) {
233     if span.in_derive_expansion() {
234         return;
235     }
236     let method_span = method_span.unwrap_or(span);
237     tcx.struct_span_lint_hir(lint, hir_id, method_span, |lint| {
238         let mut diag = lint.build(message);
239         if let hir::Node::Expr(_) = tcx.hir().get(hir_id) {
240             let kind = tcx.def_kind(def_id).descr(def_id);
241             deprecation_suggestion(&mut diag, kind, suggestion, method_span);
242         }
243         diag.emit()
244     });
245 }
246
247 /// Result of `TyCtxt::eval_stability`.
248 pub enum EvalResult {
249     /// We can use the item because it is stable or we provided the
250     /// corresponding feature gate.
251     Allow,
252     /// We cannot use the item because it is unstable and we did not provide the
253     /// corresponding feature gate.
254     Deny { feature: Symbol, reason: Option<Symbol>, issue: Option<NonZeroU32>, is_soft: bool },
255     /// The item does not have the `#[stable]` or `#[unstable]` marker assigned.
256     Unmarked,
257 }
258
259 // See issue #38412.
260 fn skip_stability_check_due_to_privacy(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
261     if tcx.def_kind(def_id) == DefKind::TyParam {
262         // Have no visibility, considered public for the purpose of this check.
263         return false;
264     }
265     match tcx.visibility(def_id) {
266         // Must check stability for `pub` items.
267         ty::Visibility::Public => false,
268
269         // These are not visible outside crate; therefore
270         // stability markers are irrelevant, if even present.
271         ty::Visibility::Restricted(..) | ty::Visibility::Invisible => true,
272     }
273 }
274
275 impl<'tcx> TyCtxt<'tcx> {
276     /// Evaluates the stability of an item.
277     ///
278     /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
279     /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
280     /// unstable feature otherwise.
281     ///
282     /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
283     /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
284     /// `id`.
285     pub fn eval_stability(
286         self,
287         def_id: DefId,
288         id: Option<HirId>,
289         span: Span,
290         method_span: Option<Span>,
291     ) -> EvalResult {
292         // Deprecated attributes apply in-crate and cross-crate.
293         if let Some(id) = id {
294             if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) {
295                 let parent_def_id = self.hir().local_def_id(self.hir().get_parent_item(id));
296                 let skip = self
297                     .lookup_deprecation_entry(parent_def_id.to_def_id())
298                     .map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry));
299
300                 // #[deprecated] doesn't emit a notice if we're not on the
301                 // topmost deprecation. For example, if a struct is deprecated,
302                 // the use of a field won't be linted.
303                 //
304                 // #[rustc_deprecated] however wants to emit down the whole
305                 // hierarchy.
306                 if !skip || depr_entry.attr.is_since_rustc_version {
307                     let path = &with_no_trimmed_paths(|| self.def_path_str(def_id));
308                     let kind = self.def_kind(def_id).descr(def_id);
309                     let (message, lint) = deprecation_message(&depr_entry.attr, kind, path);
310                     late_report_deprecation(
311                         self,
312                         &message,
313                         depr_entry.attr.suggestion,
314                         lint,
315                         span,
316                         method_span,
317                         id,
318                         def_id,
319                     );
320                 }
321             };
322         }
323
324         let is_staged_api =
325             self.lookup_stability(DefId { index: CRATE_DEF_INDEX, ..def_id }).is_some();
326         if !is_staged_api {
327             return EvalResult::Allow;
328         }
329
330         let stability = self.lookup_stability(def_id);
331         debug!(
332             "stability: \
333                 inspecting def_id={:?} span={:?} of stability={:?}",
334             def_id, span, stability
335         );
336
337         // Only the cross-crate scenario matters when checking unstable APIs
338         let cross_crate = !def_id.is_local();
339         if !cross_crate {
340             return EvalResult::Allow;
341         }
342
343         // Issue #38412: private items lack stability markers.
344         if skip_stability_check_due_to_privacy(self, def_id) {
345             return EvalResult::Allow;
346         }
347
348         match stability {
349             Some(&Stability {
350                 level: attr::Unstable { reason, issue, is_soft }, feature, ..
351             }) => {
352                 if span.allows_unstable(feature) {
353                     debug!("stability: skipping span={:?} since it is internal", span);
354                     return EvalResult::Allow;
355                 }
356                 if self.stability().active_features.contains(&feature) {
357                     return EvalResult::Allow;
358                 }
359
360                 // When we're compiling the compiler itself we may pull in
361                 // crates from crates.io, but those crates may depend on other
362                 // crates also pulled in from crates.io. We want to ideally be
363                 // able to compile everything without requiring upstream
364                 // modifications, so in the case that this looks like a
365                 // `rustc_private` crate (e.g., a compiler crate) and we also have
366                 // the `-Z force-unstable-if-unmarked` flag present (we're
367                 // compiling a compiler crate), then let this missing feature
368                 // annotation slide.
369                 if feature == sym::rustc_private && issue == NonZeroU32::new(27812) {
370                     if self.sess.opts.debugging_opts.force_unstable_if_unmarked {
371                         return EvalResult::Allow;
372                     }
373                 }
374
375                 EvalResult::Deny { feature, reason, issue, is_soft }
376             }
377             Some(_) => {
378                 // Stable APIs are always ok to call and deprecated APIs are
379                 // handled by the lint emitting logic above.
380                 EvalResult::Allow
381             }
382             None => EvalResult::Unmarked,
383         }
384     }
385
386     /// Checks if an item is stable or error out.
387     ///
388     /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
389     /// exist, emits an error.
390     ///
391     /// This function will also check if the item is deprecated.
392     /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted.
393     pub fn check_stability(
394         self,
395         def_id: DefId,
396         id: Option<HirId>,
397         span: Span,
398         method_span: Option<Span>,
399     ) {
400         self.check_optional_stability(def_id, id, span, method_span, |span, def_id| {
401             // The API could be uncallable for other reasons, for example when a private module
402             // was referenced.
403             self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id));
404         })
405     }
406
407     /// Like `check_stability`, except that we permit items to have custom behaviour for
408     /// missing stability attributes (not necessarily just emit a `bug!`). This is necessary
409     /// for default generic parameters, which only have stability attributes if they were
410     /// added after the type on which they're defined.
411     pub fn check_optional_stability(
412         self,
413         def_id: DefId,
414         id: Option<HirId>,
415         span: Span,
416         method_span: Option<Span>,
417         unmarked: impl FnOnce(Span, DefId),
418     ) {
419         let soft_handler = |lint, span, msg: &_| {
420             self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
421                 lint.build(msg).emit()
422             })
423         };
424         match self.eval_stability(def_id, id, span, method_span) {
425             EvalResult::Allow => {}
426             EvalResult::Deny { feature, reason, issue, is_soft } => {
427                 report_unstable(self.sess, feature, reason, issue, is_soft, span, soft_handler)
428             }
429             EvalResult::Unmarked => unmarked(span, def_id),
430         }
431     }
432
433     pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> {
434         self.lookup_deprecation_entry(id).map(|depr| depr.attr)
435     }
436 }