diff --git a/src/librustc/middle/stability.rs b/src/librustc/middle/stability.rs new file mode 100644 index 00000000000..5ef4942ac3b --- /dev/null +++ b/src/librustc/middle/stability.rs @@ -0,0 +1,410 @@ +//! A pass that annotates every item and method with its stability level, +//! propagating default levels lexically from parent to children ast nodes. + +pub use self::StabilityLevel::*; + +use crate::hir::def::DefKind; +use crate::hir::def_id::{CrateNum, DefId, CRATE_DEF_INDEX}; +use crate::hir::{self, HirId}; +use crate::lint::builtin::BuiltinLintDiagnostics; +use crate::lint::{self, in_derive_expansion, Lint}; +use crate::session::{DiagnosticMessageId, Session}; +use crate::ty::{self, TyCtxt}; +use crate::util::nodemap::{FxHashMap, FxHashSet}; +use errors::DiagnosticBuilder; +use rustc_feature::GateIssue; +use rustc_span::{MultiSpan, Span}; +use syntax::ast::CRATE_NODE_ID; +use syntax::attr::{self, ConstStability, Deprecation, RustcDeprecation, Stability}; +use syntax::errors::Applicability; +use syntax::feature_gate::feature_err_issue; +use syntax::symbol::{sym, Symbol}; + +use std::num::NonZeroU32; + +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum StabilityLevel { + Unstable, + Stable, +} + +impl StabilityLevel { + pub fn from_attr_level(level: &attr::StabilityLevel) -> Self { + if level.is_stable() { Stable } else { Unstable } + } +} + +/// An entry in the `depr_map`. +#[derive(Clone, HashStable)] +pub struct DeprecationEntry { + /// The metadata of the attribute associated with this entry. + pub attr: Deprecation, + /// The `DefId` where the attr was originally attached. `None` for non-local + /// `DefId`'s. + origin: Option, +} + +impl DeprecationEntry { + pub fn local(attr: Deprecation, id: HirId) -> DeprecationEntry { + DeprecationEntry { attr, origin: Some(id) } + } + + pub fn external(attr: Deprecation) -> DeprecationEntry { + DeprecationEntry { attr, origin: None } + } + + pub fn same_origin(&self, other: &DeprecationEntry) -> bool { + match (self.origin, other.origin) { + (Some(o1), Some(o2)) => o1 == o2, + _ => false, + } + } +} + +/// A stability index, giving the stability level for items and methods. +#[derive(HashStable)] +pub struct Index<'tcx> { + /// This is mostly a cache, except the stabilities of local items + /// are filled by the annotator. + pub stab_map: FxHashMap, + pub const_stab_map: FxHashMap, + pub depr_map: FxHashMap, + + /// Maps for each crate whether it is part of the staged API. + pub staged_api: FxHashMap, + + /// Features enabled for this crate. + pub active_features: FxHashSet, +} + +impl<'tcx> Index<'tcx> { + pub fn local_stability(&self, id: HirId) -> Option<&'tcx Stability> { + self.stab_map.get(&id).cloned() + } + + pub fn local_const_stability(&self, id: HirId) -> Option<&'tcx ConstStability> { + self.const_stab_map.get(&id).cloned() + } + + pub fn local_deprecation_entry(&self, id: HirId) -> Option { + self.depr_map.get(&id).cloned() + } +} + +pub fn report_unstable( + sess: &Session, + feature: Symbol, + reason: Option, + issue: Option, + is_soft: bool, + span: Span, + soft_handler: impl FnOnce(&'static lint::Lint, Span, &str), +) { + let msg = match reason { + Some(r) => format!("use of unstable library feature '{}': {}", feature, r), + None => format!("use of unstable library feature '{}'", &feature), + }; + + let msp: MultiSpan = span.into(); + let cm = &sess.parse_sess.source_map(); + let span_key = msp.primary_span().and_then(|sp: Span| { + if !sp.is_dummy() { + let file = cm.lookup_char_pos(sp.lo()).file; + if file.name.is_macros() { None } else { Some(span) } + } else { + None + } + }); + + let error_id = (DiagnosticMessageId::StabilityId(issue), span_key, msg.clone()); + let fresh = sess.one_time_diagnostics.borrow_mut().insert(error_id); + if fresh { + if is_soft { + soft_handler(lint::builtin::SOFT_UNSTABLE, span, &msg) + } else { + feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg) + .emit(); + } + } +} + +/// Checks whether an item marked with `deprecated(since="X")` is currently +/// deprecated (i.e., whether X is not greater than the current rustc version). +pub fn deprecation_in_effect(since: &str) -> bool { + fn parse_version(ver: &str) -> Vec { + // We ignore non-integer components of the version (e.g., "nightly"). + ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect() + } + + if let Some(rustc) = option_env!("CFG_RELEASE") { + let since: Vec = parse_version(since); + let rustc: Vec = parse_version(rustc); + // We simply treat invalid `since` attributes as relating to a previous + // Rust version, thus always displaying the warning. + if since.len() != 3 { + return true; + } + since <= rustc + } else { + // By default, a deprecation warning applies to + // the current version of the compiler. + true + } +} + +pub fn deprecation_suggestion( + diag: &mut DiagnosticBuilder<'_>, + suggestion: Option, + span: Span, +) { + if let Some(suggestion) = suggestion { + diag.span_suggestion( + span, + "replace the use of the deprecated item", + suggestion.to_string(), + Applicability::MachineApplicable, + ); + } +} + +fn deprecation_message_common(message: String, reason: Option) -> String { + match reason { + Some(reason) => format!("{}: {}", message, reason), + None => message, + } +} + +pub fn deprecation_message(depr: &Deprecation, path: &str) -> (String, &'static Lint) { + let message = format!("use of deprecated item '{}'", path); + (deprecation_message_common(message, depr.note), lint::builtin::DEPRECATED) +} + +pub fn rustc_deprecation_message(depr: &RustcDeprecation, path: &str) -> (String, &'static Lint) { + let (message, lint) = if deprecation_in_effect(&depr.since.as_str()) { + (format!("use of deprecated item '{}'", path), lint::builtin::DEPRECATED) + } else { + ( + format!( + "use of item '{}' that will be deprecated in future version {}", + path, depr.since + ), + lint::builtin::DEPRECATED_IN_FUTURE, + ) + }; + (deprecation_message_common(message, Some(depr.reason)), lint) +} + +pub fn early_report_deprecation( + lint_buffer: &'a mut lint::LintBuffer, + message: &str, + suggestion: Option, + lint: &'static Lint, + span: Span, +) { + if in_derive_expansion(span) { + return; + } + + let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span); + lint_buffer.buffer_lint_with_diagnostic(lint, CRATE_NODE_ID, span, message, diag); +} + +fn late_report_deprecation( + tcx: TyCtxt<'_>, + message: &str, + suggestion: Option, + lint: &'static Lint, + span: Span, + def_id: DefId, + hir_id: HirId, +) { + if in_derive_expansion(span) { + return; + } + + let mut diag = tcx.struct_span_lint_hir(lint, hir_id, span, message); + if let hir::Node::Expr(_) = tcx.hir().get(hir_id) { + deprecation_suggestion(&mut diag, suggestion, span); + } + diag.emit(); + if hir_id == hir::DUMMY_HIR_ID { + span_bug!(span, "emitted a {} lint with dummy HIR id: {:?}", lint.name, def_id); + } +} + +/// Result of `TyCtxt::eval_stability`. +pub enum EvalResult { + /// We can use the item because it is stable or we provided the + /// corresponding feature gate. + Allow, + /// We cannot use the item because it is unstable and we did not provide the + /// corresponding feature gate. + Deny { feature: Symbol, reason: Option, issue: Option, is_soft: bool }, + /// The item does not have the `#[stable]` or `#[unstable]` marker assigned. + Unmarked, +} + +impl<'tcx> TyCtxt<'tcx> { + // See issue #38412. + fn skip_stability_check_due_to_privacy(self, mut def_id: DefId) -> bool { + // Check if `def_id` is a trait method. + match self.def_kind(def_id) { + Some(DefKind::Method) | Some(DefKind::AssocTy) | Some(DefKind::AssocConst) => { + if let ty::TraitContainer(trait_def_id) = self.associated_item(def_id).container { + // Trait methods do not declare visibility (even + // for visibility info in cstore). Use containing + // trait instead, so methods of `pub` traits are + // themselves considered `pub`. + def_id = trait_def_id; + } + } + _ => {} + } + + let visibility = self.visibility(def_id); + + match visibility { + // Must check stability for `pub` items. + ty::Visibility::Public => false, + + // These are not visible outside crate; therefore + // stability markers are irrelevant, if even present. + ty::Visibility::Restricted(..) | ty::Visibility::Invisible => true, + } + } + + /// Evaluates the stability of an item. + /// + /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding + /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending + /// unstable feature otherwise. + /// + /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been + /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to + /// `id`. + pub fn eval_stability(self, def_id: DefId, id: Option, span: Span) -> EvalResult { + // Deprecated attributes apply in-crate and cross-crate. + if let Some(id) = id { + if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) { + let parent_def_id = self.hir().local_def_id(self.hir().get_parent_item(id)); + let skip = self + .lookup_deprecation_entry(parent_def_id) + .map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry)); + + if !skip { + let (message, lint) = + deprecation_message(&depr_entry.attr, &self.def_path_str(def_id)); + late_report_deprecation(self, &message, None, lint, span, def_id, id); + } + }; + } + + let is_staged_api = + self.lookup_stability(DefId { index: CRATE_DEF_INDEX, ..def_id }).is_some(); + if !is_staged_api { + return EvalResult::Allow; + } + + let stability = self.lookup_stability(def_id); + debug!( + "stability: \ + inspecting def_id={:?} span={:?} of stability={:?}", + def_id, span, stability + ); + + if let Some(id) = id { + if let Some(stability) = stability { + if let Some(depr) = &stability.rustc_depr { + let (message, lint) = + rustc_deprecation_message(depr, &self.def_path_str(def_id)); + late_report_deprecation( + self, + &message, + depr.suggestion, + lint, + span, + def_id, + id, + ); + } + } + } + + // Only the cross-crate scenario matters when checking unstable APIs + let cross_crate = !def_id.is_local(); + if !cross_crate { + return EvalResult::Allow; + } + + // Issue #38412: private items lack stability markers. + if self.skip_stability_check_due_to_privacy(def_id) { + return EvalResult::Allow; + } + + match stability { + Some(&Stability { + level: attr::Unstable { reason, issue, is_soft }, feature, .. + }) => { + if span.allows_unstable(feature) { + debug!("stability: skipping span={:?} since it is internal", span); + return EvalResult::Allow; + } + if self.stability().active_features.contains(&feature) { + return EvalResult::Allow; + } + + // When we're compiling the compiler itself we may pull in + // crates from crates.io, but those crates may depend on other + // crates also pulled in from crates.io. We want to ideally be + // able to compile everything without requiring upstream + // modifications, so in the case that this looks like a + // `rustc_private` crate (e.g., a compiler crate) and we also have + // the `-Z force-unstable-if-unmarked` flag present (we're + // compiling a compiler crate), then let this missing feature + // annotation slide. + if feature == sym::rustc_private && issue == NonZeroU32::new(27812) { + if self.sess.opts.debugging_opts.force_unstable_if_unmarked { + return EvalResult::Allow; + } + } + + EvalResult::Deny { feature, reason, issue, is_soft } + } + Some(_) => { + // Stable APIs are always ok to call and deprecated APIs are + // handled by the lint emitting logic above. + EvalResult::Allow + } + None => EvalResult::Unmarked, + } + } + + /// Checks if an item is stable or error out. + /// + /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not + /// exist, emits an error. + /// + /// Additionally, this function will also check if the item is deprecated. If so, and `id` is + /// not `None`, a deprecated lint attached to `id` will be emitted. + pub fn check_stability(self, def_id: DefId, id: Option, span: Span) { + let soft_handler = + |lint, span, msg: &_| self.lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, msg); + match self.eval_stability(def_id, id, span) { + EvalResult::Allow => {} + EvalResult::Deny { feature, reason, issue, is_soft } => { + report_unstable(self.sess, feature, reason, issue, is_soft, span, soft_handler) + } + EvalResult::Unmarked => { + // The API could be uncallable for other reasons, for example when a private module + // was referenced. + self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id)); + } + } + } +} + +impl<'tcx> TyCtxt<'tcx> { + pub fn lookup_deprecation(self, id: DefId) -> Option { + self.lookup_deprecation_entry(id).map(|depr| depr.attr) + } +} diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs index daa5ca37a98..d83a720170b 100644 --- a/src/librustc/ty/context.rs +++ b/src/librustc/ty/context.rs @@ -2765,10 +2765,6 @@ pub fn provide(providers: &mut ty::query::Providers<'_>) { Lrc::new(tcx.glob_map.get(&id).cloned().unwrap_or_default()) }; - providers.stability_index = |tcx, cnum| { - assert_eq!(cnum, LOCAL_CRATE); - tcx.arena.alloc(stability::Index::new(tcx)) - }; providers.lookup_stability = |tcx, id| { assert_eq!(id.krate, LOCAL_CRATE); let id = tcx.hir().definitions().def_index_to_hir_id(id.index); diff --git a/src/librustc_interface/passes.rs b/src/librustc_interface/passes.rs index 4d15424112e..670ca30faf2 100644 --- a/src/librustc_interface/passes.rs +++ b/src/librustc_interface/passes.rs @@ -8,8 +8,8 @@ use rustc::dep_graph::DepGraph; use rustc::hir; use rustc::hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc::lint; +use rustc::middle; use rustc::middle::cstore::{CrateStore, MetadataLoader, MetadataLoaderDyn}; -use rustc::middle::{self, stability}; use rustc::session::config::{self, CrateType, Input, OutputFilenames, OutputType}; use rustc::session::config::{PpMode, PpSourceMode}; use rustc::session::search_paths::PathKind; @@ -687,7 +687,6 @@ pub fn default_provide(providers: &mut ty::query::Providers<'_>) { typeck::provide(providers); ty::provide(providers); traits::provide(providers); - stability::provide(providers); rustc_passes::provide(providers); rustc_resolve::provide(providers); rustc_traits::provide(providers); @@ -881,7 +880,7 @@ fn analysis(tcx: TyCtxt<'_>, cnum: CrateNum) -> Result<()> { }, { time(sess, "unused lib feature checking", || { - stability::check_unused_or_stable_features(tcx) + rustc_passes::stability::check_unused_or_stable_features(tcx) }); }, { diff --git a/src/librustc_passes/lib.rs b/src/librustc_passes/lib.rs index 8a10c8fe89d..a692c45ced4 100644 --- a/src/librustc_passes/lib.rs +++ b/src/librustc_passes/lib.rs @@ -32,6 +32,7 @@ mod liveness; pub mod loops; mod reachable; mod region; +pub mod stability; pub fn provide(providers: &mut Providers<'_>) { check_const::provide(providers); @@ -43,4 +44,5 @@ pub fn provide(providers: &mut Providers<'_>) { intrinsicck::provide(providers); reachable::provide(providers); region::provide(providers); + stability::provide(providers); } diff --git a/src/librustc_passes/stability.rs b/src/librustc_passes/stability.rs index 7b186bc7a14..fcbc742b9a0 100644 --- a/src/librustc_passes/stability.rs +++ b/src/librustc_passes/stability.rs @@ -1,26 +1,21 @@ //! A pass that annotates every item and method with its stability level, //! propagating default levels lexically from parent to children ast nodes. -pub use self::StabilityLevel::*; - -use crate::hir::def::{DefKind, Res}; -use crate::hir::def_id::{CrateNum, DefId, CRATE_DEF_INDEX, LOCAL_CRATE}; -use crate::hir::intravisit::{self, NestedVisitorMap, Visitor}; -use crate::hir::{self, Generics, HirId, Item, StructField, Variant}; -use crate::lint::builtin::BuiltinLintDiagnostics; -use crate::lint::{self, in_derive_expansion, Lint}; -use crate::middle::privacy::AccessLevels; -use crate::session::{DiagnosticMessageId, Session}; -use crate::ty::query::Providers; -use crate::ty::{self, TyCtxt}; -use crate::util::nodemap::{FxHashMap, FxHashSet}; -use errors::DiagnosticBuilder; -use rustc_feature::GateIssue; -use rustc_span::{MultiSpan, Span}; -use syntax::ast::{Attribute, CRATE_NODE_ID}; -use syntax::attr::{self, ConstStability, Deprecation, RustcDeprecation, Stability}; -use syntax::errors::Applicability; -use syntax::feature_gate::{feature_err, feature_err_issue}; +use rustc::hir::def::{DefKind, Res}; +use rustc::hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE}; +use rustc::hir::intravisit::{self, NestedVisitorMap, Visitor}; +use rustc::hir::{self, Generics, HirId, Item, StructField, Variant}; +use rustc::lint; +use rustc::middle::privacy::AccessLevels; +use rustc::middle::stability::{DeprecationEntry, Index}; +use rustc::session::Session; +use rustc::ty::query::Providers; +use rustc::ty::TyCtxt; +use rustc::util::nodemap::{FxHashMap, FxHashSet}; +use rustc_span::Span; +use syntax::ast::Attribute; +use syntax::attr::{self, Stability}; +use syntax::feature_gate::feature_err; use syntax::symbol::{sym, Symbol}; use std::cmp::Ordering; @@ -29,18 +24,6 @@ use std::num::NonZeroU32; use rustc_error_codes::*; -#[derive(PartialEq, Clone, Copy, Debug)] -pub enum StabilityLevel { - Unstable, - Stable, -} - -impl StabilityLevel { - pub fn from_attr_level(level: &attr::StabilityLevel) -> Self { - if level.is_stable() { Stable } else { Unstable } - } -} - #[derive(PartialEq)] enum AnnotationKind { // Annotation is required if not inherited from unstable parents @@ -51,49 +34,6 @@ enum AnnotationKind { Container, } -/// An entry in the `depr_map`. -#[derive(Clone, HashStable)] -pub struct DeprecationEntry { - /// The metadata of the attribute associated with this entry. - pub attr: Deprecation, - /// The `DefId` where the attr was originally attached. `None` for non-local - /// `DefId`'s. - origin: Option, -} - -impl DeprecationEntry { - fn local(attr: Deprecation, id: HirId) -> DeprecationEntry { - DeprecationEntry { attr, origin: Some(id) } - } - - pub fn external(attr: Deprecation) -> DeprecationEntry { - DeprecationEntry { attr, origin: None } - } - - pub fn same_origin(&self, other: &DeprecationEntry) -> bool { - match (self.origin, other.origin) { - (Some(o1), Some(o2)) => o1 == o2, - _ => false, - } - } -} - -/// A stability index, giving the stability level for items and methods. -#[derive(HashStable)] -pub struct Index<'tcx> { - /// This is mostly a cache, except the stabilities of local items - /// are filled by the annotator. - stab_map: FxHashMap, - const_stab_map: FxHashMap, - depr_map: FxHashMap, - - /// Maps for each crate whether it is part of the staged API. - staged_api: FxHashMap, - - /// Features enabled for this crate. - active_features: FxHashSet, -} - // A private tree-walker for producing an Index. struct Annotator<'a, 'tcx> { tcx: TyCtxt<'tcx>, @@ -401,84 +341,70 @@ impl<'a, 'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'a, 'tcx> { } } -impl<'tcx> Index<'tcx> { - pub fn new(tcx: TyCtxt<'tcx>) -> Index<'tcx> { - let is_staged_api = - tcx.sess.opts.debugging_opts.force_unstable_if_unmarked || tcx.features().staged_api; - let mut staged_api = FxHashMap::default(); - staged_api.insert(LOCAL_CRATE, is_staged_api); - let mut index = Index { - staged_api, - stab_map: Default::default(), - const_stab_map: Default::default(), - depr_map: Default::default(), - active_features: Default::default(), +fn new_index(tcx: TyCtxt<'tcx>) -> Index<'tcx> { + let is_staged_api = + tcx.sess.opts.debugging_opts.force_unstable_if_unmarked || tcx.features().staged_api; + let mut staged_api = FxHashMap::default(); + staged_api.insert(LOCAL_CRATE, is_staged_api); + let mut index = Index { + staged_api, + stab_map: Default::default(), + const_stab_map: Default::default(), + depr_map: Default::default(), + active_features: Default::default(), + }; + + let active_lib_features = &tcx.features().declared_lib_features; + let active_lang_features = &tcx.features().declared_lang_features; + + // Put the active features into a map for quick lookup. + index.active_features = active_lib_features + .iter() + .map(|&(ref s, ..)| s.clone()) + .chain(active_lang_features.iter().map(|&(ref s, ..)| s.clone())) + .collect(); + + { + let krate = tcx.hir().krate(); + let mut annotator = Annotator { + tcx, + index: &mut index, + parent_stab: None, + parent_depr: None, + in_trait_impl: false, }; - let active_lib_features = &tcx.features().declared_lib_features; - let active_lang_features = &tcx.features().declared_lang_features; - - // Put the active features into a map for quick lookup. - index.active_features = active_lib_features - .iter() - .map(|&(ref s, ..)| s.clone()) - .chain(active_lang_features.iter().map(|&(ref s, ..)| s.clone())) - .collect(); - - { - let krate = tcx.hir().krate(); - let mut annotator = Annotator { - tcx, - index: &mut index, - parent_stab: None, - parent_depr: None, - in_trait_impl: false, - }; - - // If the `-Z force-unstable-if-unmarked` flag is passed then we provide - // a parent stability annotation which indicates that this is private - // with the `rustc_private` feature. This is intended for use when - // compiling librustc crates themselves so we can leverage crates.io - // while maintaining the invariant that all sysroot crates are unstable - // by default and are unable to be used. - if tcx.sess.opts.debugging_opts.force_unstable_if_unmarked { - let reason = "this crate is being loaded from the sysroot, an \ - unstable location; did you mean to load this crate \ - from crates.io via `Cargo.toml` instead?"; - let stability = tcx.intern_stability(Stability { - level: attr::StabilityLevel::Unstable { - reason: Some(Symbol::intern(reason)), - issue: NonZeroU32::new(27812), - is_soft: false, - }, - feature: sym::rustc_private, - rustc_depr: None, - }); - annotator.parent_stab = Some(stability); - } - - annotator.annotate( - hir::CRATE_HIR_ID, - &krate.attrs, - krate.span, - AnnotationKind::Required, - |v| intravisit::walk_crate(v, krate), - ); + // If the `-Z force-unstable-if-unmarked` flag is passed then we provide + // a parent stability annotation which indicates that this is private + // with the `rustc_private` feature. This is intended for use when + // compiling librustc crates themselves so we can leverage crates.io + // while maintaining the invariant that all sysroot crates are unstable + // by default and are unable to be used. + if tcx.sess.opts.debugging_opts.force_unstable_if_unmarked { + let reason = "this crate is being loaded from the sysroot, an \ + unstable location; did you mean to load this crate \ + from crates.io via `Cargo.toml` instead?"; + let stability = tcx.intern_stability(Stability { + level: attr::StabilityLevel::Unstable { + reason: Some(Symbol::intern(reason)), + issue: NonZeroU32::new(27812), + is_soft: false, + }, + feature: sym::rustc_private, + rustc_depr: None, + }); + annotator.parent_stab = Some(stability); } - return index; - } - pub fn local_stability(&self, id: HirId) -> Option<&'tcx Stability> { - self.stab_map.get(&id).cloned() - } - - pub fn local_const_stability(&self, id: HirId) -> Option<&'tcx ConstStability> { - self.const_stab_map.get(&id).cloned() - } - - pub fn local_deprecation_entry(&self, id: HirId) -> Option { - self.depr_map.get(&id).cloned() + annotator.annotate( + hir::CRATE_HIR_ID, + &krate.attrs, + krate.span, + AnnotationKind::Required, + |v| intravisit::walk_crate(v, krate), + ); } + return index; } /// Cross-references the feature names of unstable APIs with enabled @@ -487,326 +413,18 @@ fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, module_def_id: DefId) { tcx.hir().visit_item_likes_in_module(module_def_id, &mut Checker { tcx }.as_deep_visitor()); } -pub fn provide(providers: &mut Providers<'_>) { +pub(crate) fn provide(providers: &mut Providers<'_>) { *providers = Providers { check_mod_unstable_api_usage, ..*providers }; -} - -pub fn report_unstable( - sess: &Session, - feature: Symbol, - reason: Option, - issue: Option, - is_soft: bool, - span: Span, - soft_handler: impl FnOnce(&'static lint::Lint, Span, &str), -) { - let msg = match reason { - Some(r) => format!("use of unstable library feature '{}': {}", feature, r), - None => format!("use of unstable library feature '{}'", &feature), + providers.stability_index = |tcx, cnum| { + assert_eq!(cnum, LOCAL_CRATE); + tcx.arena.alloc(new_index(tcx)) }; - - let msp: MultiSpan = span.into(); - let cm = &sess.parse_sess.source_map(); - let span_key = msp.primary_span().and_then(|sp: Span| { - if !sp.is_dummy() { - let file = cm.lookup_char_pos(sp.lo()).file; - if file.name.is_macros() { None } else { Some(span) } - } else { - None - } - }); - - let error_id = (DiagnosticMessageId::StabilityId(issue), span_key, msg.clone()); - let fresh = sess.one_time_diagnostics.borrow_mut().insert(error_id); - if fresh { - if is_soft { - soft_handler(lint::builtin::SOFT_UNSTABLE, span, &msg) - } else { - feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg) - .emit(); - } - } -} - -/// Checks whether an item marked with `deprecated(since="X")` is currently -/// deprecated (i.e., whether X is not greater than the current rustc version). -pub fn deprecation_in_effect(since: &str) -> bool { - fn parse_version(ver: &str) -> Vec { - // We ignore non-integer components of the version (e.g., "nightly"). - ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect() - } - - if let Some(rustc) = option_env!("CFG_RELEASE") { - let since: Vec = parse_version(since); - let rustc: Vec = parse_version(rustc); - // We simply treat invalid `since` attributes as relating to a previous - // Rust version, thus always displaying the warning. - if since.len() != 3 { - return true; - } - since <= rustc - } else { - // By default, a deprecation warning applies to - // the current version of the compiler. - true - } -} - -pub fn deprecation_suggestion( - diag: &mut DiagnosticBuilder<'_>, - suggestion: Option, - span: Span, -) { - if let Some(suggestion) = suggestion { - diag.span_suggestion( - span, - "replace the use of the deprecated item", - suggestion.to_string(), - Applicability::MachineApplicable, - ); - } -} - -fn deprecation_message_common(message: String, reason: Option) -> String { - match reason { - Some(reason) => format!("{}: {}", message, reason), - None => message, - } -} - -pub fn deprecation_message(depr: &Deprecation, path: &str) -> (String, &'static Lint) { - let message = format!("use of deprecated item '{}'", path); - (deprecation_message_common(message, depr.note), lint::builtin::DEPRECATED) -} - -pub fn rustc_deprecation_message(depr: &RustcDeprecation, path: &str) -> (String, &'static Lint) { - let (message, lint) = if deprecation_in_effect(&depr.since.as_str()) { - (format!("use of deprecated item '{}'", path), lint::builtin::DEPRECATED) - } else { - ( - format!( - "use of item '{}' that will be deprecated in future version {}", - path, depr.since - ), - lint::builtin::DEPRECATED_IN_FUTURE, - ) - }; - (deprecation_message_common(message, Some(depr.reason)), lint) -} - -pub fn early_report_deprecation( - lint_buffer: &'a mut lint::LintBuffer, - message: &str, - suggestion: Option, - lint: &'static Lint, - span: Span, -) { - if in_derive_expansion(span) { - return; - } - - let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span); - lint_buffer.buffer_lint_with_diagnostic(lint, CRATE_NODE_ID, span, message, diag); -} - -fn late_report_deprecation( - tcx: TyCtxt<'_>, - message: &str, - suggestion: Option, - lint: &'static Lint, - span: Span, - def_id: DefId, - hir_id: HirId, -) { - if in_derive_expansion(span) { - return; - } - - let mut diag = tcx.struct_span_lint_hir(lint, hir_id, span, message); - if let hir::Node::Expr(_) = tcx.hir().get(hir_id) { - deprecation_suggestion(&mut diag, suggestion, span); - } - diag.emit(); - if hir_id == hir::DUMMY_HIR_ID { - span_bug!(span, "emitted a {} lint with dummy HIR id: {:?}", lint.name, def_id); - } } struct Checker<'tcx> { tcx: TyCtxt<'tcx>, } -/// Result of `TyCtxt::eval_stability`. -pub enum EvalResult { - /// We can use the item because it is stable or we provided the - /// corresponding feature gate. - Allow, - /// We cannot use the item because it is unstable and we did not provide the - /// corresponding feature gate. - Deny { feature: Symbol, reason: Option, issue: Option, is_soft: bool }, - /// The item does not have the `#[stable]` or `#[unstable]` marker assigned. - Unmarked, -} - -impl<'tcx> TyCtxt<'tcx> { - // See issue #38412. - fn skip_stability_check_due_to_privacy(self, mut def_id: DefId) -> bool { - // Check if `def_id` is a trait method. - match self.def_kind(def_id) { - Some(DefKind::Method) | Some(DefKind::AssocTy) | Some(DefKind::AssocConst) => { - if let ty::TraitContainer(trait_def_id) = self.associated_item(def_id).container { - // Trait methods do not declare visibility (even - // for visibility info in cstore). Use containing - // trait instead, so methods of `pub` traits are - // themselves considered `pub`. - def_id = trait_def_id; - } - } - _ => {} - } - - let visibility = self.visibility(def_id); - - match visibility { - // Must check stability for `pub` items. - ty::Visibility::Public => false, - - // These are not visible outside crate; therefore - // stability markers are irrelevant, if even present. - ty::Visibility::Restricted(..) | ty::Visibility::Invisible => true, - } - } - - /// Evaluates the stability of an item. - /// - /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding - /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending - /// unstable feature otherwise. - /// - /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been - /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to - /// `id`. - pub fn eval_stability(self, def_id: DefId, id: Option, span: Span) -> EvalResult { - // Deprecated attributes apply in-crate and cross-crate. - if let Some(id) = id { - if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) { - let parent_def_id = self.hir().local_def_id(self.hir().get_parent_item(id)); - let skip = self - .lookup_deprecation_entry(parent_def_id) - .map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry)); - - if !skip { - let (message, lint) = - deprecation_message(&depr_entry.attr, &self.def_path_str(def_id)); - late_report_deprecation(self, &message, None, lint, span, def_id, id); - } - }; - } - - let is_staged_api = - self.lookup_stability(DefId { index: CRATE_DEF_INDEX, ..def_id }).is_some(); - if !is_staged_api { - return EvalResult::Allow; - } - - let stability = self.lookup_stability(def_id); - debug!( - "stability: \ - inspecting def_id={:?} span={:?} of stability={:?}", - def_id, span, stability - ); - - if let Some(id) = id { - if let Some(stability) = stability { - if let Some(depr) = &stability.rustc_depr { - let (message, lint) = - rustc_deprecation_message(depr, &self.def_path_str(def_id)); - late_report_deprecation( - self, - &message, - depr.suggestion, - lint, - span, - def_id, - id, - ); - } - } - } - - // Only the cross-crate scenario matters when checking unstable APIs - let cross_crate = !def_id.is_local(); - if !cross_crate { - return EvalResult::Allow; - } - - // Issue #38412: private items lack stability markers. - if self.skip_stability_check_due_to_privacy(def_id) { - return EvalResult::Allow; - } - - match stability { - Some(&Stability { - level: attr::Unstable { reason, issue, is_soft }, feature, .. - }) => { - if span.allows_unstable(feature) { - debug!("stability: skipping span={:?} since it is internal", span); - return EvalResult::Allow; - } - if self.stability().active_features.contains(&feature) { - return EvalResult::Allow; - } - - // When we're compiling the compiler itself we may pull in - // crates from crates.io, but those crates may depend on other - // crates also pulled in from crates.io. We want to ideally be - // able to compile everything without requiring upstream - // modifications, so in the case that this looks like a - // `rustc_private` crate (e.g., a compiler crate) and we also have - // the `-Z force-unstable-if-unmarked` flag present (we're - // compiling a compiler crate), then let this missing feature - // annotation slide. - if feature == sym::rustc_private && issue == NonZeroU32::new(27812) { - if self.sess.opts.debugging_opts.force_unstable_if_unmarked { - return EvalResult::Allow; - } - } - - EvalResult::Deny { feature, reason, issue, is_soft } - } - Some(_) => { - // Stable APIs are always ok to call and deprecated APIs are - // handled by the lint emitting logic above. - EvalResult::Allow - } - None => EvalResult::Unmarked, - } - } - - /// Checks if an item is stable or error out. - /// - /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not - /// exist, emits an error. - /// - /// Additionally, this function will also check if the item is deprecated. If so, and `id` is - /// not `None`, a deprecated lint attached to `id` will be emitted. - pub fn check_stability(self, def_id: DefId, id: Option, span: Span) { - let soft_handler = - |lint, span, msg: &_| self.lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, msg); - match self.eval_stability(def_id, id, span) { - EvalResult::Allow => {} - EvalResult::Deny { feature, reason, issue, is_soft } => { - report_unstable(self.sess, feature, reason, issue, is_soft, span, soft_handler) - } - EvalResult::Unmarked => { - // The API could be uncallable for other reasons, for example when a private module - // was referenced. - self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id)); - } - } - } -} - impl Visitor<'tcx> for Checker<'tcx> { /// Because stability levels are scoped lexically, we want to walk /// nested items in the context of the outer item, so enable @@ -894,12 +512,6 @@ impl Visitor<'tcx> for Checker<'tcx> { } } -impl<'tcx> TyCtxt<'tcx> { - pub fn lookup_deprecation(self, id: DefId) -> Option { - self.lookup_deprecation_entry(id).map(|depr| depr.attr) - } -} - /// Given the list of enabled features that were not language features (i.e., that /// were expected to be library features), and the list of features used from /// libraries, identify activated features that don't exist and error about them.