Skip to content

Commit

Permalink
Auto merge of #116437 - nnethercote:rustc_features, r=Nilstrieb
Browse files Browse the repository at this point in the history
Clean up `rustc_features`

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

r? `@Nilstrieb`
  • Loading branch information
bors committed Oct 7, 2023
2 parents 598e29b + 81d1f7e commit cf21a08
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 166 deletions.
162 changes: 79 additions & 83 deletions compiler/rustc_expand/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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 {
Expand All @@ -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) => {
Expand All @@ -136,54 +110,76 @@ 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 });
continue;
}
}

// 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());
}
}

Expand Down
69 changes: 41 additions & 28 deletions compiler/rustc_feature/src/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,26 @@ 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,
Incomplete,
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),
)+) => {
Expand All @@ -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),
Expand All @@ -58,25 +55,43 @@ 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
),+
}

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 {
Expand All @@ -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),
}
}
Expand All @@ -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),
}
}
Expand All @@ -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),
}
}
Expand Down
Loading

0 comments on commit cf21a08

Please sign in to comment.