diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 8658cea137a7d..7ad0e799f4464 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -13,17 +13,16 @@ use rustc_ast::NodeId;
 use rustc_ast::{self as ast, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem};
 use rustc_attr as attr;
 use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
-use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::fx::FxHashSet;
 use rustc_feature::{Feature, Features, State as FeatureState};
-use rustc_feature::{
-    ACCEPTED_FEATURES, ACTIVE_FEATURES, REMOVED_FEATURES, STABLE_REMOVED_FEATURES,
-};
+use rustc_feature::{ACCEPTED_FEATURES, ACTIVE_FEATURES, REMOVED_FEATURES};
 use rustc_parse::validate_attr;
 use rustc_session::parse::feature_err;
 use rustc_session::Session;
 use rustc_span::edition::{Edition, ALL_EDITIONS};
 use rustc_span::symbol::{sym, Symbol};
-use rustc_span::{Span, DUMMY_SP};
+use rustc_span::Span;
+use thin_vec::ThinVec;
 
 /// A folder that strips out items that do not belong in the current configuration.
 pub struct StripUnconfigured<'a> {
@@ -37,13 +36,6 @@ pub struct StripUnconfigured<'a> {
 }
 
 pub fn features(sess: &Session, krate_attrs: &[Attribute]) -> Features {
-    fn feature_removed(sess: &Session, span: Span, reason: Option<&str>) {
-        sess.emit_err(FeatureRemoved {
-            span,
-            reason: reason.map(|reason| FeatureRemovedReason { reason }),
-        });
-    }
-
     fn active_features_up_to(edition: Edition) -> impl Iterator<Item = &'static Feature> {
         ACTIVE_FEATURES.iter().filter(move |feature| {
             if let Some(feature_edition) = feature.edition {
@@ -54,67 +46,49 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute]) -> Features {
         })
     }
 
-    let mut features = Features::default();
-    let mut edition_enabled_features = FxHashMap::default();
-    let crate_edition = sess.edition();
-
-    for &edition in ALL_EDITIONS {
-        if edition <= crate_edition {
-            // The `crate_edition` implies its respective umbrella feature-gate
-            // (i.e., `#![feature(rust_20XX_preview)]` isn't needed on edition 20XX).
-            edition_enabled_features.insert(edition.feature_name(), edition);
+    fn feature_list(attr: &Attribute) -> ThinVec<ast::NestedMetaItem> {
+        if attr.has_name(sym::feature) && let Some(list) = attr.meta_item_list() {
+            list
+        } else {
+            ThinVec::new()
         }
     }
 
-    for feature in active_features_up_to(crate_edition) {
-        feature.set(&mut features, DUMMY_SP);
-        edition_enabled_features.insert(feature.name, crate_edition);
-    }
-
-    // Process the edition umbrella feature-gates first, to ensure
-    // `edition_enabled_features` is completed before it's queried.
-    for attr in krate_attrs {
-        if !attr.has_name(sym::feature) {
-            continue;
-        }
-
-        let Some(list) = attr.meta_item_list() else {
-            continue;
-        };
-
-        for mi in list {
-            if !mi.is_word() {
-                continue;
-            }
-
-            let name = mi.name_or_empty();
+    let mut features = Features::default();
 
-            let edition = ALL_EDITIONS.iter().find(|e| name == e.feature_name()).copied();
-            if let Some(edition) = edition {
-                if edition <= crate_edition {
-                    continue;
-                }
+    // The edition from `--edition`.
+    let crate_edition = sess.edition();
 
-                for feature in active_features_up_to(edition) {
-                    // FIXME(Manishearth) there is currently no way to set
-                    // lib features by edition
-                    feature.set(&mut features, DUMMY_SP);
-                    edition_enabled_features.insert(feature.name, edition);
+    // The maximum of (a) the edition from `--edition` and (b) any edition
+    // umbrella feature-gates declared in the code.
+    // - E.g. if `crate_edition` is 2015 but `rust_2018_preview` is present,
+    //   `feature_edition` is 2018
+    let mut features_edition = crate_edition;
+    for attr in krate_attrs {
+        for mi in feature_list(attr) {
+            if mi.is_word() {
+                let name = mi.name_or_empty();
+                let edition = ALL_EDITIONS.iter().find(|e| name == e.feature_name()).copied();
+                if let Some(edition) = edition && edition > features_edition {
+                    features_edition = edition;
                 }
             }
         }
     }
 
-    for attr in krate_attrs {
-        if !attr.has_name(sym::feature) {
-            continue;
-        }
-
-        let Some(list) = attr.meta_item_list() else {
-            continue;
-        };
+    // Enable edition-dependent features based on `features_edition`.
+    // - E.g. enable `test_2018_feature` if `features_edition` is 2018 or higher
+    let mut edition_enabled_features = FxHashSet::default();
+    for feature in active_features_up_to(features_edition) {
+        // FIXME(Manishearth) there is currently no way to set lib features by
+        // edition.
+        edition_enabled_features.insert(feature.name);
+        feature.set(&mut features);
+    }
 
-        for mi in list {
+    // Process all features declared in the code.
+    for attr in krate_attrs {
+        for mi in feature_list(attr) {
             let name = match mi.ident() {
                 Some(ident) if mi.is_word() => ident.name,
                 Some(ident) => {
@@ -136,38 +110,59 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute]) -> Features {
                 }
             };
 
-            if let Some(&edition) = edition_enabled_features.get(&name) {
+            // If the declared feature is an edition umbrella feature-gate,
+            // warn if it was redundant w.r.t. `crate_edition`.
+            // - E.g. warn if `rust_2018_preview` is declared when
+            //   `crate_edition` is 2018
+            // - E.g. don't warn if `rust_2018_preview` is declared when
+            //   `crate_edition` is 2015.
+            if let Some(&edition) = ALL_EDITIONS.iter().find(|e| name == e.feature_name()) {
+                if edition <= crate_edition {
+                    sess.emit_warning(FeatureIncludedInEdition {
+                        span: mi.span(),
+                        feature: name,
+                        edition,
+                    });
+                }
+                features.set_declared_lang_feature(name, mi.span(), None);
+                continue;
+            }
+
+            // If the declared feature is edition-dependent and was already
+            // enabled due to `feature_edition`, give a warning.
+            // - E.g. warn if `test_2018_feature` is declared when
+            //   `feature_edition` is 2018 or higher.
+            if edition_enabled_features.contains(&name) {
                 sess.emit_warning(FeatureIncludedInEdition {
                     span: mi.span(),
                     feature: name,
-                    edition,
+                    edition: features_edition,
                 });
+                features.set_declared_lang_feature(name, mi.span(), None);
                 continue;
             }
 
-            if ALL_EDITIONS.iter().any(|e| name == e.feature_name()) {
-                // Handled in the separate loop above.
-                continue;
-            }
-
-            let removed = REMOVED_FEATURES.iter().find(|f| name == f.name);
-            let stable_removed = STABLE_REMOVED_FEATURES.iter().find(|f| name == f.name);
-            if let Some(Feature { state, .. }) = removed.or(stable_removed) {
-                if let FeatureState::Removed { reason } | FeatureState::Stabilized { reason } =
-                    state
-                {
-                    feature_removed(sess, mi.span(), *reason);
+            // If the declared feature has been removed, issue an error.
+            if let Some(Feature { state, .. }) = REMOVED_FEATURES.iter().find(|f| name == f.name) {
+                if let FeatureState::Removed { reason } = state {
+                    sess.emit_err(FeatureRemoved {
+                        span: mi.span(),
+                        reason: reason.map(|reason| FeatureRemovedReason { reason }),
+                    });
                     continue;
                 }
             }
 
+            // If the declared feature is stable, record it.
             if let Some(Feature { since, .. }) = ACCEPTED_FEATURES.iter().find(|f| name == f.name) {
                 let since = Some(Symbol::intern(since));
-                features.declared_lang_features.push((name, mi.span(), since));
-                features.active_features.insert(name);
+                features.set_declared_lang_feature(name, mi.span(), since);
                 continue;
             }
 
+            // If `-Z allow-features` is used and the declared feature is
+            // unstable and not also listed as one of the allowed features,
+            // issue an error.
             if let Some(allowed) = sess.opts.unstable_opts.allow_features.as_ref() {
                 if allowed.iter().all(|f| name.as_str() != f) {
                     sess.emit_err(FeatureNotAllowed { span: mi.span(), name });
@@ -175,15 +170,16 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute]) -> Features {
                 }
             }
 
+            // If the declared feature is unstable, record it.
             if let Some(f) = ACTIVE_FEATURES.iter().find(|f| name == f.name) {
-                f.set(&mut features, mi.span());
-                features.declared_lang_features.push((name, mi.span(), None));
-                features.active_features.insert(name);
+                f.set(&mut features);
+                features.set_declared_lang_feature(name, mi.span(), None);
                 continue;
             }
 
-            features.declared_lib_features.push((name, mi.span()));
-            features.active_features.insert(name);
+            // Otherwise, the feature is unknown. Record it as a lib feature.
+            // It will be checked later.
+            features.set_declared_lib_feature(name, mi.span());
         }
     }
 
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index a02c04ecd3ecb..83961647bd442 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -7,15 +7,6 @@ use rustc_span::edition::Edition;
 use rustc_span::symbol::{sym, Symbol};
 use rustc_span::Span;
 
-macro_rules! set {
-    ($field: ident) => {{
-        fn f(features: &mut Features, _: Span) {
-            features.$field = true;
-        }
-        f as fn(&mut Features, Span)
-    }};
-}
-
 #[derive(PartialEq)]
 enum FeatureStatus {
     Default,
@@ -23,16 +14,19 @@ enum FeatureStatus {
     Internal,
 }
 
-macro_rules! declare_features {
-    (__status_to_enum active) => {
+macro_rules! status_to_enum {
+    (active) => {
         FeatureStatus::Default
     };
-    (__status_to_enum incomplete) => {
+    (incomplete) => {
         FeatureStatus::Incomplete
     };
-    (__status_to_enum internal) => {
+    (internal) => {
         FeatureStatus::Internal
     };
+}
+
+macro_rules! declare_features {
     ($(
         $(#[doc = $doc:tt])* ($status:ident, $feature:ident, $ver:expr, $issue:expr, $edition:expr),
     )+) => {
@@ -43,7 +37,10 @@ macro_rules! declare_features {
             &[$(
                 // (sym::$feature, $ver, $issue, $edition, set!($feature))
                 Feature {
-                    state: State::Active { set: set!($feature) },
+                    state: State::Active {
+                        // Sets this feature's corresponding bool within `features`.
+                        set: |features| features.$feature = true,
+                    },
                     name: sym::$feature,
                     since: $ver,
                     issue: to_nonzero($issue),
@@ -58,8 +55,9 @@ macro_rules! declare_features {
             pub declared_lang_features: Vec<(Symbol, Span, Option<Symbol>)>,
             /// `#![feature]` attrs for non-language (library) features.
             pub declared_lib_features: Vec<(Symbol, Span)>,
-            /// Features enabled for this crate.
-            pub active_features: FxHashSet<Symbol>,
+            /// `declared_lang_features` + `declared_lib_features`.
+            pub declared_features: FxHashSet<Symbol>,
+            /// Individual features (unstable only).
             $(
                 $(#[doc = $doc])*
                 pub $feature: bool
@@ -67,16 +65,33 @@ macro_rules! declare_features {
         }
 
         impl Features {
+            pub fn set_declared_lang_feature(
+                &mut self,
+                symbol: Symbol,
+                span: Span,
+                since: Option<Symbol>
+            ) {
+                self.declared_lang_features.push((symbol, span, since));
+                self.declared_features.insert(symbol);
+            }
+
+            pub fn set_declared_lib_feature(&mut self, symbol: Symbol, span: Span) {
+                self.declared_lib_features.push((symbol, span));
+                self.declared_features.insert(symbol);
+            }
+
             pub fn walk_feature_fields(&self, mut f: impl FnMut(&str, bool)) {
                 $(f(stringify!($feature), self.$feature);)+
             }
 
-            /// Is the given feature active?
-            pub fn active(&self, feature: Symbol) -> bool {
-                self.active_features.contains(&feature)
+            /// Is the given feature explicitly declared, i.e. named in a
+            /// `#![feature(...)]` within the code?
+            pub fn declared(&self, feature: Symbol) -> bool {
+                self.declared_features.contains(&feature)
             }
 
-            /// Is the given feature enabled?
+            /// Is the given feature enabled, i.e. declared or automatically
+            /// enabled due to the edition?
             ///
             /// Panics if the symbol doesn't correspond to a declared feature.
             pub fn enabled(&self, feature: Symbol) -> bool {
@@ -93,11 +108,10 @@ macro_rules! declare_features {
             pub fn incomplete(&self, feature: Symbol) -> bool {
                 match feature {
                     $(
-                        sym::$feature => declare_features!(__status_to_enum $status) == FeatureStatus::Incomplete,
+                        sym::$feature => status_to_enum!($status) == FeatureStatus::Incomplete,
                     )*
                     // accepted and removed features aren't in this file but are never incomplete
-                    _ if self.declared_lang_features.iter().any(|f| f.0 == feature) => false,
-                    _ if self.declared_lib_features.iter().any(|f| f.0 == feature) => false,
+                    _ if self.declared_features.contains(&feature) => false,
                     _ => panic!("`{}` was not listed in `declare_features`", feature),
                 }
             }
@@ -108,12 +122,11 @@ macro_rules! declare_features {
             pub fn internal(&self, feature: Symbol) -> bool {
                 match feature {
                     $(
-                        sym::$feature => declare_features!(__status_to_enum $status) == FeatureStatus::Internal,
+                        sym::$feature => status_to_enum!($status) == FeatureStatus::Internal,
                     )*
                     // accepted and removed features aren't in this file but are never internal
                     // (a removed feature might have been internal, but it doesn't matter anymore)
-                    _ if self.declared_lang_features.iter().any(|f| f.0 == feature) => false,
-                    _ if self.declared_lib_features.iter().any(|f| f.0 == feature) => false,
+                    _ if self.declared_features.contains(&feature) => false,
                     _ => panic!("`{}` was not listed in `declare_features`", feature),
                 }
             }
@@ -123,9 +136,9 @@ macro_rules! declare_features {
 
 impl Feature {
     /// Sets this feature in `Features`. Panics if called on a non-active feature.
-    pub fn set(&self, features: &mut Features, span: Span) {
+    pub fn set(&self, features: &mut Features) {
         match self.state {
-            State::Active { set } => set(features, span),
+            State::Active { set } => set(features),
             _ => panic!("called `set` on feature `{}` which is not `active`", self.name),
         }
     }
diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs
index 69e33115922ab..4721bff0ec719 100644
--- a/compiler/rustc_feature/src/lib.rs
+++ b/compiler/rustc_feature/src/lib.rs
@@ -23,16 +23,15 @@ mod removed;
 #[cfg(test)]
 mod tests;
 
-use rustc_span::{edition::Edition, symbol::Symbol, Span};
+use rustc_span::{edition::Edition, symbol::Symbol};
 use std::fmt;
 use std::num::NonZeroU32;
 
 #[derive(Clone, Copy)]
 pub enum State {
     Accepted,
-    Active { set: fn(&mut Features, Span) },
+    Active { set: fn(&mut Features) },
     Removed { reason: Option<&'static str> },
-    Stabilized { reason: Option<&'static str> },
 }
 
 impl fmt::Debug for State {
@@ -41,7 +40,6 @@ impl fmt::Debug for State {
             State::Accepted { .. } => write!(f, "accepted"),
             State::Active { .. } => write!(f, "active"),
             State::Removed { .. } => write!(f, "removed"),
-            State::Stabilized { .. } => write!(f, "stabilized"),
         }
     }
 }
@@ -79,8 +77,8 @@ pub enum UnstableFeatures {
 impl UnstableFeatures {
     /// This takes into account `RUSTC_BOOTSTRAP`.
     ///
-    /// If `krate` is [`Some`], then setting `RUSTC_BOOTSTRAP=krate` will enable the nightly features.
-    /// Otherwise, only `RUSTC_BOOTSTRAP=1` will work.
+    /// If `krate` is [`Some`], then setting `RUSTC_BOOTSTRAP=krate` will enable the nightly
+    /// features. Otherwise, only `RUSTC_BOOTSTRAP=1` will work.
     pub fn from_environment(krate: Option<&str>) -> Self {
         // `true` if this is a feature-staged build, i.e., on the beta or stable channel.
         let disable_unstable_features =
@@ -107,19 +105,17 @@ impl UnstableFeatures {
 }
 
 fn find_lang_feature_issue(feature: Symbol) -> Option<NonZeroU32> {
-    if let Some(info) = ACTIVE_FEATURES.iter().find(|t| t.name == feature) {
-        info.issue
-    } else {
-        // search in Accepted, Removed, or Stable Removed features
-        let found = ACCEPTED_FEATURES
-            .iter()
-            .chain(REMOVED_FEATURES)
-            .chain(STABLE_REMOVED_FEATURES)
-            .find(|t| t.name == feature);
-        match found {
-            Some(found) => found.issue,
-            None => panic!("feature `{feature}` is not declared anywhere"),
-        }
+    // Search in all the feature lists.
+    let found = []
+        .iter()
+        .chain(ACTIVE_FEATURES)
+        .chain(ACCEPTED_FEATURES)
+        .chain(REMOVED_FEATURES)
+        .find(|t| t.name == feature);
+
+    match found {
+        Some(found) => found.issue,
+        None => panic!("feature `{feature}` is not declared anywhere"),
     }
 }
 
@@ -152,4 +148,4 @@ pub use builtin_attrs::{
     is_valid_for_get_attr, AttributeGate, AttributeTemplate, AttributeType, BuiltinAttribute,
     GatedCfg, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
 };
-pub use removed::{REMOVED_FEATURES, STABLE_REMOVED_FEATURES};
+pub use removed::REMOVED_FEATURES;
diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs
index da18cb2a239e8..de15deef1789e 100644
--- a/compiler/rustc_feature/src/removed.rs
+++ b/compiler/rustc_feature/src/removed.rs
@@ -20,23 +20,6 @@ macro_rules! declare_features {
             ),+
         ];
     };
-
-    ($(
-        $(#[doc = $doc:tt])* (stable_removed, $feature:ident, $ver:expr, $issue:expr, None),
-    )+) => {
-        /// Represents stable features which have since been removed (it was once Accepted)
-        pub const STABLE_REMOVED_FEATURES: &[Feature] = &[
-            $(
-                Feature {
-                    state: State::Stabilized { reason: None },
-                    name: sym::$feature,
-                    since: $ver,
-                    issue: to_nonzero($issue),
-                    edition: None,
-                }
-            ),+
-        ];
-    };
 }
 
 #[rustfmt::skip]
@@ -141,6 +124,11 @@ declare_features! (
     (removed, no_coverage, "CURRENT_RUSTC_VERSION", Some(84605), None, Some("renamed to `coverage_attribute`")),
     /// Allows `#[no_debug]`.
     (removed, no_debug, "1.43.0", Some(29721), None, Some("removed due to lack of demand")),
+    /// Note: this feature was previously recorded in a separate
+    /// `STABLE_REMOVED` list because it, uniquely, was once stable but was
+    /// then removed. But there was no utility storing it separately, so now
+    /// it's in this list.
+    (removed, no_stack_check, "1.0.0", None, None, None),
     /// Allows using `#[on_unimplemented(..)]` on traits.
     /// (Moved to `rustc_attrs`.)
     (removed, on_unimplemented, "1.40.0", None, None, None),
@@ -208,8 +196,3 @@ declare_features! (
     // feature-group-end: removed features
     // -------------------------------------------------------------------------
 );
-
-#[rustfmt::skip]
-declare_features! (
-    (stable_removed, no_stack_check, "1.0.0", None, None),
-);
diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs
index 908ab8b613e88..c66f64dde3291 100644
--- a/compiler/rustc_middle/src/middle/stability.rs
+++ b/compiler/rustc_middle/src/middle/stability.rs
@@ -448,14 +448,14 @@ impl<'tcx> TyCtxt<'tcx> {
                     debug!("stability: skipping span={:?} since it is internal", span);
                     return EvalResult::Allow;
                 }
-                if self.features().active(feature) {
+                if self.features().declared(feature) {
                     return EvalResult::Allow;
                 }
 
                 // If this item was previously part of a now-stabilized feature which is still
                 // active (i.e. the user hasn't removed the attribute for the stabilized feature
                 // yet) then allow use of this item.
-                if let Some(implied_by) = implied_by && self.features().active(implied_by) {
+                if let Some(implied_by) = implied_by && self.features().declared(implied_by) {
                     return EvalResult::Allow;
                 }
 
@@ -532,7 +532,7 @@ impl<'tcx> TyCtxt<'tcx> {
                     debug!("body stability: skipping span={:?} since it is internal", span);
                     return EvalResult::Allow;
                 }
-                if self.features().active(feature) {
+                if self.features().declared(feature) {
                     return EvalResult::Allow;
                 }
 
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 949c6ab5ac0c1..5f012ec29fe00 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -1074,8 +1074,8 @@ pub struct Resolver<'a, 'tcx> {
     /// Also includes of list of each fields visibility
     struct_constructors: LocalDefIdMap<(Res, ty::Visibility<DefId>, Vec<ty::Visibility<DefId>>)>,
 
-    /// Features enabled for this crate.
-    active_features: FxHashSet<Symbol>,
+    /// Features declared for this crate.
+    declared_features: FxHashSet<Symbol>,
 
     lint_buffer: LintBuffer,
 
@@ -1417,12 +1417,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
             multi_segment_macro_resolutions: Default::default(),
             builtin_attrs: Default::default(),
             containers_deriving_copy: Default::default(),
-            active_features: features
-                .declared_lib_features
-                .iter()
-                .map(|(feat, ..)| *feat)
-                .chain(features.declared_lang_features.iter().map(|(feat, ..)| *feat))
-                .collect(),
+            declared_features: features.declared_features.clone(),
             lint_buffer: LintBuffer::default(),
             next_node_id: CRATE_NODE_ID,
             node_id_to_def_id,
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index 90ae08ce37c1f..f0a1a4ff931e9 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -854,7 +854,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                 let feature = stability.feature;
 
                 let is_allowed = |feature| {
-                    self.active_features.contains(&feature) || span.allows_unstable(feature)
+                    self.declared_features.contains(&feature) || span.allows_unstable(feature)
                 };
                 let allowed_by_implication = implied_by.is_some_and(|feature| is_allowed(feature));
                 if !is_allowed(feature) && !allowed_by_implication {
diff --git a/src/tools/clippy/clippy_lints/src/manual_float_methods.rs b/src/tools/clippy/clippy_lints/src/manual_float_methods.rs
index 88db7ae6aece0..15c8417e70284 100644
--- a/src/tools/clippy/clippy_lints/src/manual_float_methods.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_float_methods.rs
@@ -85,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
         if !in_external_macro(cx.sess(), expr.span)
             && (
                 matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst)
-                    || cx.tcx.features().active(sym!(const_float_classify))
+                    || cx.tcx.features().declared(sym!(const_float_classify))
             ) && let ExprKind::Binary(kind, lhs, rhs) = expr.kind
             && let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind
             && let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind