]> git.lizzy.rs Git - rust.git/blob - src/librustc/middle/stability.rs
Rollup merge of #70038 - DutchGhost:const-forget-tests, r=RalfJung
[rust.git] / src / librustc / 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::ast::CRATE_NODE_ID;
8 use rustc_attr::{self as attr, ConstStability, Deprecation, RustcDeprecation, 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, CRATE_DEF_INDEX};
15 use rustc_hir::{self, HirId};
16 use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
17 use rustc_session::lint::{BuiltinLintDiagnostics, Lint, LintBuffer};
18 use rustc_session::parse::feature_err_issue;
19 use rustc_session::{DiagnosticMessageId, Session};
20 use rustc_span::symbol::{sym, Symbol};
21 use rustc_span::{MultiSpan, Span};
22
23 use std::num::NonZeroU32;
24
25 #[derive(PartialEq, Clone, Copy, Debug)]
26 pub enum StabilityLevel {
27     Unstable,
28     Stable,
29 }
30
31 impl StabilityLevel {
32     pub fn from_attr_level(level: &attr::StabilityLevel) -> Self {
33         if level.is_stable() { Stable } else { Unstable }
34     }
35 }
36
37 /// An entry in the `depr_map`.
38 #[derive(Clone, HashStable)]
39 pub struct DeprecationEntry {
40     /// The metadata of the attribute associated with this entry.
41     pub attr: Deprecation,
42     /// The `DefId` where the attr was originally attached. `None` for non-local
43     /// `DefId`'s.
44     origin: Option<HirId>,
45 }
46
47 impl DeprecationEntry {
48     pub fn local(attr: Deprecation, id: HirId) -> DeprecationEntry {
49         DeprecationEntry { attr, origin: Some(id) }
50     }
51
52     pub fn external(attr: Deprecation) -> DeprecationEntry {
53         DeprecationEntry { attr, origin: None }
54     }
55
56     pub fn same_origin(&self, other: &DeprecationEntry) -> bool {
57         match (self.origin, other.origin) {
58             (Some(o1), Some(o2)) => o1 == o2,
59             _ => false,
60         }
61     }
62 }
63
64 /// A stability index, giving the stability level for items and methods.
65 #[derive(HashStable)]
66 pub struct Index<'tcx> {
67     /// This is mostly a cache, except the stabilities of local items
68     /// are filled by the annotator.
69     pub stab_map: FxHashMap<HirId, &'tcx Stability>,
70     pub const_stab_map: FxHashMap<HirId, &'tcx ConstStability>,
71     pub depr_map: FxHashMap<HirId, DeprecationEntry>,
72
73     /// Maps for each crate whether it is part of the staged API.
74     pub staged_api: FxHashMap<CrateNum, bool>,
75
76     /// Features enabled for this crate.
77     pub active_features: FxHashSet<Symbol>,
78 }
79
80 impl<'tcx> Index<'tcx> {
81     pub fn local_stability(&self, id: HirId) -> Option<&'tcx Stability> {
82         self.stab_map.get(&id).cloned()
83     }
84
85     pub fn local_const_stability(&self, id: HirId) -> Option<&'tcx ConstStability> {
86         self.const_stab_map.get(&id).cloned()
87     }
88
89     pub fn local_deprecation_entry(&self, id: HirId) -> Option<DeprecationEntry> {
90         self.depr_map.get(&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     is_soft: bool,
100     span: Span,
101     soft_handler: impl FnOnce(&'static Lint, Span, &str),
102 ) {
103     let msg = match reason {
104         Some(r) => format!("use of unstable library feature '{}': {}", feature, r),
105         None => format!("use of unstable library feature '{}'", &feature),
106     };
107
108     let msp: MultiSpan = span.into();
109     let sm = &sess.parse_sess.source_map();
110     let span_key = msp.primary_span().and_then(|sp: Span| {
111         if !sp.is_dummy() {
112             let file = sm.lookup_char_pos(sp.lo()).file;
113             if file.is_imported() { None } else { Some(span) }
114         } else {
115             None
116         }
117     });
118
119     let error_id = (DiagnosticMessageId::StabilityId(issue), span_key, msg.clone());
120     let fresh = sess.one_time_diagnostics.borrow_mut().insert(error_id);
121     if fresh {
122         if is_soft {
123             soft_handler(SOFT_UNSTABLE, span, &msg)
124         } else {
125             feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg)
126                 .emit();
127         }
128     }
129 }
130
131 /// Checks whether an item marked with `deprecated(since="X")` is currently
132 /// deprecated (i.e., whether X is not greater than the current rustc version).
133 pub fn deprecation_in_effect(since: &str) -> bool {
134     fn parse_version(ver: &str) -> Vec<u32> {
135         // We ignore non-integer components of the version (e.g., "nightly").
136         ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect()
137     }
138
139     if let Some(rustc) = option_env!("CFG_RELEASE") {
140         let since: Vec<u32> = parse_version(since);
141         let rustc: Vec<u32> = parse_version(rustc);
142         // We simply treat invalid `since` attributes as relating to a previous
143         // Rust version, thus always displaying the warning.
144         if since.len() != 3 {
145             return true;
146         }
147         since <= rustc
148     } else {
149         // By default, a deprecation warning applies to
150         // the current version of the compiler.
151         true
152     }
153 }
154
155 pub fn deprecation_suggestion(
156     diag: &mut DiagnosticBuilder<'_>,
157     suggestion: Option<Symbol>,
158     span: Span,
159 ) {
160     if let Some(suggestion) = suggestion {
161         diag.span_suggestion(
162             span,
163             "replace the use of the deprecated item",
164             suggestion.to_string(),
165             Applicability::MachineApplicable,
166         );
167     }
168 }
169
170 fn deprecation_message_common(message: String, reason: Option<Symbol>) -> String {
171     match reason {
172         Some(reason) => format!("{}: {}", message, reason),
173         None => message,
174     }
175 }
176
177 pub fn deprecation_message(depr: &Deprecation, path: &str) -> (String, &'static Lint) {
178     let message = format!("use of deprecated item '{}'", path);
179     (deprecation_message_common(message, depr.note), DEPRECATED)
180 }
181
182 pub fn rustc_deprecation_message(depr: &RustcDeprecation, path: &str) -> (String, &'static Lint) {
183     let (message, lint) = if deprecation_in_effect(&depr.since.as_str()) {
184         (format!("use of deprecated item '{}'", path), DEPRECATED)
185     } else {
186         (
187             format!(
188                 "use of item '{}' that will be deprecated in future version {}",
189                 path, depr.since
190             ),
191             DEPRECATED_IN_FUTURE,
192         )
193     };
194     (deprecation_message_common(message, Some(depr.reason)), lint)
195 }
196
197 pub fn early_report_deprecation(
198     lint_buffer: &'a mut LintBuffer,
199     message: &str,
200     suggestion: Option<Symbol>,
201     lint: &'static Lint,
202     span: Span,
203 ) {
204     if span.in_derive_expansion() {
205         return;
206     }
207
208     let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span);
209     lint_buffer.buffer_lint_with_diagnostic(lint, CRATE_NODE_ID, span, message, diag);
210 }
211
212 fn late_report_deprecation(
213     tcx: TyCtxt<'_>,
214     message: &str,
215     suggestion: Option<Symbol>,
216     lint: &'static Lint,
217     span: Span,
218     def_id: DefId,
219     hir_id: HirId,
220 ) {
221     if span.in_derive_expansion() {
222         return;
223     }
224
225     tcx.struct_span_lint_hir(lint, hir_id, span, |lint| {
226         let mut diag = lint.build(message);
227         if let hir::Node::Expr(_) = tcx.hir().get(hir_id) {
228             deprecation_suggestion(&mut diag, suggestion, span);
229         }
230         diag.emit()
231     });
232     if hir_id == hir::DUMMY_HIR_ID {
233         span_bug!(span, "emitted a {} lint with dummy HIR id: {:?}", lint.name, def_id);
234     }
235 }
236
237 /// Result of `TyCtxt::eval_stability`.
238 pub enum EvalResult {
239     /// We can use the item because it is stable or we provided the
240     /// corresponding feature gate.
241     Allow,
242     /// We cannot use the item because it is unstable and we did not provide the
243     /// corresponding feature gate.
244     Deny { feature: Symbol, reason: Option<Symbol>, issue: Option<NonZeroU32>, is_soft: bool },
245     /// The item does not have the `#[stable]` or `#[unstable]` marker assigned.
246     Unmarked,
247 }
248
249 // See issue #38412.
250 fn skip_stability_check_due_to_privacy(tcx: TyCtxt<'_>, mut def_id: DefId) -> bool {
251     // Check if `def_id` is a trait method.
252     match tcx.def_kind(def_id) {
253         Some(DefKind::AssocFn) | Some(DefKind::AssocTy) | Some(DefKind::AssocConst) => {
254             if let ty::TraitContainer(trait_def_id) = tcx.associated_item(def_id).container {
255                 // Trait methods do not declare visibility (even
256                 // for visibility info in cstore). Use containing
257                 // trait instead, so methods of `pub` traits are
258                 // themselves considered `pub`.
259                 def_id = trait_def_id;
260             }
261         }
262         _ => {}
263     }
264
265     let visibility = tcx.visibility(def_id);
266
267     match visibility {
268         // Must check stability for `pub` items.
269         ty::Visibility::Public => false,
270
271         // These are not visible outside crate; therefore
272         // stability markers are irrelevant, if even present.
273         ty::Visibility::Restricted(..) | ty::Visibility::Invisible => true,
274     }
275 }
276
277 impl<'tcx> TyCtxt<'tcx> {
278     /// Evaluates the stability of an item.
279     ///
280     /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
281     /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
282     /// unstable feature otherwise.
283     ///
284     /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
285     /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
286     /// `id`.
287     pub fn eval_stability(self, def_id: DefId, id: Option<HirId>, span: Span) -> EvalResult {
288         // Deprecated attributes apply in-crate and cross-crate.
289         if let Some(id) = id {
290             if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) {
291                 let parent_def_id = self.hir().local_def_id(self.hir().get_parent_item(id));
292                 let skip = self
293                     .lookup_deprecation_entry(parent_def_id)
294                     .map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry));
295
296                 if !skip {
297                     let (message, lint) =
298                         deprecation_message(&depr_entry.attr, &self.def_path_str(def_id));
299                     late_report_deprecation(self, &message, None, lint, span, def_id, id);
300                 }
301             };
302         }
303
304         let is_staged_api =
305             self.lookup_stability(DefId { index: CRATE_DEF_INDEX, ..def_id }).is_some();
306         if !is_staged_api {
307             return EvalResult::Allow;
308         }
309
310         let stability = self.lookup_stability(def_id);
311         debug!(
312             "stability: \
313                 inspecting def_id={:?} span={:?} of stability={:?}",
314             def_id, span, stability
315         );
316
317         if let Some(id) = id {
318             if let Some(stability) = stability {
319                 if let Some(depr) = &stability.rustc_depr {
320                     let (message, lint) =
321                         rustc_deprecation_message(depr, &self.def_path_str(def_id));
322                     late_report_deprecation(
323                         self,
324                         &message,
325                         depr.suggestion,
326                         lint,
327                         span,
328                         def_id,
329                         id,
330                     );
331                 }
332             }
333         }
334
335         // Only the cross-crate scenario matters when checking unstable APIs
336         let cross_crate = !def_id.is_local();
337         if !cross_crate {
338             return EvalResult::Allow;
339         }
340
341         // Issue #38412: private items lack stability markers.
342         if skip_stability_check_due_to_privacy(self, def_id) {
343             return EvalResult::Allow;
344         }
345
346         match stability {
347             Some(&Stability {
348                 level: attr::Unstable { reason, issue, is_soft }, feature, ..
349             }) => {
350                 if span.allows_unstable(feature) {
351                     debug!("stability: skipping span={:?} since it is internal", span);
352                     return EvalResult::Allow;
353                 }
354                 if self.stability().active_features.contains(&feature) {
355                     return EvalResult::Allow;
356                 }
357
358                 // When we're compiling the compiler itself we may pull in
359                 // crates from crates.io, but those crates may depend on other
360                 // crates also pulled in from crates.io. We want to ideally be
361                 // able to compile everything without requiring upstream
362                 // modifications, so in the case that this looks like a
363                 // `rustc_private` crate (e.g., a compiler crate) and we also have
364                 // the `-Z force-unstable-if-unmarked` flag present (we're
365                 // compiling a compiler crate), then let this missing feature
366                 // annotation slide.
367                 if feature == sym::rustc_private && issue == NonZeroU32::new(27812) {
368                     if self.sess.opts.debugging_opts.force_unstable_if_unmarked {
369                         return EvalResult::Allow;
370                     }
371                 }
372
373                 EvalResult::Deny { feature, reason, issue, is_soft }
374             }
375             Some(_) => {
376                 // Stable APIs are always ok to call and deprecated APIs are
377                 // handled by the lint emitting logic above.
378                 EvalResult::Allow
379             }
380             None => EvalResult::Unmarked,
381         }
382     }
383
384     /// Checks if an item is stable or error out.
385     ///
386     /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
387     /// exist, emits an error.
388     ///
389     /// Additionally, this function will also check if the item is deprecated. If so, and `id` is
390     /// not `None`, a deprecated lint attached to `id` will be emitted.
391     pub fn check_stability(self, def_id: DefId, id: Option<HirId>, span: Span) {
392         let soft_handler = |lint, span, msg: &_| {
393             self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
394                 lint.build(msg).emit()
395             })
396         };
397         match self.eval_stability(def_id, id, span) {
398             EvalResult::Allow => {}
399             EvalResult::Deny { feature, reason, issue, is_soft } => {
400                 report_unstable(self.sess, feature, reason, issue, is_soft, span, soft_handler)
401             }
402             EvalResult::Unmarked => {
403                 // The API could be uncallable for other reasons, for example when a private module
404                 // was referenced.
405                 self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id));
406             }
407         }
408     }
409
410     pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> {
411         self.lookup_deprecation_entry(id).map(|depr| depr.attr)
412     }
413 }