Skip to main content

rustfmt_nightly/parse/macros/
cfg_select.rs

1//! See [`cfg_select!` reference](
2//! https://doc.rust-lang.org/nightly/reference/conditional-compilation.html#the-cfg_select-macro
3//! ) for grammar.
4
5use std::panic::{AssertUnwindSafe, catch_unwind};
6
7use rustc_ast::ast;
8use rustc_ast::token;
9use rustc_ast::token::{Token, TokenKind};
10use rustc_ast::tokenstream::TokenStream;
11use rustc_parse::exp;
12use rustc_parse::parser::{AllowConstBlockItems, ForceCollect};
13use rustc_span::Span;
14use tracing::debug;
15
16use crate::parse::macros::build_stream_parser;
17use crate::parse::session::ParseSess;
18use crate::spanned::Spanned;
19
20pub(crate) fn parse_items_from_cfg_select<'a>(
21    psess: &'a ParseSess,
22    mac: &'a ast::MacCall,
23) -> Result<Vec<ast::Item>, &'static str> {
24    match catch_unwind(AssertUnwindSafe(|| {
25        parse_items_from_cfg_select_inner(psess, mac)
26    })) {
27        Ok(Ok(items)) => Ok(items),
28        Ok(err @ Err(_)) => err,
29        Err(..) => Err("failed to parse cfg_select!"),
30    }
31}
32
33fn parse_items_from_cfg_select_inner<'a>(
34    psess: &'a ParseSess,
35    mac: &'a ast::MacCall,
36) -> Result<Vec<ast::Item>, &'static str> {
37    let ts = mac.args.tokens.clone();
38    let mut parser = build_stream_parser(psess.inner(), ts);
39
40    if parser.token == TokenKind::OpenBrace {
41        return Err("Expression position cfg_select! not yet supported");
42    }
43
44    let mut items = vec![];
45
46    while parser.token.kind != TokenKind::Eof {
47        if !parser.eat_keyword(exp!(Underscore)) {
48            parser.parse_attr_item(ForceCollect::No).map_err(|e| {
49                e.cancel();
50                "Failed to parse attr item"
51            })?;
52        }
53
54        if !parser.eat(exp!(FatArrow)) {
55            return Err("Expected a fat arrow");
56        }
57
58        if !parser.eat(exp!(OpenBrace)) {
59            return Err("Expected an opening brace");
60        }
61
62        while parser.token != TokenKind::CloseBrace && parser.token.kind != TokenKind::Eof {
63            let item = match parser
64                .parse_item(ForceCollect::No, AllowConstBlockItems::DoesNotMatter)
65            {
66                Ok(Some(item_ptr)) => *item_ptr,
67                Ok(None) => {
68                    // Advance the parser by at least one token to prevent an infinite loop
69                    parser.bump();
70                    continue;
71                }
72                Err(err) => {
73                    err.cancel();
74                    parser.psess.dcx().reset_err_count();
75                    return Err(
76                        "Expected item inside cfg_select block, but failed to parse it as an item",
77                    );
78                }
79            };
80            if let ast::ItemKind::Mod(..) = item.kind {
81                items.push(item);
82            }
83        }
84
85        if !parser.eat(exp!(CloseBrace)) {
86            return Err("Expected a closing brace");
87        }
88
89        if parser.eat(exp!(Eof)) {
90            break;
91        }
92    }
93
94    Ok(items)
95}
96
97/// LHS predicate of a `cfg_select!` arm.
98pub(crate) enum CfgSelectFormatPredicate {
99    /// Example: the `unix` in `unix => {}`. Notably, outer or inner attributes are not permitted.
100    Cfg(ast::MetaItemInner),
101    /// `_` in `_ => {}`.
102    Wildcard(Span),
103}
104
105impl Spanned for CfgSelectFormatPredicate {
106    fn span(&self) -> rustc_span::Span {
107        match self {
108            Self::Cfg(meta_item_inner) => meta_item_inner.span(),
109            Self::Wildcard(span) => *span,
110        }
111    }
112}
113
114/// Each `$predicate => $production` arm in `cfg_select!`.
115pub(crate) struct CfgSelectArm {
116    /// The `$predicate` part.
117    pub(crate) predicate: CfgSelectFormatPredicate,
118    /// Span of `=>`.
119    pub(crate) arrow: Token,
120    /// The RHS `$production` expression.
121    pub(crate) expr: Box<ast::Expr>,
122    /// `cfg_select!` arms `$production`s can be optionally `,` terminated, like `match` arms.
123    /// The `,` is not needed when `$production` is itself braced `{}`.
124    pub(crate) trailing_comma: Option<Span>,
125}
126
127impl PartialEq for &CfgSelectArm {
128    fn eq(&self, other: &Self) -> bool {
129        // consider the arms equal if they have the same span
130        self.span() == other.span()
131    }
132}
133
134impl Spanned for CfgSelectArm {
135    fn span(&self) -> Span {
136        self.predicate
137            .span()
138            .with_hi(if let Some(comma) = self.trailing_comma {
139                comma.hi()
140            } else {
141                self.expr.span.hi()
142            })
143    }
144}
145
146impl std::fmt::Debug for CfgSelectArm {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        match &self.predicate {
149            CfgSelectFormatPredicate::Cfg(cfg_entry) => cfg_entry.fmt(f)?,
150            CfgSelectFormatPredicate::Wildcard(t) => t.fmt(f)?,
151        };
152        write!(f, "=> {:?}", self.expr)
153    }
154}
155
156// FIXME(ytmimi) would be nice if rustfmt didn't need to implement parsing logic on its own
157// and could instead just call rustc_attr_parsing::parse_cfg_select, but this is fine for now.
158pub(crate) fn parse_cfg_select_arms(
159    psess: &ParseSess,
160    ts: TokenStream,
161) -> Option<Vec<CfgSelectArm>> {
162    let mut cfg_select_predicates = vec![];
163    let mut parser = build_stream_parser(psess.inner(), ts);
164
165    while parser.token != token::Eof {
166        let predicate = if parser.eat_keyword(exp!(Underscore)) {
167            CfgSelectFormatPredicate::Wildcard(parser.prev_token.span)
168        } else {
169            let Ok(meta_item) = parser.parse_meta_item_inner().map_err(|e| e.cancel()) else {
170                debug!("Failed to parse cfg entry in cfg_select! predicate");
171                return None;
172            };
173            CfgSelectFormatPredicate::Cfg(meta_item)
174        };
175
176        if let Err(e) = parser.expect(exp!(FatArrow)) {
177            e.cancel();
178            debug!("Expected to find a `=>` after cfg_selec! predicate.");
179            return None;
180        };
181
182        let arrow = parser.prev_token;
183
184        let Ok(expr) = parser.parse_expr().map_err(|e| e.cancel()) else {
185            debug!("Couldn't parse cfg_select! arm body after `=>`.");
186            return None;
187        };
188
189        let trailing_comma = if parser.eat(exp!(Comma)) {
190            Some(parser.prev_token.span)
191        } else {
192            None
193        };
194
195        let arm = CfgSelectArm {
196            predicate,
197            arrow,
198            expr,
199            trailing_comma,
200        };
201
202        cfg_select_predicates.push(arm);
203    }
204    Some(cfg_select_predicates)
205}