Move stability queries to librustc_passes.
Contains: - check_mod_unstable_api_usage query; - stability_index query; - check_unused_or_stable features pass.
This commit is contained in:
parent
eca7797d6b
commit
36b6630771
410
src/librustc/middle/stability.rs
Normal file
410
src/librustc/middle/stability.rs
Normal file
@ -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<HirId>,
|
||||
}
|
||||
|
||||
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<HirId, &'tcx Stability>,
|
||||
pub const_stab_map: FxHashMap<HirId, &'tcx ConstStability>,
|
||||
pub depr_map: FxHashMap<HirId, DeprecationEntry>,
|
||||
|
||||
/// Maps for each crate whether it is part of the staged API.
|
||||
pub staged_api: FxHashMap<CrateNum, bool>,
|
||||
|
||||
/// Features enabled for this crate.
|
||||
pub active_features: FxHashSet<Symbol>,
|
||||
}
|
||||
|
||||
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<DeprecationEntry> {
|
||||
self.depr_map.get(&id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_unstable(
|
||||
sess: &Session,
|
||||
feature: Symbol,
|
||||
reason: Option<Symbol>,
|
||||
issue: Option<NonZeroU32>,
|
||||
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<u32> {
|
||||
// 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<u32> = parse_version(since);
|
||||
let rustc: Vec<u32> = 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<Symbol>,
|
||||
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<Symbol>) -> 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<Symbol>,
|
||||
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<Symbol>,
|
||||
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<Symbol>, issue: Option<NonZeroU32>, 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<HirId>, 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<HirId>, 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<Deprecation> {
|
||||
self.lookup_deprecation_entry(id).map(|depr| depr.attr)
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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)
|
||||
});
|
||||
},
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<HirId>,
|
||||
}
|
||||
|
||||
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<HirId, &'tcx Stability>,
|
||||
const_stab_map: FxHashMap<HirId, &'tcx ConstStability>,
|
||||
depr_map: FxHashMap<HirId, DeprecationEntry>,
|
||||
|
||||
/// Maps for each crate whether it is part of the staged API.
|
||||
staged_api: FxHashMap<CrateNum, bool>,
|
||||
|
||||
/// Features enabled for this crate.
|
||||
active_features: FxHashSet<Symbol>,
|
||||
}
|
||||
|
||||
// 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<DeprecationEntry> {
|
||||
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<Symbol>,
|
||||
issue: Option<NonZeroU32>,
|
||||
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<u32> {
|
||||
// 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<u32> = parse_version(since);
|
||||
let rustc: Vec<u32> = 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<Symbol>,
|
||||
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<Symbol>) -> 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<Symbol>,
|
||||
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<Symbol>,
|
||||
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<Symbol>, issue: Option<NonZeroU32>, 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<HirId>, 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<HirId>, 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<Deprecation> {
|
||||
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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user