]> git.proxmox.com Git - rustc.git/blob - src/librustc_middle/middle/stability.rs
New upstream version 1.47.0+dfsg1
[rustc.git] / src / librustc_middle / 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::CRATE_NODE_ID;
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, 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(is_since_rustc_version: bool, since: Option<&str>) -> bool {
134 let since = if let Some(since) = since {
135 if is_since_rustc_version {
136 since
137 } else {
138 // We assume that the deprecation is in effect if it's not a
139 // rustc version.
140 return true;
141 }
142 } else {
143 // If since attribute is not set, then we're definitely in effect.
144 return true;
145 };
146 fn parse_version(ver: &str) -> Vec<u32> {
147 // We ignore non-integer components of the version (e.g., "nightly").
148 ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect()
149 }
150
151 if let Some(rustc) = option_env!("CFG_RELEASE") {
152 let since: Vec<u32> = parse_version(&since);
153 let rustc: Vec<u32> = parse_version(rustc);
154 // We simply treat invalid `since` attributes as relating to a previous
155 // Rust version, thus always displaying the warning.
156 if since.len() != 3 {
157 return true;
158 }
159 since <= rustc
160 } else {
161 // By default, a deprecation warning applies to
162 // the current version of the compiler.
163 true
164 }
165 }
166
167 pub fn deprecation_suggestion(
168 diag: &mut DiagnosticBuilder<'_>,
169 kind: &str,
170 suggestion: Option<Symbol>,
171 span: Span,
172 ) {
173 if let Some(suggestion) = suggestion {
174 diag.span_suggestion(
175 span,
176 &format!("replace the use of the deprecated {}", kind),
177 suggestion.to_string(),
178 Applicability::MachineApplicable,
179 );
180 }
181 }
182
183 pub fn deprecation_message(depr: &Deprecation, kind: &str, path: &str) -> (String, &'static Lint) {
184 let (message, lint) = if deprecation_in_effect(
185 depr.is_since_rustc_version,
186 depr.since.map(Symbol::as_str).as_deref(),
187 ) {
188 (format!("use of deprecated {} `{}`", kind, path), DEPRECATED)
189 } else {
190 (
191 format!(
192 "use of {} `{}` that will be deprecated in future version {}",
193 kind,
194 path,
195 depr.since.unwrap()
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 ) {
214 if span.in_derive_expansion() {
215 return;
216 }
217
218 let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span);
219 lint_buffer.buffer_lint_with_diagnostic(lint, CRATE_NODE_ID, span, message, diag);
220 }
221
222 fn late_report_deprecation(
223 tcx: TyCtxt<'_>,
224 message: &str,
225 suggestion: Option<Symbol>,
226 lint: &'static Lint,
227 span: Span,
228 hir_id: HirId,
229 def_id: DefId,
230 ) {
231 if span.in_derive_expansion() {
232 return;
233 }
234
235 tcx.struct_span_lint_hir(lint, hir_id, span, |lint| {
236 let mut diag = lint.build(message);
237 if let hir::Node::Expr(_) = tcx.hir().get(hir_id) {
238 let kind = tcx.def_kind(def_id).descr(def_id);
239 deprecation_suggestion(&mut diag, kind, suggestion, span);
240 }
241 diag.emit()
242 });
243 }
244
245 /// Result of `TyCtxt::eval_stability`.
246 pub enum EvalResult {
247 /// We can use the item because it is stable or we provided the
248 /// corresponding feature gate.
249 Allow,
250 /// We cannot use the item because it is unstable and we did not provide the
251 /// corresponding feature gate.
252 Deny { feature: Symbol, reason: Option<Symbol>, issue: Option<NonZeroU32>, is_soft: bool },
253 /// The item does not have the `#[stable]` or `#[unstable]` marker assigned.
254 Unmarked,
255 }
256
257 // See issue #38412.
258 fn skip_stability_check_due_to_privacy(tcx: TyCtxt<'_>, mut def_id: DefId) -> bool {
259 // Check if `def_id` is a trait method.
260 match tcx.def_kind(def_id) {
261 DefKind::AssocFn | DefKind::AssocTy | DefKind::AssocConst => {
262 if let ty::TraitContainer(trait_def_id) = tcx.associated_item(def_id).container {
263 // Trait methods do not declare visibility (even
264 // for visibility info in cstore). Use containing
265 // trait instead, so methods of `pub` traits are
266 // themselves considered `pub`.
267 def_id = trait_def_id;
268 }
269 }
270 _ => {}
271 }
272
273 let visibility = tcx.visibility(def_id);
274
275 match visibility {
276 // Must check stability for `pub` items.
277 ty::Visibility::Public => false,
278
279 // These are not visible outside crate; therefore
280 // stability markers are irrelevant, if even present.
281 ty::Visibility::Restricted(..) | ty::Visibility::Invisible => true,
282 }
283 }
284
285 impl<'tcx> TyCtxt<'tcx> {
286 /// Evaluates the stability of an item.
287 ///
288 /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
289 /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
290 /// unstable feature otherwise.
291 ///
292 /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
293 /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
294 /// `id`.
295 pub fn eval_stability(self, def_id: DefId, id: Option<HirId>, span: Span) -> EvalResult {
296 // Deprecated attributes apply in-crate and cross-crate.
297 if let Some(id) = id {
298 if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) {
299 let parent_def_id = self.hir().local_def_id(self.hir().get_parent_item(id));
300 let skip = self
301 .lookup_deprecation_entry(parent_def_id.to_def_id())
302 .map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry));
303
304 // #[deprecated] doesn't emit a notice if we're not on the
305 // topmost deprecation. For example, if a struct is deprecated,
306 // the use of a field won't be linted.
307 //
308 // #[rustc_deprecated] however wants to emit down the whole
309 // hierarchy.
310 if !skip || depr_entry.attr.is_since_rustc_version {
311 let path = &self.def_path_str(def_id);
312 let kind = self.def_kind(def_id).descr(def_id);
313 let (message, lint) = deprecation_message(&depr_entry.attr, kind, path);
314 late_report_deprecation(
315 self,
316 &message,
317 depr_entry.attr.suggestion,
318 lint,
319 span,
320 id,
321 def_id,
322 );
323 }
324 };
325 }
326
327 let is_staged_api =
328 self.lookup_stability(DefId { index: CRATE_DEF_INDEX, ..def_id }).is_some();
329 if !is_staged_api {
330 return EvalResult::Allow;
331 }
332
333 let stability = self.lookup_stability(def_id);
334 debug!(
335 "stability: \
336 inspecting def_id={:?} span={:?} of stability={:?}",
337 def_id, span, stability
338 );
339
340 // Only the cross-crate scenario matters when checking unstable APIs
341 let cross_crate = !def_id.is_local();
342 if !cross_crate {
343 return EvalResult::Allow;
344 }
345
346 // Issue #38412: private items lack stability markers.
347 if skip_stability_check_due_to_privacy(self, def_id) {
348 return EvalResult::Allow;
349 }
350
351 match stability {
352 Some(&Stability {
353 level: attr::Unstable { reason, issue, is_soft }, feature, ..
354 }) => {
355 if span.allows_unstable(feature) {
356 debug!("stability: skipping span={:?} since it is internal", span);
357 return EvalResult::Allow;
358 }
359 if self.stability().active_features.contains(&feature) {
360 return EvalResult::Allow;
361 }
362
363 // When we're compiling the compiler itself we may pull in
364 // crates from crates.io, but those crates may depend on other
365 // crates also pulled in from crates.io. We want to ideally be
366 // able to compile everything without requiring upstream
367 // modifications, so in the case that this looks like a
368 // `rustc_private` crate (e.g., a compiler crate) and we also have
369 // the `-Z force-unstable-if-unmarked` flag present (we're
370 // compiling a compiler crate), then let this missing feature
371 // annotation slide.
372 if feature == sym::rustc_private && issue == NonZeroU32::new(27812) {
373 if self.sess.opts.debugging_opts.force_unstable_if_unmarked {
374 return EvalResult::Allow;
375 }
376 }
377
378 EvalResult::Deny { feature, reason, issue, is_soft }
379 }
380 Some(_) => {
381 // Stable APIs are always ok to call and deprecated APIs are
382 // handled by the lint emitting logic above.
383 EvalResult::Allow
384 }
385 None => EvalResult::Unmarked,
386 }
387 }
388
389 /// Checks if an item is stable or error out.
390 ///
391 /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
392 /// exist, emits an error.
393 ///
394 /// Additionally, this function will also check if the item is deprecated. If so, and `id` is
395 /// not `None`, a deprecated lint attached to `id` will be emitted.
396 pub fn check_stability(self, def_id: DefId, id: Option<HirId>, span: Span) {
397 let soft_handler = |lint, span, msg: &_| {
398 self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
399 lint.build(msg).emit()
400 })
401 };
402 match self.eval_stability(def_id, id, span) {
403 EvalResult::Allow => {}
404 EvalResult::Deny { feature, reason, issue, is_soft } => {
405 report_unstable(self.sess, feature, reason, issue, is_soft, span, soft_handler)
406 }
407 EvalResult::Unmarked => {
408 // The API could be uncallable for other reasons, for example when a private module
409 // was referenced.
410 self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id));
411 }
412 }
413 }
414
415 pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> {
416 self.lookup_deprecation_entry(id).map(|depr| depr.attr)
417 }
418 }