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 { 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 { + 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)>, /// `#![feature]` attrs for non-language (library) features. pub declared_lib_features: Vec<(Symbol, Span)>, - /// Features enabled for this crate. - pub active_features: FxHashSet, + /// `declared_lang_features` + `declared_lib_features`. + pub declared_features: FxHashSet, + /// 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 + ) { + 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 { - 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, Vec>)>, - /// Features enabled for this crate. - active_features: FxHashSet, + /// Features declared for this crate. + declared_features: FxHashSet, 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 ed9189a180495..f09946955651b 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