Skip to main content

rustc_attr_parsing/attributes/
cfg_select.rs

1use rustc_ast::token::Token;
2use rustc_ast::tokenstream::TokenStream;
3use rustc_ast::{AttrStyle, NodeId, token};
4use rustc_attr_ir::target::Target;
5use rustc_attr_ir::{AttrPath, CfgEntry};
6use rustc_data_structures::fx::FxHashMap;
7use rustc_errors::Diagnostic;
8use rustc_feature::Features;
9use rustc_lint_defs::builtin::UNREACHABLE_CFG_SELECT_PREDICATES;
10use rustc_parse::exp;
11use rustc_parse::parser::{Parser, Recovery};
12use rustc_session::Session;
13use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
14
15use crate::attributes::AttributeSafety;
16use crate::parser::{AllowExprMetavar, MetaItemOrLitParser};
17use crate::{
18    AttributeParser, AttributeTemplate, EvalConfigResult, ParsedDescription, ShouldEmit,
19    diagnostics, parse_cfg_entry,
20};
21
22#[derive(#[automatically_derived]
impl ::core::clone::Clone for CfgSelectPredicate {
    #[inline]
    fn clone(&self) -> CfgSelectPredicate {
        match self {
            CfgSelectPredicate::Cfg(__self_0) =>
                CfgSelectPredicate::Cfg(::core::clone::Clone::clone(__self_0)),
            CfgSelectPredicate::Wildcard(__self_0) =>
                CfgSelectPredicate::Wildcard(::core::clone::Clone::clone(__self_0)),
        }
    }
}Clone)]
23pub enum CfgSelectPredicate {
24    Cfg(CfgEntry),
25    Wildcard(Token),
26}
27
28impl CfgSelectPredicate {
29    fn span(&self) -> Span {
30        match self {
31            CfgSelectPredicate::Cfg(cfg_entry) => cfg_entry.span(),
32            CfgSelectPredicate::Wildcard(token) => token.span,
33        }
34    }
35}
36
37#[derive(#[automatically_derived]
impl ::core::default::Default for CfgSelectBranches {
    #[inline]
    fn default() -> CfgSelectBranches {
        CfgSelectBranches {
            reachable: ::core::default::Default::default(),
            wildcard: ::core::default::Default::default(),
            unreachable: ::core::default::Default::default(),
        }
    }
}Default)]
38pub struct CfgSelectBranches {
39    /// All the conditional branches.
40    pub reachable: Vec<(CfgEntry, TokenStream, Span)>,
41    /// The first wildcard `_ => { ... }` branch.
42    pub wildcard: Option<(Token, TokenStream, Span)>,
43    /// All branches after the first wildcard, including further wildcards.
44    /// These branches are kept for formatting.
45    pub unreachable: Vec<(CfgSelectPredicate, TokenStream, Span)>,
46}
47
48impl CfgSelectBranches {
49    /// Removes the top-most branch for which `predicate` returns `true`,
50    /// or the wildcard if none of the reachable branches satisfied the predicate.
51    pub fn pop_first_match<F>(&mut self, predicate: F) -> Option<(CfgEntry, TokenStream, Span)>
52    where
53        F: Fn(&CfgEntry) -> EvalConfigResult,
54    {
55        for (index, (cfg, _, _)) in self.reachable.iter_mut().enumerate() {
56            match predicate(cfg) {
57                EvalConfigResult::True => {
58                    return Some(self.reachable.remove(index));
59                }
60                EvalConfigResult::False { reason } => {
61                    *cfg = reason;
62                }
63            }
64        }
65
66        self.wildcard.take().map(|(_, tts, span)| (CfgEntry::Bool(true, span), tts, span))
67    }
68
69    /// Consume this value and iterate over all the `TokenStream`s that it stores.
70    pub fn into_iter_tts(self) -> impl Iterator<Item = (CfgEntry, TokenStream, Span)> {
71        let it1 = self.reachable.into_iter();
72        let it2 =
73            self.wildcard.into_iter().map(|(_, tts, span)| (CfgEntry::Bool(true, span), tts, span));
74        let it3 = self
75            .unreachable
76            .into_iter()
77            .map(|(_, tts, span)| (CfgEntry::Bool(false, span), tts, span));
78
79        it1.chain(it2).chain(it3)
80    }
81}
82
83pub fn parse_cfg_select(
84    p: &mut Parser<'_>,
85    sess: &Session,
86    features: Option<&Features>,
87    lint_node_id: NodeId,
88) -> Result<CfgSelectBranches, ErrorGuaranteed> {
89    let mut branches = CfgSelectBranches::default();
90
91    while p.token != token::Eof {
92        p.recover_from_outer_attributes("`cfg_select` branches").map_err(|e| e.emit())?;
93
94        if p.eat_keyword(::rustc_parse::parser::token_type::ExpKeywordPair {
    kw: rustc_span::symbol::kw::Underscore,
    token_type: ::rustc_parse::parser::token_type::TokenType::KwUnderscore,
}exp!(Underscore)) {
95            let underscore = p.prev_token;
96            p.expect(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::FatArrow,
    token_type: ::rustc_parse::parser::token_type::TokenType::FatArrow,
}exp!(FatArrow)).map_err(|e| e.emit())?;
97
98            let tts = p.parse_cfg_select_branch_rhs().map_err(|e| e.emit())?;
99            let span = underscore.span.to(p.token.span);
100
101            match branches.wildcard {
102                None => branches.wildcard = Some((underscore, tts, span)),
103                Some(_) => {
104                    branches.unreachable.push((CfgSelectPredicate::Wildcard(underscore), tts, span))
105                }
106            }
107        } else {
108            let meta = MetaItemOrLitParser::parse_single(
109                p,
110                ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
111                AllowExprMetavar::Yes,
112            )
113            .map_err(|diag| diag.emit())?;
114            let cfg_span = meta.span();
115            let cfg = AttributeParser::parse_single_args(
116                sess,
117                cfg_span,
118                cfg_span,
119                AttrStyle::Inner,
120                AttrPath { segments: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [sym::cfg_select]))vec![sym::cfg_select].into_boxed_slice(), span: cfg_span },
121                None,
122                AttributeSafety::Normal,
123                ParsedDescription::Macro,
124                cfg_span,
125                lint_node_id,
126                // Doesn't matter what the target actually is here.
127                Target::Crate,
128                features,
129                ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
130                &meta,
131                parse_cfg_entry,
132                &AttributeTemplate::default(),
133            )?;
134
135            p.expect(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::FatArrow,
    token_type: ::rustc_parse::parser::token_type::TokenType::FatArrow,
}exp!(FatArrow)).map_err(|e| e.emit())?;
136
137            let tts = p.parse_cfg_select_branch_rhs().map_err(|e| e.emit())?;
138            let span = cfg_span.to(p.token.span);
139
140            match branches.wildcard {
141                None => branches.reachable.push((cfg, tts, span)),
142                Some(_) => branches.unreachable.push((CfgSelectPredicate::Cfg(cfg), tts, span)),
143            }
144        }
145    }
146
147    let it = branches
148        .reachable
149        .iter()
150        .map(|(entry, _, _)| CfgSelectPredicate::Cfg(entry.clone()))
151        .chain(branches.wildcard.as_ref().map(|(t, _, _)| CfgSelectPredicate::Wildcard(*t)))
152        .chain(branches.unreachable.iter().map(|(entry, _, _)| CfgSelectPredicate::clone(entry)));
153
154    lint_unreachable(p, it, lint_node_id);
155
156    Ok(branches)
157}
158
159fn lint_unreachable(
160    p: &mut Parser<'_>,
161    predicates: impl Iterator<Item = CfgSelectPredicate>,
162    lint_node_id: NodeId,
163) {
164    // Symbols that have a known value.
165    let mut known = FxHashMap::<Symbol, bool>::default();
166    let mut wildcard_span = None;
167    let mut it = predicates;
168
169    let branch_is_unreachable = |predicate: CfgSelectPredicate, wildcard_span| {
170        let span = predicate.span();
171        p.psess.dyn_buffer_lint(
172            UNREACHABLE_CFG_SELECT_PREDICATES,
173            span,
174            lint_node_id,
175            move |dcx, level| match wildcard_span {
176                Some(wildcard_span) => {
177                    diagnostics::UnreachableCfgSelectPredicateWildcard { span, wildcard_span }
178                        .into_diag(dcx, level)
179                }
180                None => diagnostics::UnreachableCfgSelectPredicate { span }.into_diag(dcx, level),
181            },
182        );
183    };
184
185    for predicate in &mut it {
186        let CfgSelectPredicate::Cfg(ref cfg_entry) = predicate else {
187            wildcard_span = Some(predicate.span());
188            break;
189        };
190
191        match cfg_entry {
192            CfgEntry::Bool(true, _) => {
193                wildcard_span = Some(predicate.span());
194                break;
195            }
196            CfgEntry::Bool(false, _) => continue,
197            CfgEntry::NameValue { name, value, .. } => match value {
198                None => {
199                    // `name` will be false in all subsequent branches.
200                    let current = known.insert(*name, false);
201
202                    match current {
203                        None => continue,
204                        Some(false) => {
205                            branch_is_unreachable(predicate, None);
206                            break;
207                        }
208                        Some(true) => {
209                            // this branch will be taken, so all subsequent branches are unreachable.
210                            break;
211                        }
212                    }
213                }
214                Some(_) => {
215                    // FIXME(https://github.com/rust-lang/rust/pull/149960#issuecomment-3780301699): expand capabilities.
216                }
217            },
218            CfgEntry::Not(inner, _) => match &**inner {
219                CfgEntry::NameValue { name, value: None, .. } => {
220                    // `name` will be true in all subsequent branches.
221                    let current = known.insert(*name, true);
222
223                    match current {
224                        None => continue,
225                        Some(true) => {
226                            branch_is_unreachable(predicate, None);
227                            break;
228                        }
229                        Some(false) => {
230                            // this branch will be taken, so all subsequent branches are unreachable.
231                            break;
232                        }
233                    }
234                }
235                _ => {
236                    // FIXME(https://github.com/rust-lang/rust/pull/149960#issuecomment-3780301699): expand capabilities.
237                }
238            },
239            CfgEntry::All(_, _) | CfgEntry::Any(_, _) => {
240                /* for now we don't bother solving these */
241            }
242            CfgEntry::Version(..) => { /* don't bother solving these */ }
243        }
244    }
245
246    for predicate in it {
247        branch_is_unreachable(predicate, wildcard_span)
248    }
249}