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