Files
rust/compiler/rustc_passes/src/stability.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1163 lines
47 KiB
Rust
Raw Normal View History

//! A pass that annotates every item and method with its stability level,
//! propagating default levels lexically from parent to children ast nodes.
use std::num::NonZero;
use rustc_ast_lowering::stability::extern_abi_stability;
use rustc_attr_data_structures::{
2023-01-30 20:47:11 +00:00
self as attrs, AttributeKind, ConstStability, DefaultBodyStability, DeprecatedSince, Stability,
StabilityLevel, StableSince, UnstableReason, VERSION_PLACEHOLDER, find_attr,
};
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet};
use rustc_feature::{EnabledLangFeature, EnabledLibFeature};
use rustc_hir::def::{DefKind, Res};
2023-11-17 23:40:04 +00:00
use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId, LocalModDefId};
use rustc_hir::intravisit::{self, Visitor, VisitorExt};
use rustc_hir::{self as hir, AmbigArg, FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
use rustc_middle::hir::nested_filter;
use rustc_middle::middle::lib_features::{FeatureStability, LibFeatures};
use rustc_middle::middle::privacy::EffectiveVisibilities;
2025-07-12 10:58:33 +00:00
use rustc_middle::middle::stability::{AllowUnstable, Deprecated, DeprecationEntry, EvalResult};
use rustc_middle::query::{LocalCrate, Providers};
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_session::lint;
use rustc_session::lint::builtin::{DEPRECATED, INEFFECTIVE_UNSTABLE_TRAIT_IMPL};
use rustc_span::{Span, Symbol, sym};
2025-07-16 22:32:05 +00:00
use tracing::instrument;
use crate::errors;
2015-11-16 19:53:41 +03:00
#[derive(PartialEq)]
enum AnnotationKind {
/// Annotation is required if not inherited from unstable parents.
2015-11-16 21:01:06 +03:00
Required,
/// Annotation is useless, reject it.
2015-11-16 21:01:06 +03:00
Prohibited,
/// Deprecation annotation is useless, reject it. (Stability attribute is still required.)
DeprecationProhibited,
/// Annotation itself is useless, but it can be propagated to children.
2015-11-16 21:01:06 +03:00
Container,
2015-11-16 19:53:41 +03:00
}
2025-07-16 22:34:00 +00:00
fn inherit_deprecation(def_kind: DefKind) -> bool {
2022-01-18 21:01:07 +01:00
match def_kind {
2025-07-16 22:34:00 +00:00
DefKind::LifetimeParam | DefKind::TyParam | DefKind::ConstParam => false,
_ => true,
2022-01-18 21:01:07 +01:00
}
}
2025-07-16 22:34:00 +00:00
fn inherit_const_stability(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
2025-07-12 10:41:16 +00:00
let def_kind = tcx.def_kind(def_id);
match def_kind {
DefKind::AssocFn | DefKind::AssocTy | DefKind::AssocConst => {
match tcx.def_kind(tcx.local_parent(def_id)) {
2025-07-16 22:34:00 +00:00
DefKind::Impl { of_trait: true } => true,
_ => false,
2025-07-12 10:41:16 +00:00
}
}
2025-07-16 22:34:00 +00:00
_ => false,
2025-07-12 10:41:16 +00:00
}
}
fn annotation_kind(tcx: TyCtxt<'_>, def_id: LocalDefId) -> AnnotationKind {
let def_kind = tcx.def_kind(def_id);
match def_kind {
// Inherent impls and foreign modules serve only as containers for other items,
// they don't have their own stability. They still can be annotated as unstable
// and propagate this unstability to children, but this annotation is completely
// optional. They inherit stability from their parents when unannotated.
DefKind::Impl { of_trait: false } | DefKind::ForeignMod => AnnotationKind::Container,
DefKind::Impl { of_trait: true } => AnnotationKind::DeprecationProhibited,
// Allow stability attributes on default generic arguments.
DefKind::TyParam | DefKind::ConstParam => {
match &tcx.hir_node_by_def_id(def_id).expect_generic_param().kind {
hir::GenericParamKind::Type { default: Some(_), .. }
| hir::GenericParamKind::Const { default: Some(_), .. } => {
AnnotationKind::Container
}
_ => AnnotationKind::Prohibited,
}
}
// Impl items in trait impls cannot have stability.
DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst => {
match tcx.def_kind(tcx.local_parent(def_id)) {
DefKind::Impl { of_trait: true } => AnnotationKind::Prohibited,
_ => AnnotationKind::Required,
}
}
_ => AnnotationKind::Required,
}
}
2022-01-18 21:01:07 +01:00
fn lookup_deprecation_entry(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<DeprecationEntry> {
let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
let depr = attrs::find_attr!(attrs,
AttributeKind::Deprecation { deprecation, span: _ } => *deprecation
);
let Some(depr) = depr else {
2025-07-16 22:34:00 +00:00
if inherit_deprecation(tcx.def_kind(def_id)) {
2022-01-18 21:01:07 +01:00
let parent_id = tcx.opt_local_parent(def_id)?;
let parent_depr = tcx.lookup_deprecation_entry(parent_id)?;
return Some(parent_depr);
}
return None;
};
// `Deprecation` is just two pointers, no need to intern it
Some(DeprecationEntry::local(depr, def_id))
}
2025-07-16 22:34:00 +00:00
fn inherit_stability(def_kind: DefKind) -> bool {
2025-07-12 10:15:25 +00:00
match def_kind {
2025-07-16 22:34:00 +00:00
DefKind::Field | DefKind::Variant | DefKind::Ctor(..) => true,
_ => false,
2025-07-12 10:15:25 +00:00
}
}
/// 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
2025-07-16 22:42:55 +00:00
/// compiling library and `rustc_*` crates themselves so we can leverage crates.io
2025-07-12 10:15:25 +00:00
/// while maintaining the invariant that all sysroot crates are unstable
/// by default and are unable to be used.
const FORCE_UNSTABLE: Stability = Stability {
level: attrs::StabilityLevel::Unstable {
reason: UnstableReason::Default,
issue: NonZero::new(27812),
is_soft: false,
implied_by: None,
old_name: None,
},
feature: sym::rustc_private,
};
#[instrument(level = "debug", skip(tcx))]
fn lookup_stability(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Stability> {
// Propagate unstability. This can happen even for non-staged-api crates in case
// -Zforce-unstable-if-unmarked is set.
if !tcx.features().staged_api() {
if !tcx.sess.opts.unstable_opts.force_unstable_if_unmarked {
return None;
}
let Some(parent) = tcx.opt_local_parent(def_id) else { return Some(FORCE_UNSTABLE) };
2025-07-16 22:34:00 +00:00
if inherit_deprecation(tcx.def_kind(def_id)) {
2025-07-12 10:15:25 +00:00
let parent = tcx.lookup_stability(parent)?;
if parent.is_unstable() {
return Some(parent);
}
}
return None;
}
// # Regular stability
let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
let stab =
attrs::find_attr!(attrs, AttributeKind::Stability { stability, span: _ } => *stability);
if let Some(stab) = stab {
return Some(stab);
}
2025-07-16 22:34:00 +00:00
if inherit_deprecation(tcx.def_kind(def_id)) {
2025-07-12 10:15:25 +00:00
let Some(parent) = tcx.opt_local_parent(def_id) else {
return tcx
.sess
.opts
.unstable_opts
.force_unstable_if_unmarked
.then_some(FORCE_UNSTABLE);
};
let parent = tcx.lookup_stability(parent)?;
2025-07-16 22:34:00 +00:00
if parent.is_unstable() || inherit_stability(tcx.def_kind(def_id)) {
2025-07-12 10:15:25 +00:00
return Some(parent);
}
}
None
}
2023-01-30 20:47:11 +00:00
#[instrument(level = "debug", skip(tcx))]
fn lookup_default_body_stability(
tcx: TyCtxt<'_>,
def_id: LocalDefId,
) -> Option<DefaultBodyStability> {
if !tcx.features().staged_api() {
return None;
}
let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
// FIXME: check that this item can have body stability
attrs::find_attr!(attrs, AttributeKind::BodyStability { stability, .. } => *stability)
}
2025-07-12 10:41:16 +00:00
#[instrument(level = "debug", skip(tcx))]
fn lookup_const_stability(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<ConstStability> {
if !tcx.features().staged_api() {
// Propagate unstability. This can happen even for non-staged-api crates in case
// -Zforce-unstable-if-unmarked is set.
2025-07-16 22:34:00 +00:00
if inherit_deprecation(tcx.def_kind(def_id)) {
2025-07-12 10:41:16 +00:00
let parent = tcx.opt_local_parent(def_id)?;
let parent_stab = tcx.lookup_stability(parent)?;
if parent_stab.is_unstable()
&& let Some(fn_sig) = tcx.hir_node_by_def_id(def_id).fn_sig()
&& fn_sig.header.is_const()
{
let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
let const_stability_indirect =
find_attr!(attrs, AttributeKind::ConstStabilityIndirect);
return Some(ConstStability::unmarked(const_stability_indirect, parent_stab));
}
}
return None;
}
let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
let const_stability_indirect = find_attr!(attrs, AttributeKind::ConstStabilityIndirect);
let const_stab = attrs::find_attr!(attrs, AttributeKind::ConstStability { stability, span: _ } => *stability);
// After checking the immediate attributes, get rid of the span and compute implied
// const stability: inherit feature gate from regular stability.
let mut const_stab = const_stab
.map(|const_stab| ConstStability::from_partial(const_stab, const_stability_indirect));
2025-07-16 22:43:26 +00:00
// If this is a const fn but not annotated with stability markers, see if we can inherit
// regular stability.
2025-07-12 10:41:16 +00:00
if let Some(fn_sig) = tcx.hir_node_by_def_id(def_id).fn_sig()
2025-07-16 22:43:26 +00:00
&& fn_sig.header.is_const()
&& const_stab.is_none()
// We only ever inherit unstable features.
&& let Some(inherit_regular_stab) = tcx.lookup_stability(def_id)
&& inherit_regular_stab.is_unstable()
2025-07-12 10:41:16 +00:00
{
const_stab = Some(ConstStability {
// We subject these implicitly-const functions to recursive const stability.
const_stable_indirect: true,
promotable: false,
level: inherit_regular_stab.level,
feature: inherit_regular_stab.feature,
});
}
if let Some(const_stab) = const_stab {
return Some(const_stab);
}
2025-07-16 22:42:55 +00:00
// `impl const Trait for Type` items forward their const stability to their immediate children.
2025-07-12 10:41:16 +00:00
// FIXME(const_trait_impl): how is this supposed to interact with `#[rustc_const_stable_indirect]`?
// Currently, once that is set, we do not inherit anything from the parent any more.
2025-07-16 22:34:00 +00:00
if inherit_const_stability(tcx, def_id) {
2025-07-12 10:41:16 +00:00
let parent = tcx.opt_local_parent(def_id)?;
let parent = tcx.lookup_const_stability(parent)?;
if parent.is_const_unstable() {
return Some(parent);
}
}
None
}
/// A private tree-walker for producing an `Index`.
2025-07-12 10:58:33 +00:00
struct Annotator<'tcx> {
2019-06-14 00:48:52 +03:00
tcx: TyCtxt<'tcx>,
2025-07-12 10:58:33 +00:00
implications: UnordMap<Symbol, Symbol>,
}
2025-07-12 10:58:33 +00:00
impl<'tcx> Annotator<'tcx> {
/// Determine the stability for a node based on its attributes and inherited stability. The
/// stability is recorded in the index and used as the parent. If the node is a function,
/// `fn_sig` is its signature.
2025-07-12 10:57:57 +00:00
#[instrument(level = "trace", skip(self))]
fn annotate(&mut self, def_id: LocalDefId) {
if !self.tcx.features().staged_api() {
return;
}
2025-07-12 10:57:57 +00:00
if let Some(stability) = self.tcx.lookup_stability(def_id)
&& let StabilityLevel::Unstable { implied_by: Some(implied_by), .. } = stability.level
2025-07-12 10:15:25 +00:00
{
2025-07-12 10:58:33 +00:00
self.implications.insert(implied_by, stability.feature);
}
2025-07-12 10:57:57 +00:00
if let Some(stability) = self.tcx.lookup_const_stability(def_id)
&& let StabilityLevel::Unstable { implied_by: Some(implied_by), .. } = stability.level
{
2025-07-12 10:58:33 +00:00
self.implications.insert(implied_by, stability.feature);
}
}
}
2025-07-12 10:58:33 +00:00
impl<'tcx> Visitor<'tcx> for Annotator<'tcx> {
/// Because stability levels are scoped lexically, we want to walk
/// nested items in the context of the outer item, so enable
/// deep-walking.
type NestedFilter = nested_filter::All;
2020-01-07 17:25:33 +01:00
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
}
2019-11-28 19:28:50 +01:00
fn visit_item(&mut self, i: &'tcx Item<'tcx>) {
2019-09-26 17:51:36 +01:00
match i.kind {
hir::ItemKind::Struct(_, _, ref sd) => {
if let Some(ctor_def_id) = sd.ctor_def_id() {
2025-07-12 10:57:57 +00:00
self.annotate(ctor_def_id);
2015-11-16 19:53:41 +03:00
}
}
_ => {}
}
2025-07-12 10:57:57 +00:00
self.annotate(i.owner_id.def_id);
intravisit::walk_item(self, i)
}
2019-11-28 21:47:10 +01:00
fn visit_trait_item(&mut self, ti: &'tcx hir::TraitItem<'tcx>) {
2025-07-12 10:57:57 +00:00
self.annotate(ti.owner_id.def_id);
intravisit::walk_trait_item(self, ti);
}
2019-11-28 22:16:44 +01:00
fn visit_impl_item(&mut self, ii: &'tcx hir::ImplItem<'tcx>) {
2025-07-12 10:57:57 +00:00
self.annotate(ii.owner_id.def_id);
intravisit::walk_impl_item(self, ii);
}
fn visit_variant(&mut self, var: &'tcx Variant<'tcx>) {
2025-07-12 10:57:57 +00:00
self.annotate(var.def_id);
if let Some(ctor_def_id) = var.data.ctor_def_id() {
self.annotate(ctor_def_id);
}
2025-07-12 10:57:57 +00:00
intravisit::walk_variant(self, var)
}
fn visit_field_def(&mut self, s: &'tcx FieldDef<'tcx>) {
2025-07-12 10:57:57 +00:00
self.annotate(s.def_id);
intravisit::walk_field_def(self, s);
}
2019-11-28 20:18:29 +01:00
fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) {
2025-07-12 10:57:57 +00:00
self.annotate(i.owner_id.def_id);
intravisit::walk_foreign_item(self, i);
2015-11-16 19:53:41 +03:00
}
fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
2025-07-12 10:57:57 +00:00
self.annotate(p.def_id);
intravisit::walk_generic_param(self, p);
}
}
struct MissingStabilityAnnotations<'tcx> {
2019-06-14 00:48:52 +03:00
tcx: TyCtxt<'tcx>,
effective_visibilities: &'tcx EffectiveVisibilities,
}
impl<'tcx> MissingStabilityAnnotations<'tcx> {
/// Verify that deprecation and stability attributes make sense with one another.
#[instrument(level = "trace", skip(self))]
fn check_compatible_stability(&self, def_id: LocalDefId) {
if !self.tcx.features().staged_api() {
return;
}
let depr = self.tcx.lookup_deprecation_entry(def_id);
let stab = self.tcx.lookup_stability(def_id);
let const_stab = self.tcx.lookup_const_stability(def_id);
macro_rules! find_attr_span {
($name:ident) => {{
let attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id));
attrs::find_attr!(attrs, AttributeKind::$name { span, .. } => *span)
}}
}
if stab.is_none()
&& depr.map_or(false, |d| d.attr.is_since_rustc_version())
&& let Some(span) = find_attr_span!(Deprecation)
{
self.tcx.dcx().emit_err(errors::DeprecatedAttribute { span });
}
if let Some(stab) = stab {
// Error if prohibited, or can't inherit anything from a container.
let kind = annotation_kind(self.tcx, def_id);
if kind == AnnotationKind::Prohibited
|| (kind == AnnotationKind::Container && stab.level.is_stable() && depr.is_some())
{
if let Some(span) = find_attr_span!(Stability) {
let item_sp = self.tcx.def_span(def_id);
self.tcx.dcx().emit_err(errors::UselessStability { span, item_sp });
}
}
// Check if deprecated_since < stable_since. If it is,
// this is *almost surely* an accident.
if let Some(depr) = depr
&& let DeprecatedSince::RustcVersion(dep_since) = depr.attr.since
&& let attrs::StabilityLevel::Stable { since: stab_since, .. } = stab.level
&& let Some(span) = find_attr_span!(Stability)
{
let item_sp = self.tcx.def_span(def_id);
match stab_since {
StableSince::Current => {
self.tcx
.dcx()
.emit_err(errors::CannotStabilizeDeprecated { span, item_sp });
}
StableSince::Version(stab_since) => {
if dep_since < stab_since {
self.tcx
.dcx()
.emit_err(errors::CannotStabilizeDeprecated { span, item_sp });
}
}
StableSince::Err(_) => {
// An error already reported. Assume the unparseable stabilization
// version is older than the deprecation version.
}
}
}
}
// If the current node is a function with const stability attributes (directly given or
// implied), check if the function/method is const or the parent impl block is const.
let fn_sig = self.tcx.hir_node_by_def_id(def_id).fn_sig();
if let Some(fn_sig) = fn_sig
&& !fn_sig.header.is_const()
&& const_stab.is_some()
&& find_attr_span!(ConstStability).is_some()
{
self.tcx.dcx().emit_err(errors::MissingConstErr { fn_sig_span: fn_sig.span });
}
// If this is marked const *stable*, it must also be regular-stable.
if let Some(const_stab) = const_stab
&& let Some(fn_sig) = fn_sig
&& const_stab.is_const_stable()
&& !stab.is_some_and(|s| s.is_stable())
&& let Some(const_span) = find_attr_span!(ConstStability)
{
self.tcx
.dcx()
.emit_err(errors::ConstStableNotStable { fn_sig_span: fn_sig.span, const_span });
}
if let Some(stab) = &const_stab
&& stab.is_const_stable()
&& stab.const_stable_indirect
&& let Some(span) = find_attr_span!(ConstStability)
{
self.tcx.dcx().emit_err(errors::RustcConstStableIndirectPairing { span });
}
}
2025-07-12 10:41:16 +00:00
#[instrument(level = "debug", skip(self))]
fn check_missing_stability(&self, def_id: LocalDefId) {
2025-07-12 10:15:25 +00:00
let stab = self.tcx.lookup_stability(def_id);
2025-07-12 10:41:16 +00:00
self.tcx.ensure_ok().lookup_const_stability(def_id);
if !self.tcx.sess.is_test_crate()
&& stab.is_none()
&& self.effective_visibilities.is_reachable(def_id)
{
let descr = self.tcx.def_descr(def_id.to_def_id());
let span = self.tcx.def_span(def_id);
self.tcx.dcx().emit_err(errors::MissingStabilityAttr { span, descr });
}
}
fn check_missing_const_stability(&self, def_id: LocalDefId) {
let is_const = self.tcx.is_const_fn(def_id.to_def_id())
|| (self.tcx.def_kind(def_id.to_def_id()) == DefKind::Trait
&& self.tcx.is_const_trait(def_id.to_def_id()));
// Reachable const fn/trait must have a stability attribute.
if is_const
&& self.effective_visibilities.is_reachable(def_id)
&& self.tcx.lookup_const_stability(def_id).is_none()
{
let span = self.tcx.def_span(def_id);
let descr = self.tcx.def_descr(def_id.to_def_id());
self.tcx.dcx().emit_err(errors::MissingConstStabAttr { span, descr });
}
}
}
impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
2020-01-07 17:25:33 +01:00
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
2016-11-28 18:10:37 +01:00
}
2019-11-28 19:28:50 +01:00
fn visit_item(&mut self, i: &'tcx Item<'tcx>) {
self.check_compatible_stability(i.owner_id.def_id);
// Inherent impls and foreign modules serve only as containers for other items,
// they don't have their own stability. They still can be annotated as unstable
// and propagate this instability to children, but this annotation is completely
// optional. They inherit stability from their parents when unannotated.
if !matches!(
i.kind,
hir::ItemKind::Impl(hir::Impl { of_trait: None, .. })
| hir::ItemKind::ForeignMod { .. }
) {
self.check_missing_stability(i.owner_id.def_id);
}
// Ensure stable `const fn` have a const stability attribute.
self.check_missing_const_stability(i.owner_id.def_id);
intravisit::walk_item(self, i)
}
2019-11-28 21:47:10 +01:00
fn visit_trait_item(&mut self, ti: &'tcx hir::TraitItem<'tcx>) {
self.check_compatible_stability(ti.owner_id.def_id);
self.check_missing_stability(ti.owner_id.def_id);
intravisit::walk_trait_item(self, ti);
}
2019-11-28 22:16:44 +01:00
fn visit_impl_item(&mut self, ii: &'tcx hir::ImplItem<'tcx>) {
self.check_compatible_stability(ii.owner_id.def_id);
let impl_def_id = self.tcx.hir_get_parent_item(ii.hir_id());
if self.tcx.impl_trait_ref(impl_def_id).is_none() {
self.check_missing_stability(ii.owner_id.def_id);
self.check_missing_const_stability(ii.owner_id.def_id);
}
intravisit::walk_impl_item(self, ii);
}
fn visit_variant(&mut self, var: &'tcx Variant<'tcx>) {
self.check_compatible_stability(var.def_id);
self.check_missing_stability(var.def_id);
if let Some(ctor_def_id) = var.data.ctor_def_id() {
self.check_missing_stability(ctor_def_id);
2022-08-11 22:25:16 +00:00
}
intravisit::walk_variant(self, var);
}
fn visit_field_def(&mut self, s: &'tcx FieldDef<'tcx>) {
self.check_compatible_stability(s.def_id);
self.check_missing_stability(s.def_id);
intravisit::walk_field_def(self, s);
}
2019-11-28 20:18:29 +01:00
fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) {
self.check_compatible_stability(i.owner_id.def_id);
self.check_missing_stability(i.owner_id.def_id);
intravisit::walk_foreign_item(self, i);
}
fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
self.check_compatible_stability(p.def_id);
// Note that we don't need to `check_missing_stability` for default generic parameters,
// as we assume that any default generic parameters without attributes are automatically
// stable (assuming they have not inherited instability from their parent).
intravisit::walk_generic_param(self, p);
}
}
2025-07-12 10:58:33 +00:00
fn stability_implications(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> UnordMap<Symbol, Symbol> {
let mut annotator = Annotator { tcx, implications: Default::default() };
2025-07-12 10:57:57 +00:00
annotator.annotate(CRATE_DEF_ID);
tcx.hir_walk_toplevel_module(&mut annotator);
2025-07-12 10:58:33 +00:00
annotator.implications
}
/// Cross-references the feature names of unstable APIs with enabled
/// features and possibly prints errors.
2023-04-26 20:53:51 +02:00
fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
tcx.hir_visit_item_likes_in_module(module_def_id, &mut Checker { tcx });
let is_staged_api =
tcx.sess.opts.unstable_opts.force_unstable_if_unmarked || tcx.features().staged_api();
if is_staged_api {
let effective_visibilities = &tcx.effective_visibilities(());
let mut missing = MissingStabilityAnnotations { tcx, effective_visibilities };
if module_def_id.is_top_level_module() {
missing.check_missing_stability(CRATE_DEF_ID);
}
tcx.hir_visit_item_likes_in_module(module_def_id, &mut missing);
}
if module_def_id.is_top_level_module() {
check_unused_or_stable_features(tcx)
}
2018-06-06 22:13:52 +02:00
}
pub(crate) fn provide(providers: &mut Providers) {
2022-01-16 22:26:46 +01:00
*providers = Providers {
check_mod_unstable_api_usage,
2025-07-12 10:58:33 +00:00
stability_implications,
2025-07-12 10:15:25 +00:00
lookup_stability,
2025-07-12 10:41:16 +00:00
lookup_const_stability,
2023-01-30 20:47:11 +00:00
lookup_default_body_stability,
2022-01-18 21:01:07 +01:00
lookup_deprecation_entry,
2022-01-16 22:26:46 +01:00
..*providers
};
}
struct Checker<'tcx> {
2019-06-14 00:48:52 +03:00
tcx: TyCtxt<'tcx>,
}
impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
2020-01-07 17:25:33 +01:00
/// Because stability levels are scoped lexically, we want to walk
/// nested items in the context of the outer item, so enable
/// deep-walking.
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
2016-10-28 22:58:32 +02:00
}
2019-11-28 19:28:50 +01:00
fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
2019-09-26 17:51:36 +01:00
match item.kind {
Move `hir::Item::ident` into `hir::ItemKind`. `hir::Item` has an `ident` field. - It's always non-empty for these item kinds: `ExternCrate`, `Static`, `Const`, `Fn`, `Macro`, `Mod`, `TyAlias`, `Enum`, `Struct`, `Union`, Trait`, TraitAalis`. - It's always empty for these item kinds: `ForeignMod`, `GlobalAsm`, `Impl`. - For `Use`, it is non-empty for `UseKind::Single` and empty for `UseKind::{Glob,ListStem}`. All of this is quite non-obvious; the only documentation is a single comment saying "The name might be a dummy name in case of anonymous items". Some sites that handle items check for an empty ident, some don't. This is a very C-like way of doing things, but this is Rust, we have sum types, we can do this properly and never forget to check for the exceptional case and never YOLO possibly empty identifiers (or possibly dummy spans) around and hope that things will work out. The commit is large but it's mostly obvious plumbing work. Some notable things. - A similar transformation makes sense for `ast::Item`, but this is already a big change. That can be done later. - Lots of assertions are added to item lowering to ensure that identifiers are empty/non-empty as expected. These will be removable when `ast::Item` is done later. - `ItemKind::Use` doesn't get an `Ident`, but `UseKind::Single` does. - `lower_use_tree` is significantly simpler. No more confusing `&mut Ident` to deal with. - `ItemKind::ident` is a new method, it returns an `Option<Ident>`. It's used with `unwrap` in a few places; sometimes it's hard to tell exactly which item kinds might occur. None of these unwraps fail on the test suite. It's conceivable that some might fail on alternative input. We can deal with those if/when they happen. - In `trait_path` the `find_map`/`if let` is replaced with a loop, and things end up much clearer that way. - `named_span` no longer checks for an empty name; instead the call site now checks for a missing identifier if necessary. - `maybe_inline_local` doesn't need the `glob` argument, it can be computed in-function from the `renamed` argument. - `arbitrary_source_item_ordering::check_mod` had a big `if` statement that was just getting the ident from the item kinds that had one. It could be mostly replaced by a single call to the new `ItemKind::ident` method. - `ItemKind` grows from 56 to 64 bytes, but `Item` stays the same size, and that's what matters, because `ItemKind` only occurs within `Item`.
2025-03-06 19:07:36 +11:00
hir::ItemKind::ExternCrate(_, ident) => {
// compiler-generated `extern crate` items have a dummy span.
2020-05-31 18:09:25 -07:00
// `std` is still checked for the `restricted-std` feature.
Move `hir::Item::ident` into `hir::ItemKind`. `hir::Item` has an `ident` field. - It's always non-empty for these item kinds: `ExternCrate`, `Static`, `Const`, `Fn`, `Macro`, `Mod`, `TyAlias`, `Enum`, `Struct`, `Union`, Trait`, TraitAalis`. - It's always empty for these item kinds: `ForeignMod`, `GlobalAsm`, `Impl`. - For `Use`, it is non-empty for `UseKind::Single` and empty for `UseKind::{Glob,ListStem}`. All of this is quite non-obvious; the only documentation is a single comment saying "The name might be a dummy name in case of anonymous items". Some sites that handle items check for an empty ident, some don't. This is a very C-like way of doing things, but this is Rust, we have sum types, we can do this properly and never forget to check for the exceptional case and never YOLO possibly empty identifiers (or possibly dummy spans) around and hope that things will work out. The commit is large but it's mostly obvious plumbing work. Some notable things. - A similar transformation makes sense for `ast::Item`, but this is already a big change. That can be done later. - Lots of assertions are added to item lowering to ensure that identifiers are empty/non-empty as expected. These will be removable when `ast::Item` is done later. - `ItemKind::Use` doesn't get an `Ident`, but `UseKind::Single` does. - `lower_use_tree` is significantly simpler. No more confusing `&mut Ident` to deal with. - `ItemKind::ident` is a new method, it returns an `Option<Ident>`. It's used with `unwrap` in a few places; sometimes it's hard to tell exactly which item kinds might occur. None of these unwraps fail on the test suite. It's conceivable that some might fail on alternative input. We can deal with those if/when they happen. - In `trait_path` the `find_map`/`if let` is replaced with a loop, and things end up much clearer that way. - `named_span` no longer checks for an empty name; instead the call site now checks for a missing identifier if necessary. - `maybe_inline_local` doesn't need the `glob` argument, it can be computed in-function from the `renamed` argument. - `arbitrary_source_item_ordering::check_mod` had a big `if` statement that was just getting the ident from the item kinds that had one. It could be mostly replaced by a single call to the new `ItemKind::ident` method. - `ItemKind` grows from 56 to 64 bytes, but `Item` stays the same size, and that's what matters, because `ItemKind` only occurs within `Item`.
2025-03-06 19:07:36 +11:00
if item.span.is_dummy() && ident.name != sym::std {
2018-06-25 01:00:21 +03:00
return;
}
let Some(cnum) = self.tcx.extern_mod_stmt_cnum(item.owner_id.def_id) else {
2022-02-19 00:48:49 +01:00
return;
};
let def_id = cnum.as_def_id();
2021-05-07 10:41:04 +08:00
self.tcx.check_stability(def_id, Some(item.hir_id()), item.span, None);
}
// For implementations of traits, check the stability of each item
// individually as it's possible to have a stable trait with unstable
// items.
hir::ItemKind::Impl(hir::Impl {
2025-02-20 18:28:48 +00:00
of_trait: Some(t), self_ty, items, constness, ..
}) => {
let features = self.tcx.features();
if features.staged_api() {
let attrs = self.tcx.hir_attrs(item.hir_id());
let stab = attrs::find_attr!(attrs, AttributeKind::Stability{stability, span} => (*stability, *span));
// FIXME(jdonszelmann): make it impossible to miss the or_else in the typesystem
let const_stab = attrs::find_attr!(attrs, AttributeKind::ConstStability{stability, ..} => *stability);
let unstable_feature_stab =
find_attr!(attrs, AttributeKind::UnstableFeatureBound(i) => i)
.map(|i| i.as_slice())
.unwrap_or_default();
// If this impl block has an #[unstable] attribute, give an
// error if all involved types and traits are stable, because
// it will have no effect.
// See: https://github.com/rust-lang/rust/issues/55436
//
// The exception is when there are both #[unstable_feature_bound(..)] and
// #![unstable(feature = "..", issue = "..")] that have the same symbol because
// that can effectively mark an impl as unstable.
//
// For example:
// ```
// #[unstable_feature_bound(feat_foo)]
// #[unstable(feature = "feat_foo", issue = "none")]
// impl Foo for Bar {}
// ```
2024-12-07 15:27:17 +01:00
if let Some((
Stability { level: attrs::StabilityLevel::Unstable { .. }, feature },
2024-12-07 15:27:17 +01:00
span,
)) = stab
{
let mut c = CheckTraitImplStable { tcx: self.tcx, fully_stable: true };
2025-01-18 22:45:41 +00:00
c.visit_ty_unambig(self_ty);
c.visit_trait_ref(t);
// Skip the lint if the impl is marked as unstable using
// #[unstable_feature_bound(..)]
let mut unstable_feature_bound_in_effect = false;
for (unstable_bound_feat_name, _) in unstable_feature_stab {
if *unstable_bound_feat_name == feature {
unstable_feature_bound_in_effect = true;
}
}
// do not lint when the trait isn't resolved, since resolution error should
// be fixed first
if t.path.res != Res::Err
&& c.fully_stable
&& !unstable_feature_bound_in_effect
{
self.tcx.emit_node_span_lint(
INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
item.hir_id(),
span,
errors::IneffectiveUnstableImpl,
);
}
}
if features.const_trait_impl()
&& let hir::Constness::Const = constness
{
let stable_or_implied_stable = match const_stab {
None => true,
Some(stab) if stab.is_const_stable() => {
// `#![feature(const_trait_impl)]` is unstable, so any impl declared stable
// needs to have an error emitted.
// Note: Remove this error once `const_trait_impl` is stabilized
self.tcx
.dcx()
.emit_err(errors::TraitImplConstStable { span: item.span });
true
}
Some(_) => false,
};
if let Some(trait_id) = t.trait_def_id()
&& let Some(const_stab) = self.tcx.lookup_const_stability(trait_id)
{
// the const stability of a trait impl must match the const stability on the trait.
if const_stab.is_const_stable() != stable_or_implied_stable {
let trait_span = self.tcx.def_ident_span(trait_id).unwrap();
let impl_stability = if stable_or_implied_stable {
errors::ImplConstStability::Stable { span: item.span }
} else {
errors::ImplConstStability::Unstable { span: item.span }
};
let trait_stability = if const_stab.is_const_stable() {
errors::TraitConstStability::Stable { span: trait_span }
} else {
errors::TraitConstStability::Unstable { span: trait_span }
};
self.tcx.dcx().emit_err(errors::TraitImplConstStabilityMismatch {
span: item.span,
impl_stability,
trait_stability,
});
}
}
}
}
if let hir::Constness::Const = constness
&& let Some(def_id) = t.trait_def_id()
{
// FIXME(const_trait_impl): Improve the span here.
self.tcx.check_const_stability(def_id, t.path.span, t.path.span);
}
2022-02-05 15:26:49 +01:00
for impl_item_ref in *items {
2025-07-02 20:15:28 +00:00
let impl_item = self.tcx.associated_item(impl_item_ref.owner_id);
if let Some(def_id) = impl_item.trait_item_def_id {
// Pass `None` to skip deprecation warnings.
2025-07-02 20:15:28 +00:00
self.tcx.check_stability(
def_id,
None,
self.tcx.def_span(impl_item_ref.owner_id),
None,
);
}
}
}
_ => (/* pass */),
}
intravisit::walk_item(self, item);
}
fn visit_poly_trait_ref(&mut self, t: &'tcx hir::PolyTraitRef<'tcx>) {
match t.modifiers.constness {
hir::BoundConstness::Always(span) | hir::BoundConstness::Maybe(span) => {
if let Some(def_id) = t.trait_ref.trait_def_id() {
self.tcx.check_const_stability(def_id, t.trait_ref.path.span, span);
}
}
hir::BoundConstness::Never => {}
}
intravisit::walk_poly_trait_ref(self, t);
}
fn visit_path(&mut self, path: &hir::Path<'tcx>, id: hir::HirId) {
if let Some(def_id) = path.res.opt_def_id() {
2021-06-15 19:35:03 +08:00
let method_span = path.segments.last().map(|s| s.ident.span);
let item_is_allowed = self.tcx.check_stability_allow_unstable(
def_id,
Some(id),
path.span,
method_span,
if is_unstable_reexport(self.tcx, id) {
AllowUnstable::Yes
} else {
AllowUnstable::No
},
);
if item_is_allowed {
// The item itself is allowed; check whether the path there is also allowed.
let is_allowed_through_unstable_modules: Option<Symbol> =
self.tcx.lookup_stability(def_id).and_then(|stab| match stab.level {
StabilityLevel::Stable { allowed_through_unstable_modules, .. } => {
allowed_through_unstable_modules
}
_ => None,
});
// Check parent modules stability as well if the item the path refers to is itself
// stable. We only emit errors for unstable path segments if the item is stable
// or allowed because stability is often inherited, so the most common case is that
// both the segments and the item are unstable behind the same feature flag.
//
// We check here rather than in `visit_path_segment` to prevent visiting the last
// path segment twice
//
// We include special cases via #[rustc_allowed_through_unstable_modules] for items
// that were accidentally stabilized through unstable paths before this check was
// added, such as `core::intrinsics::transmute`
let parents = path.segments.iter().rev().skip(1);
for path_segment in parents {
if let Some(def_id) = path_segment.res.opt_def_id() {
match is_allowed_through_unstable_modules {
None => {
// Emit a hard stability error if this path is not stable.
// use `None` for id to prevent deprecation check
self.tcx.check_stability_allow_unstable(
def_id,
None,
path.span,
None,
if is_unstable_reexport(self.tcx, id) {
AllowUnstable::Yes
} else {
AllowUnstable::No
},
);
}
Some(deprecation) => {
// Call the stability check directly so that we can control which
// diagnostic is emitted.
let eval_result = self.tcx.eval_stability_allow_unstable(
def_id,
None,
path.span,
None,
if is_unstable_reexport(self.tcx, id) {
AllowUnstable::Yes
} else {
AllowUnstable::No
},
);
let is_allowed = matches!(eval_result, EvalResult::Allow);
if !is_allowed {
// Calculating message for lint involves calling `self.def_path_str`,
// which will by default invoke the expensive `visible_parent_map` query.
// Skip all that work if the lint is allowed anyway.
2025-03-19 09:41:38 +00:00
if self.tcx.lint_level_at_node(DEPRECATED, id).level
== lint::Level::Allow
{
return;
}
// Show a deprecation message.
let def_path =
with_no_trimmed_paths!(self.tcx.def_path_str(def_id));
let def_kind = self.tcx.def_descr(def_id);
let diag = Deprecated {
sub: None,
kind: def_kind.to_owned(),
path: def_path,
note: Some(deprecation),
since_kind: lint::DeprecatedSinceKind::InEffect,
};
self.tcx.emit_node_span_lint(
DEPRECATED,
id,
method_span.unwrap_or(path.span),
diag,
);
}
}
}
}
}
}
}
intravisit::walk_path(self, path)
}
}
/// Check whether a path is a `use` item that has been marked as unstable.
///
/// See issue #94972 for details on why this is a special case
2022-12-20 22:10:40 +01:00
fn is_unstable_reexport(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
// Get the LocalDefId so we can lookup the item to check the kind.
let Some(owner) = id.as_owner() else {
return false;
};
let def_id = owner.def_id;
2025-07-12 10:15:25 +00:00
let Some(stab) = tcx.lookup_stability(def_id) else {
return false;
};
if stab.level.is_stable() {
// The re-export is not marked as unstable, don't override
return false;
}
// If this is a path that isn't a use, we don't need to do anything special
if !matches!(tcx.hir_expect_item(def_id).kind, ItemKind::Use(..)) {
return false;
}
true
}
struct CheckTraitImplStable<'tcx> {
tcx: TyCtxt<'tcx>,
fully_stable: bool,
}
impl<'tcx> Visitor<'tcx> for CheckTraitImplStable<'tcx> {
fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: hir::HirId) {
if let Some(def_id) = path.res.opt_def_id() {
if let Some(stab) = self.tcx.lookup_stability(def_id) {
self.fully_stable &= stab.level.is_stable();
}
}
intravisit::walk_path(self, path)
}
fn visit_trait_ref(&mut self, t: &'tcx TraitRef<'tcx>) {
if let Res::Def(DefKind::Trait, trait_did) = t.path.res {
if let Some(stab) = self.tcx.lookup_stability(trait_did) {
self.fully_stable &= stab.level.is_stable();
}
}
intravisit::walk_trait_ref(self, t)
}
fn visit_ty(&mut self, t: &'tcx Ty<'tcx, AmbigArg>) {
if let TyKind::Never = t.kind {
self.fully_stable = false;
}
if let TyKind::FnPtr(function) = t.kind {
if extern_abi_stability(function.abi).is_err() {
self.fully_stable = false;
}
}
intravisit::walk_ty(self, t)
}
fn visit_fn_decl(&mut self, fd: &'tcx hir::FnDecl<'tcx>) {
for ty in fd.inputs {
2025-01-18 22:45:41 +00:00
self.visit_ty_unambig(ty)
}
if let hir::FnRetTy::Return(output_ty) = fd.output {
match output_ty.kind {
TyKind::Never => {} // `-> !` is stable
2025-01-18 22:45:41 +00:00
_ => self.visit_ty_unambig(output_ty),
}
}
}
}
/// 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.
// This is `pub` for rustdoc. rustc should call it through `check_mod_unstable_api_usage`.
2019-06-21 23:49:03 +02:00
pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
let _prof_timer = tcx.sess.timer("unused_lib_feature_checking");
let enabled_lang_features = tcx.features().enabled_lang_features();
let mut lang_features = UnordSet::default();
for EnabledLangFeature { gate_name, attr_sp, stable_since } in enabled_lang_features {
if let Some(version) = stable_since {
2018-07-23 02:03:01 +01:00
// Warn if the user has enabled an already-stable lang feature.
unnecessary_stable_feature_lint(tcx, *attr_sp, *gate_name, *version);
2018-07-23 02:03:01 +01:00
}
if !lang_features.insert(gate_name) {
2018-07-23 02:03:01 +01:00
// Warn if the user enables a lang feature multiple times.
tcx.dcx().emit_err(errors::DuplicateFeatureErr { span: *attr_sp, feature: *gate_name });
2018-07-23 02:03:01 +01:00
}
}
let enabled_lib_features = tcx.features().enabled_lib_features();
let mut remaining_lib_features = FxIndexMap::default();
for EnabledLibFeature { gate_name, attr_sp } in enabled_lib_features {
if remaining_lib_features.contains_key(gate_name) {
// Warn if the user enables a lib feature multiple times.
tcx.dcx().emit_err(errors::DuplicateFeatureErr { span: *attr_sp, feature: *gate_name });
}
remaining_lib_features.insert(*gate_name, *attr_sp);
}
2018-07-24 00:56:00 +01:00
// `stdbuild` has special handling for `libc`, so we need to
// recognise the feature when building std.
2018-08-06 16:46:08 +01:00
// Likewise, libtest is handled specially, so `test` isn't
// available as we'd like it to be.
// FIXME: only remove `libc` when `stdbuild` is enabled.
2018-08-06 16:46:08 +01:00
// FIXME: remove special casing for `test`.
// FIXME(#120456) - is `swap_remove` correct?
remaining_lib_features.swap_remove(&sym::libc);
remaining_lib_features.swap_remove(&sym::test);
/// For each feature in `defined_features`..
///
/// - If it is in `remaining_lib_features` (those features with `#![feature(..)]` attributes in
/// the current crate), check if it is stable (or partially stable) and thus an unnecessary
/// attribute.
/// - If it is in `remaining_implications` (a feature that is referenced by an `implied_by`
/// from the current crate), then remove it from the remaining implications.
///
/// Once this function has been invoked for every feature (local crate and all extern crates),
/// then..
///
/// - If features remain in `remaining_lib_features`, then the user has enabled a feature that
/// does not exist.
/// - If features remain in `remaining_implications`, the `implied_by` refers to a feature that
/// does not exist.
///
/// By structuring the code in this way: checking the features defined from each crate one at a
/// time, less loading from metadata is performed and thus compiler performance is improved.
fn check_features<'tcx>(
tcx: TyCtxt<'tcx>,
remaining_lib_features: &mut FxIndexMap<Symbol, Span>,
remaining_implications: &mut UnordMap<Symbol, Symbol>,
defined_features: &LibFeatures,
all_implications: &UnordMap<Symbol, Symbol>,
) {
for (feature, stability) in defined_features.to_sorted_vec() {
if let FeatureStability::AcceptedSince(since) = stability
&& let Some(span) = remaining_lib_features.get(&feature)
{
// Warn if the user has enabled an already-stable lib feature.
if let Some(implies) = all_implications.get(&feature) {
unnecessary_partially_stable_feature_lint(tcx, *span, feature, *implies, since);
} else {
unnecessary_stable_feature_lint(tcx, *span, feature, since);
}
}
// FIXME(#120456) - is `swap_remove` correct?
remaining_lib_features.swap_remove(&feature);
// `feature` is the feature doing the implying, but `implied_by` is the feature with
// the attribute that establishes this relationship. `implied_by` is guaranteed to be a
// feature defined in the local crate because `remaining_implications` is only the
// implications from this crate.
remaining_implications.remove(&feature);
if let FeatureStability::Unstable { old_name: Some(alias) } = stability {
if let Some(span) = remaining_lib_features.swap_remove(&alias) {
tcx.dcx().emit_err(errors::RenamedFeature { span, feature, alias });
}
}
if remaining_lib_features.is_empty() && remaining_implications.is_empty() {
2019-12-22 17:42:04 -05:00
break;
}
}
}
// All local crate implications need to have the feature that implies it confirmed to exist.
2023-11-17 23:40:04 +00:00
let mut remaining_implications = tcx.stability_implications(LOCAL_CRATE).clone();
// We always collect the lib features enabled in the current crate, even if there are
// no unknown features, because the collection also does feature attribute validation.
2023-11-17 23:40:04 +00:00
let local_defined_features = tcx.lib_features(LOCAL_CRATE);
if !remaining_lib_features.is_empty() || !remaining_implications.is_empty() {
// Loading the implications of all crates is unavoidable to be able to emit the partial
// stabilization diagnostic, but it can be avoided when there are no
// `remaining_lib_features`.
let mut all_implications = remaining_implications.clone();
for &cnum in tcx.crates(()) {
all_implications
.extend_unord(tcx.stability_implications(cnum).items().map(|(k, v)| (*k, *v)));
}
check_features(
tcx,
&mut remaining_lib_features,
&mut remaining_implications,
local_defined_features,
&all_implications,
);
for &cnum in tcx.crates(()) {
if remaining_lib_features.is_empty() && remaining_implications.is_empty() {
break;
}
check_features(
tcx,
&mut remaining_lib_features,
&mut remaining_implications,
tcx.lib_features(cnum),
&all_implications,
);
}
}
for (feature, span) in remaining_lib_features {
tcx.dcx().emit_err(errors::UnknownFeature { span, feature });
}
for (&implied_by, &feature) in remaining_implications.to_sorted_stable_ord() {
2023-11-17 23:40:04 +00:00
let local_defined_features = tcx.lib_features(LOCAL_CRATE);
let span = local_defined_features
.stability
.get(&feature)
2023-11-17 23:40:04 +00:00
.expect("feature that implied another does not exist")
.1;
tcx.dcx().emit_err(errors::ImpliedFeatureNotExist { span, feature, implied_by });
}
// FIXME(#44232): the `used_features` table no longer exists, so we
2020-03-06 12:13:55 +01:00
// don't lint about unused features. We should re-enable this one day!
}
2015-01-16 10:25:16 -08:00
fn unnecessary_partially_stable_feature_lint(
tcx: TyCtxt<'_>,
span: Span,
feature: Symbol,
implies: Symbol,
since: Symbol,
) {
tcx.emit_node_span_lint(
2022-09-16 11:01:02 +04:00
lint::builtin::STABLE_FEATURES,
hir::CRATE_HIR_ID,
span,
errors::UnnecessaryPartialStableFeature {
span,
line: tcx.sess.source_map().span_extend_to_line(span),
feature,
since,
implies,
2022-09-16 11:01:02 +04:00
},
);
}
fn unnecessary_stable_feature_lint(
tcx: TyCtxt<'_>,
span: Span,
feature: Symbol,
mut since: Symbol,
) {
if since.as_str() == VERSION_PLACEHOLDER {
since = sym::env_CFG_RELEASE;
}
tcx.emit_node_span_lint(
lint::builtin::STABLE_FEATURES,
hir::CRATE_HIR_ID,
span,
errors::UnnecessaryStableFeature { feature, since },
);
}