Auto merge of #116437 - nnethercote:rustc_features, r=Nilstrieb

Clean up `rustc_features`

Plenty more to be done, but this is a decent start.

r? `@Nilstrieb`
This commit is contained in:
bors 2023-10-07 19:11:17 +00:00
commit cf21a0823b
8 changed files with 150 additions and 167 deletions

View File

@ -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 {
})
}
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()
}
}
let mut features = Features::default();
let mut edition_enabled_features = FxHashMap::default();
// The edition from `--edition`.
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);
}
}
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.
// 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 {
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;
}
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 {
if edition <= crate_edition {
continue;
}
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);
if let Some(edition) = edition && edition > features_edition {
features_edition = edition;
}
}
}
}
// 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);
}
// Process all features declared in the code.
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 {
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 ALL_EDITIONS.iter().any(|e| name == e.feature_name()) {
// Handled in the separate loop above.
// 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: features_edition,
});
features.set_declared_lang_feature(name, mi.span(), None);
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());
}
}

View File

@ -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),
}
}

View File

@ -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,21 +105,19 @@ 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
// Search in all the feature lists.
let found = []
.iter()
.chain(ACTIVE_FEATURES)
.chain(ACCEPTED_FEATURES)
.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"),
}
}
}
const fn to_nonzero(n: Option<u32>) -> Option<NonZeroU32> {
// Can be replaced with `n.and_then(NonZeroU32::new)` if that is ever usable
@ -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;

View File

@ -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),
);

View File

@ -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;
}

View File

@ -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,

View File

@ -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 {

View File

@ -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