pub use self::StabilityLevel::*;
use crate::ty::{self, TyCtxt};
use rustc_ast::NodeId;
use rustc_attr::{self as attr, ConstStability, Deprecation, Stability};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_feature::GateIssue;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{CrateNum, DefId, CRATE_DEF_INDEX};
use rustc_hir::{self, HirId};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
use rustc_session::lint::{BuiltinLintDiagnostics, Lint, LintBuffer};
use rustc_session::parse::feature_err_issue;
use rustc_session::{DiagnosticMessageId, Session};
use rustc_span::symbol::{sym, Symbol};
use rustc_span::{MultiSpan, Span};
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 }
}
}
#[derive(Clone, HashStable)]
pub struct DeprecationEntry {
pub attr: Deprecation,
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,
}
}
}
#[derive(HashStable)]
pub struct Index<'tcx> {
pub stab_map: FxHashMap<HirId, &'tcx Stability>,
pub const_stab_map: FxHashMap<HirId, &'tcx ConstStability>,
pub depr_map: FxHashMap<HirId, DeprecationEntry>,
pub staged_api: FxHashMap<CrateNum, bool>,
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, 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 sm = &sess.parse_sess.source_map();
let span_key = msp.primary_span().and_then(|sp: Span| {
if !sp.is_dummy() {
let file = sm.lookup_char_pos(sp.lo()).file;
if file.is_imported() { 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(SOFT_UNSTABLE, span, &msg)
} else {
feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg)
.emit();
}
}
}
pub fn deprecation_in_effect(is_since_rustc_version: bool, since: Option<&str>) -> bool {
let since = if let Some(since) = since {
if is_since_rustc_version {
since
} else {
return true;
}
} else {
return true;
};
fn parse_version(ver: &str) -> Vec<u32> {
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);
if since.len() != 3 {
return true;
}
since <= rustc
} else {
true
}
}
pub fn deprecation_suggestion(
diag: &mut DiagnosticBuilder<'_>,
kind: &str,
suggestion: Option<Symbol>,
span: Span,
) {
if let Some(suggestion) = suggestion {
diag.span_suggestion(
span,
&format!("replace the use of the deprecated {}", kind),
suggestion.to_string(),
Applicability::MachineApplicable,
);
}
}
pub fn deprecation_message(depr: &Deprecation, kind: &str, path: &str) -> (String, &'static Lint) {
let (message, lint) = if deprecation_in_effect(
depr.is_since_rustc_version,
depr.since.map(Symbol::as_str).as_deref(),
) {
(format!("use of deprecated {} `{}`", kind, path), DEPRECATED)
} else {
(
format!(
"use of {} `{}` that will be deprecated in future version {}",
kind,
path,
depr.since.unwrap()
),
DEPRECATED_IN_FUTURE,
)
};
let message = match depr.note {
Some(reason) => format!("{}: {}", message, reason),
None => message,
};
(message, lint)
}
pub fn early_report_deprecation(
lint_buffer: &'a mut LintBuffer,
message: &str,
suggestion: Option<Symbol>,
lint: &'static Lint,
span: Span,
node_id: NodeId,
) {
if span.in_derive_expansion() {
return;
}
let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span);
lint_buffer.buffer_lint_with_diagnostic(lint, node_id, span, message, diag);
}
fn late_report_deprecation(
tcx: TyCtxt<'_>,
message: &str,
suggestion: Option<Symbol>,
lint: &'static Lint,
span: Span,
hir_id: HirId,
def_id: DefId,
) {
if span.in_derive_expansion() {
return;
}
tcx.struct_span_lint_hir(lint, hir_id, span, |lint| {
let mut diag = lint.build(message);
if let hir::Node::Expr(_) = tcx.hir().get(hir_id) {
let kind = tcx.def_kind(def_id).descr(def_id);
deprecation_suggestion(&mut diag, kind, suggestion, span);
}
diag.emit()
});
}
pub enum EvalResult {
Allow,
Deny { feature: Symbol, reason: Option<Symbol>, issue: Option<NonZeroU32>, is_soft: bool },
Unmarked,
}
fn skip_stability_check_due_to_privacy(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
if tcx.def_kind(def_id) == DefKind::TyParam {
return false;
}
match tcx.visibility(def_id) {
ty::Visibility::Public => false,
ty::Visibility::Restricted(..) | ty::Visibility::Invisible => true,
}
}
impl<'tcx> TyCtxt<'tcx> {
pub fn eval_stability(self, def_id: DefId, id: Option<HirId>, span: Span) -> EvalResult {
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.to_def_id())
.map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry));
if !skip || depr_entry.attr.is_since_rustc_version {
let path = &with_no_trimmed_paths(|| self.def_path_str(def_id));
let kind = self.def_kind(def_id).descr(def_id);
let (message, lint) = deprecation_message(&depr_entry.attr, kind, path);
late_report_deprecation(
self,
&message,
depr_entry.attr.suggestion,
lint,
span,
id,
def_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
);
let cross_crate = !def_id.is_local();
if !cross_crate {
return EvalResult::Allow;
}
if skip_stability_check_due_to_privacy(self, 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;
}
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(_) => {
EvalResult::Allow
}
None => EvalResult::Unmarked,
}
}
pub fn check_stability(self, def_id: DefId, id: Option<HirId>, span: Span) {
self.check_optional_stability(def_id, id, span, |span, def_id| {
self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id));
})
}
pub fn check_optional_stability(
self,
def_id: DefId,
id: Option<HirId>,
span: Span,
unmarked: impl FnOnce(Span, DefId),
) {
let soft_handler = |lint, span, msg: &_| {
self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
lint.build(msg).emit()
})
};
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 => unmarked(span, def_id),
}
}
pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> {
self.lookup_deprecation_entry(id).map(|depr| depr.attr)
}
}