Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/cfg_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,33 @@ pub struct CfgSelectBranches {
pub unreachable: Vec<(CfgSelectPredicate, TokenStream, Span)>,
}

impl CfgSelectBranches {
/// Removes the top-most branch for which `predicate` returns `true`,
/// or the wildcard if none of the reachable branches satisfied the predicate.
pub fn pop_first_match<F>(&mut self, predicate: F) -> Option<(TokenStream, Span)>
where
F: Fn(&CfgEntry) -> bool,
{
for (index, (cfg, _, _)) in self.reachable.iter().enumerate() {
if predicate(cfg) {
let matched = self.reachable.remove(index);
return Some((matched.1, matched.2));
}
}

self.wildcard.take().map(|(_, tts, span)| (tts, span))
}

/// Consume this value and iterate over all the `TokenStream`s that it stores.
pub fn into_iter_tts(self) -> impl Iterator<Item = (TokenStream, Span)> {
let it1 = self.reachable.into_iter().map(|(_, tts, span)| (tts, span));
let it2 = self.wildcard.into_iter().map(|(_, tts, span)| (tts, span));
let it3 = self.unreachable.into_iter().map(|(_, tts, span)| (tts, span));

it1.chain(it2).chain(it3)
}
}

pub fn parse_cfg_select(
p: &mut Parser<'_>,
sess: &Session,
Expand Down
75 changes: 59 additions & 16 deletions compiler/rustc_builtin_macros/src/cfg_select.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,62 @@
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{Expr, ast};
use rustc_attr_parsing as attr;
use rustc_attr_parsing::{
CfgSelectBranches, CfgSelectPredicate, EvalConfigResult, parse_cfg_select,
};
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult};
use rustc_span::{Ident, Span, sym};
use smallvec::SmallVec;

use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};

/// Selects the first arm whose predicate evaluates to true.
fn select_arm(ecx: &ExtCtxt<'_>, branches: CfgSelectBranches) -> Option<(TokenStream, Span)> {
for (cfg, tt, arm_span) in branches.reachable {
if let EvalConfigResult::True = attr::eval_config_entry(&ecx.sess, &cfg) {
return Some((tt, arm_span));
}
/// This intermediate structure is used to emit parse errors for the branches that are not chosen.
/// The `MacResult` instance below parses all branches, emitting any errors it encounters, but only
/// keeps the parse result for the selected branch.
struct CfgSelectResult<'cx, 'sess> {
ecx: &'cx mut ExtCtxt<'sess>,
site_span: Span,
selected_tts: TokenStream,
selected_span: Span,
other_branches: CfgSelectBranches,
}

fn tts_to_mac_result<'cx, 'sess>(
ecx: &'cx mut ExtCtxt<'sess>,
site_span: Span,
tts: TokenStream,
span: Span,
) -> Box<dyn MacResult + 'cx> {
match ExpandResult::from_tts(ecx, tts, site_span, span, Ident::with_dummy_span(sym::cfg_select))
{
ExpandResult::Ready(x) => x,
_ => unreachable!("from_tts always returns Ready"),
}
}

macro_rules! forward_to_parser_any_macro {
($method_name:ident, $ret_ty:ty) => {
fn $method_name(self: Box<Self>) -> Option<$ret_ty> {
let CfgSelectResult { ecx, site_span, selected_tts, selected_span, .. } = *self;

for (tts, span) in self.other_branches.into_iter_tts() {
let _ = tts_to_mac_result(ecx, site_span, tts, span).$method_name();
}

tts_to_mac_result(ecx, site_span, selected_tts, selected_span).$method_name()
}
};
}

impl<'cx, 'sess> MacResult for CfgSelectResult<'cx, 'sess> {
forward_to_parser_any_macro!(make_expr, Box<Expr>);
forward_to_parser_any_macro!(make_stmts, SmallVec<[ast::Stmt; 1]>);
forward_to_parser_any_macro!(make_items, SmallVec<[Box<ast::Item>; 1]>);

branches.wildcard.map(|(_, tt, span)| (tt, span))
forward_to_parser_any_macro!(make_impl_items, SmallVec<[Box<ast::AssocItem>; 1]>);
forward_to_parser_any_macro!(make_trait_impl_items, SmallVec<[Box<ast::AssocItem>; 1]>);
forward_to_parser_any_macro!(make_trait_items, SmallVec<[Box<ast::AssocItem>; 1]>);
forward_to_parser_any_macro!(make_foreign_items, SmallVec<[Box<ast::ForeignItem>; 1]>);
}

pub(super) fn expand_cfg_select<'cx>(
Expand All @@ -31,7 +71,7 @@ pub(super) fn expand_cfg_select<'cx>(
Some(ecx.ecfg.features),
ecx.current_expansion.lint_node_id,
) {
Ok(branches) => {
Ok(mut branches) => {
if let Some((underscore, _, _)) = branches.wildcard {
// Warn for every unreachable predicate. We store the fully parsed branch for rustfmt.
for (predicate, _, _) in &branches.unreachable {
Expand All @@ -44,14 +84,17 @@ pub(super) fn expand_cfg_select<'cx>(
}
}

if let Some((tts, arm_span)) = select_arm(ecx, branches) {
return ExpandResult::from_tts(
if let Some((selected_tts, selected_span)) = branches.pop_first_match(|cfg| {
matches!(attr::eval_config_entry(&ecx.sess, cfg), EvalConfigResult::True)
}) {
let mac = CfgSelectResult {
ecx,
tts,
sp,
arm_span,
Ident::with_dummy_span(sym::cfg_select),
);
selected_tts,
selected_span,
other_branches: branches,
site_span: sp,
};
return ExpandResult::Ready(Box::new(mac));
} else {
// Emit a compiler error when none of the predicates matched.
let guar = ecx.dcx().emit_err(CfgSelectNoMatches { span: sp });
Expand Down
58 changes: 57 additions & 1 deletion compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ pub struct CfgHideShow {
pub values: ThinVec<CfgInfo>,
}

#[derive(Clone, Debug, Default, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Debug, Default, HashStable_Generic, Decodable, PrintAttribute)]
pub struct DocAttribute {
pub aliases: FxIndexMap<Symbol, Span>,
pub hidden: Option<Span>,
Expand Down Expand Up @@ -566,6 +566,62 @@ pub struct DocAttribute {
pub no_crate_inject: Option<Span>,
}

impl<E: rustc_span::SpanEncoder> rustc_serialize::Encodable<E> for DocAttribute {
fn encode(&self, encoder: &mut E) {
let DocAttribute {
aliases,
hidden,
inline,
cfg,
auto_cfg,
auto_cfg_change,
fake_variadic,
keyword,
attribute,
masked,
notable_trait,
search_unbox,
html_favicon_url,
html_logo_url,
html_playground_url,
html_root_url,
html_no_source,
issue_tracker_base_url,
rust_logo,
test_attrs,
no_crate_inject,
} = self;
rustc_serialize::Encodable::<E>::encode(aliases, encoder);
rustc_serialize::Encodable::<E>::encode(hidden, encoder);

// FIXME: The `doc(inline)` attribute is never encoded, but is it actually the right thing
// to do? I suspect the condition was broken, should maybe instead not encode anything if we
// have `doc(no_inline)`.
let inline: ThinVec<_> =
inline.iter().filter(|(i, _)| *i != DocInline::Inline).cloned().collect();
rustc_serialize::Encodable::<E>::encode(&inline, encoder);

rustc_serialize::Encodable::<E>::encode(cfg, encoder);
rustc_serialize::Encodable::<E>::encode(auto_cfg, encoder);
rustc_serialize::Encodable::<E>::encode(auto_cfg_change, encoder);
rustc_serialize::Encodable::<E>::encode(fake_variadic, encoder);
rustc_serialize::Encodable::<E>::encode(keyword, encoder);
rustc_serialize::Encodable::<E>::encode(attribute, encoder);
rustc_serialize::Encodable::<E>::encode(masked, encoder);
rustc_serialize::Encodable::<E>::encode(notable_trait, encoder);
rustc_serialize::Encodable::<E>::encode(search_unbox, encoder);
rustc_serialize::Encodable::<E>::encode(html_favicon_url, encoder);
rustc_serialize::Encodable::<E>::encode(html_logo_url, encoder);
rustc_serialize::Encodable::<E>::encode(html_playground_url, encoder);
rustc_serialize::Encodable::<E>::encode(html_root_url, encoder);
rustc_serialize::Encodable::<E>::encode(html_no_source, encoder);
rustc_serialize::Encodable::<E>::encode(issue_tracker_base_url, encoder);
rustc_serialize::Encodable::<E>::encode(rust_logo, encoder);
rustc_serialize::Encodable::<E>::encode(test_attrs, encoder);
rustc_serialize::Encodable::<E>::encode(no_crate_inject, encoder);
}
}

/// Represents parsed *built-in* inert attributes.
///
/// ## Overview
Expand Down
10 changes: 3 additions & 7 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,13 +879,9 @@ fn analyze_attr(attr: &hir::Attribute, state: &mut AnalyzeAttrState<'_>) -> bool
should_encode = true;
}
} else if let hir::Attribute::Parsed(AttributeKind::Doc(d)) = attr {
// If this is a `doc` attribute that doesn't have anything except maybe `inline` (as in
// `#[doc(inline)]`), then we can remove it. It won't be inlinable in downstream crates.
if d.inline.is_empty() {
should_encode = true;
if d.hidden.is_some() {
state.is_doc_hidden = true;
}
should_encode = true;
if d.hidden.is_some() {
state.is_doc_hidden = true;
}
} else if let &[sym::diagnostic, seg] = &*attr.path() {
should_encode = rustc_feature::is_stable_diagnostic_attribute(seg, state.features);
Expand Down
35 changes: 17 additions & 18 deletions compiler/rustc_mir_build/src/builder/matches/buckets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use tracing::debug;

use crate::builder::Builder;
use crate::builder::matches::test::is_switch_ty;
use crate::builder::matches::{Candidate, Test, TestBranch, TestCase, TestKind};
use crate::builder::matches::{Candidate, Test, TestBranch, TestKind, TestableCase};

/// Output of [`Builder::partition_candidates_into_buckets`].
pub(crate) struct PartitionedCandidates<'tcx, 'b, 'c> {
Expand Down Expand Up @@ -140,12 +140,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// branch, so it can be removed. If false, the match pair is _compatible_
// with its test branch, but still needs a more specific test.
let fully_matched;
let ret = match (&test.kind, &match_pair.test_case) {
let ret = match (&test.kind, &match_pair.testable_case) {
// If we are performing a variant switch, then this
// informs variant patterns, but nothing else.
(
&TestKind::Switch { adt_def: tested_adt_def },
&TestCase::Variant { adt_def, variant_index },
&TestableCase::Variant { adt_def, variant_index },
) => {
assert_eq!(adt_def, tested_adt_def);
fully_matched = true;
Expand All @@ -159,24 +159,23 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// things out here, in some cases.
//
// FIXME(Zalathar): Is the `is_switch_ty` test unnecessary?
(TestKind::SwitchInt, &TestCase::Constant { value })
(TestKind::SwitchInt, &TestableCase::Constant { value })
if is_switch_ty(match_pair.pattern_ty) =>
{
// An important invariant of candidate bucketing is that a candidate
// must not match in multiple branches. For `SwitchInt` tests, adding
// a new value might invalidate that property for range patterns that
// have already been partitioned into the failure arm, so we must take care
// not to add such values here.
let is_covering_range = |test_case: &TestCase<'tcx>| {
test_case.as_range().is_some_and(|range| {
let is_covering_range = |testable_case: &TestableCase<'tcx>| {
testable_case.as_range().is_some_and(|range| {
matches!(range.contains(value, self.tcx), None | Some(true))
})
};
let is_conflicting_candidate = |candidate: &&mut Candidate<'tcx>| {
candidate
.match_pairs
.iter()
.any(|mp| mp.place == Some(test_place) && is_covering_range(&mp.test_case))
candidate.match_pairs.iter().any(|mp| {
mp.place == Some(test_place) && is_covering_range(&mp.testable_case)
})
};
if prior_candidates
.get(&TestBranch::Failure)
Expand All @@ -189,7 +188,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
Some(TestBranch::Constant(value))
}
}
(TestKind::SwitchInt, TestCase::Range(range)) => {
(TestKind::SwitchInt, TestableCase::Range(range)) => {
// When performing a `SwitchInt` test, a range pattern can be
// sorted into the failure arm if it doesn't contain _any_ of
// the values being tested. (This restricts what values can be
Expand All @@ -207,7 +206,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
})
}

(TestKind::If, TestCase::Constant { value }) => {
(TestKind::If, TestableCase::Constant { value }) => {
fully_matched = true;
let value = value.try_to_bool().unwrap_or_else(|| {
span_bug!(test.span, "expected boolean value but got {value:?}")
Expand All @@ -217,7 +216,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

(
&TestKind::Len { len: test_len, op: BinOp::Eq },
&TestCase::Slice { len, variable_length },
&TestableCase::Slice { len, variable_length },
) => {
match (test_len.cmp(&(len as u64)), variable_length) {
(Ordering::Equal, false) => {
Expand Down Expand Up @@ -249,7 +248,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
(
&TestKind::Len { len: test_len, op: BinOp::Ge },
&TestCase::Slice { len, variable_length },
&TestableCase::Slice { len, variable_length },
) => {
// the test is `$actual_len >= test_len`
match (test_len.cmp(&(len as u64)), variable_length) {
Expand Down Expand Up @@ -281,7 +280,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
}

(TestKind::Range(test), TestCase::Range(pat)) => {
(TestKind::Range(test), TestableCase::Range(pat)) => {
if test == pat {
fully_matched = true;
Some(TestBranch::Success)
Expand All @@ -292,7 +291,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
if !test.overlaps(pat, self.tcx)? { Some(TestBranch::Failure) } else { None }
}
}
(TestKind::Range(range), &TestCase::Constant { value }) => {
(TestKind::Range(range), &TestableCase::Constant { value }) => {
fully_matched = false;
if !range.contains(value, self.tcx)? {
// `value` is not contained in the testing range,
Expand All @@ -303,7 +302,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
}

(TestKind::Eq { value: test_val, .. }, TestCase::Constant { value: case_val }) => {
(TestKind::Eq { value: test_val, .. }, TestableCase::Constant { value: case_val }) => {
if test_val == case_val {
fully_matched = true;
Some(TestBranch::Success)
Expand All @@ -313,7 +312,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
}

(TestKind::Deref { temp: test_temp, .. }, TestCase::Deref { temp, .. })
(TestKind::Deref { temp: test_temp, .. }, TestableCase::Deref { temp, .. })
if test_temp == temp =>
{
fully_matched = true;
Expand Down
Loading
Loading