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) => continue,
68                Err(err) => {
69                    err.cancel();
70                    parser.psess.dcx().reset_err_count();
71                    return Err(
72                        "Expected item inside cfg_select block, but failed to parse it as an item",
73                    );
74                }
75            };
76            if let ast::ItemKind::Mod(..) = item.kind {
77                items.push(item);
78            }
79        }
80
81        if !parser.eat(exp!(CloseBrace)) {
82            return Err("Expected a closing brace");
83        }
84
85        if parser.eat(exp!(Eof)) {
86            break;
87        }
88    }
89
90    Ok(items)
91}
92
93/// LHS predicate of a `cfg_select!` arm.
94pub(crate) enum CfgSelectFormatPredicate {
95    /// Example: the `unix` in `unix => {}`. Notably, outer or inner attributes are not permitted.
96    Cfg(ast::MetaItemInner),
97    /// `_` in `_ => {}`.
98    Wildcard(Span),
99}
100
101impl Spanned for CfgSelectFormatPredicate {
102    fn span(&self) -> rustc_span::Span {
103        match self {
104            Self::Cfg(meta_item_inner) => meta_item_inner.span(),
105            Self::Wildcard(span) => *span,
106        }
107    }
108}
109
110/// Each `$predicate => $production` arm in `cfg_select!`.
111pub(crate) struct CfgSelectArm {
112    /// The `$predicate` part.
113    pub(crate) predicate: CfgSelectFormatPredicate,
114    /// Span of `=>`.
115    pub(crate) arrow: Token,
116    /// The RHS `$production` expression.
117    pub(crate) expr: Box<ast::Expr>,
118    /// `cfg_select!` arms `$production`s can be optionally `,` terminated, like `match` arms.
119    /// The `,` is not needed when `$production` is itself braced `{}`.
120    pub(crate) trailing_comma: Option<Span>,
121}
122
123impl PartialEq for &CfgSelectArm {
124    fn eq(&self, other: &Self) -> bool {
125        // consider the arms equal if they have the same span
126        self.span() == other.span()
127    }
128}
129
130impl Spanned for CfgSelectArm {
131    fn span(&self) -> Span {
132        self.predicate
133            .span()
134            .with_hi(if let Some(comma) = self.trailing_comma {
135                comma.hi()
136            } else {
137                self.expr.span.hi()
138            })
139    }
140}
141
142impl std::fmt::Debug for CfgSelectArm {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        match &self.predicate {
145            CfgSelectFormatPredicate::Cfg(cfg_entry) => cfg_entry.fmt(f)?,
146            CfgSelectFormatPredicate::Wildcard(t) => t.fmt(f)?,
147        };
148        write!(f, "=> {:?}", self.expr)
149    }
150}
151
152// FIXME(ytmimi) would be nice if rustfmt didn't need to implement parsing logic on its own
153// and could instead just call rustc_attr_parsing::parse_cfg_select, but this is fine for now.
154pub(crate) fn parse_cfg_select_arms(
155    psess: &ParseSess,
156    ts: TokenStream,
157) -> Option<Vec<CfgSelectArm>> {
158    let mut cfg_select_predicates = vec![];
159    let mut parser = build_stream_parser(psess.inner(), ts);
160
161    while parser.token != token::Eof {
162        let predicate = if parser.eat_keyword(exp!(Underscore)) {
163            CfgSelectFormatPredicate::Wildcard(parser.prev_token.span)
164        } else {
165            let Ok(meta_item) = parser.parse_meta_item_inner().map_err(|e| e.cancel()) else {
166                debug!("Failed to parse cfg entry in cfg_select! predicate");
167                return None;
168            };
169            CfgSelectFormatPredicate::Cfg(meta_item)
170        };
171
172        if let Err(e) = parser.expect(exp!(FatArrow)) {
173            e.cancel();
174            debug!("Expected to find a `=>` after cfg_selec! predicate.");
175            return None;
176        };
177
178        let arrow = parser.prev_token;
179
180        let Ok(expr) = parser.parse_expr().map_err(|e| e.cancel()) else {
181            debug!("Couldn't parse cfg_select! arm body after `=>`.");
182            return None;
183        };
184
185        let trailing_comma = if parser.eat(exp!(Comma)) {
186            Some(parser.prev_token.span)
187        } else {
188            None
189        };
190
191        let arm = CfgSelectArm {
192            predicate,
193            arrow,
194            expr,
195            trailing_comma,
196        };
197
198        cfg_select_predicates.push(arm);
199    }
200    Some(cfg_select_predicates)
201}