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