cxx_gen/syntax/
cfg.rs

1use indexmap::{indexset as set, IndexSet as Set};
2use proc_macro2::Ident;
3use std::hash::{Hash, Hasher};
4use std::iter;
5use std::mem;
6use syn::parse::{Error, ParseStream, Result};
7use syn::{parenthesized, token, Attribute, LitStr, Token};
8
9#[derive(Clone)]
10pub(crate) enum CfgExpr {
11    Unconditional,
12    Eq(Ident, Option<LitStr>),
13    All(Vec<CfgExpr>),
14    Any(Vec<CfgExpr>),
15    Not(Box<CfgExpr>),
16}
17
18#[derive(Clone)]
19pub(crate) enum ComputedCfg<'a> {
20    Leaf(&'a CfgExpr),
21    All(Set<&'a CfgExpr>),
22    Any(Set<ComputedCfg<'a>>),
23}
24
25impl CfgExpr {
26    pub(crate) fn merge_and(&mut self, expr: CfgExpr) {
27        if let CfgExpr::Unconditional = self {
28            *self = expr;
29        } else if let CfgExpr::Unconditional = expr {
30            // drop
31        } else if let CfgExpr::All(list) = self {
32            list.push(expr);
33        } else {
34            let prev = mem::replace(self, CfgExpr::Unconditional);
35            *self = CfgExpr::All(vec![prev, expr]);
36        }
37    }
38}
39
40impl<'a> ComputedCfg<'a> {
41    pub(crate) fn all(one: &'a CfgExpr, two: &'a CfgExpr) -> Self {
42        if let (cfg, CfgExpr::Unconditional) | (CfgExpr::Unconditional, cfg) = (one, two) {
43            ComputedCfg::Leaf(cfg)
44        } else if one == two {
45            ComputedCfg::Leaf(one)
46        } else {
47            ComputedCfg::All(set![one, two])
48        }
49    }
50
51    pub(crate) fn merge_or(&mut self, other: impl Into<ComputedCfg<'a>>) {
52        let other = other.into();
53        if let ComputedCfg::Leaf(CfgExpr::Unconditional) = self {
54            // drop
55        } else if let ComputedCfg::Leaf(CfgExpr::Unconditional) = other {
56            *self = other;
57        } else if *self == other {
58            // drop
59        } else if let ComputedCfg::Any(list) = self {
60            list.insert(other);
61        } else {
62            let prev = mem::replace(self, ComputedCfg::Any(Set::new()));
63            let ComputedCfg::Any(list) = self else {
64                unreachable!();
65            };
66            list.extend([prev, other]);
67        }
68    }
69}
70
71impl<'a> From<&'a CfgExpr> for ComputedCfg<'a> {
72    fn from(cfg: &'a CfgExpr) -> Self {
73        ComputedCfg::Leaf(cfg)
74    }
75}
76
77impl Eq for CfgExpr {}
78
79impl PartialEq for CfgExpr {
80    fn eq(&self, other: &Self) -> bool {
81        match (self, other) {
82            (CfgExpr::Unconditional, CfgExpr::Unconditional) => true,
83            (CfgExpr::Eq(this_ident, None), CfgExpr::Eq(other_ident, None)) => {
84                this_ident == other_ident
85            }
86            (
87                CfgExpr::Eq(this_ident, Some(this_value)),
88                CfgExpr::Eq(other_ident, Some(other_value)),
89            ) => {
90                this_ident == other_ident
91                    && this_value.token().to_string() == other_value.token().to_string()
92            }
93            (CfgExpr::All(this), CfgExpr::All(other))
94            | (CfgExpr::Any(this), CfgExpr::Any(other)) => this == other,
95            (CfgExpr::Not(this), CfgExpr::Not(other)) => this == other,
96            (_, _) => false,
97        }
98    }
99}
100
101impl Hash for CfgExpr {
102    fn hash<H: Hasher>(&self, hasher: &mut H) {
103        mem::discriminant(self).hash(hasher);
104        match self {
105            CfgExpr::Unconditional => {}
106            CfgExpr::Eq(ident, value) => {
107                ident.hash(hasher);
108                // syn::LitStr does not have its own Hash impl
109                value.as_ref().map(LitStr::value).hash(hasher);
110            }
111            CfgExpr::All(inner) | CfgExpr::Any(inner) => inner.hash(hasher),
112            CfgExpr::Not(inner) => inner.hash(hasher),
113        }
114    }
115}
116
117impl<'a> Eq for ComputedCfg<'a> {}
118
119impl<'a> PartialEq for ComputedCfg<'a> {
120    fn eq(&self, other: &Self) -> bool {
121        match (self, other) {
122            (ComputedCfg::Leaf(this), ComputedCfg::Leaf(other)) => this == other,
123            // For the purpose of deduplicating the contents of an `all` or
124            // `any`, we only consider sets equal if they contain the same cfgs
125            // in the same order.
126            (ComputedCfg::All(this), ComputedCfg::All(other)) => {
127                this.len() == other.len()
128                    && iter::zip(this, other).all(|(this, other)| this == other)
129            }
130            (ComputedCfg::Any(this), ComputedCfg::Any(other)) => {
131                this.len() == other.len()
132                    && iter::zip(this, other).all(|(this, other)| this == other)
133            }
134            (_, _) => false,
135        }
136    }
137}
138
139impl<'a> Hash for ComputedCfg<'a> {
140    fn hash<H: Hasher>(&self, hasher: &mut H) {
141        mem::discriminant(self).hash(hasher);
142        match self {
143            ComputedCfg::Leaf(cfg) => cfg.hash(hasher),
144            ComputedCfg::All(inner) => inner.iter().for_each(|cfg| cfg.hash(hasher)),
145            ComputedCfg::Any(inner) => inner.iter().for_each(|cfg| cfg.hash(hasher)),
146        }
147    }
148}
149
150pub(crate) fn parse_attribute(attr: &Attribute) -> Result<CfgExpr> {
151    attr.parse_args_with(|input: ParseStream| {
152        let cfg_expr = input.call(parse_single)?;
153        input.parse::<Option<Token![,]>>()?;
154        Ok(cfg_expr)
155    })
156}
157
158fn parse_single(input: ParseStream) -> Result<CfgExpr> {
159    let ident: Ident = input.parse()?;
160    let lookahead = input.lookahead1();
161    if input.peek(token::Paren) {
162        let content;
163        parenthesized!(content in input);
164        if ident == "all" {
165            let list = content.call(parse_multiple)?;
166            Ok(CfgExpr::All(list))
167        } else if ident == "any" {
168            let list = content.call(parse_multiple)?;
169            Ok(CfgExpr::Any(list))
170        } else if ident == "not" {
171            let expr = content.call(parse_single)?;
172            content.parse::<Option<Token![,]>>()?;
173            Ok(CfgExpr::Not(Box::new(expr)))
174        } else {
175            Err(Error::new(ident.span(), "unrecognized cfg expression"))
176        }
177    } else if lookahead.peek(Token![=]) {
178        input.parse::<Token![=]>()?;
179        let string: LitStr = input.parse()?;
180        Ok(CfgExpr::Eq(ident, Some(string)))
181    } else if lookahead.peek(Token![,]) || input.is_empty() {
182        Ok(CfgExpr::Eq(ident, None))
183    } else {
184        Err(lookahead.error())
185    }
186}
187
188fn parse_multiple(input: ParseStream) -> Result<Vec<CfgExpr>> {
189    let mut vec = Vec::new();
190    while !input.is_empty() {
191        let expr = input.call(parse_single)?;
192        vec.push(expr);
193        if input.is_empty() {
194            break;
195        }
196        input.parse::<Token![,]>()?;
197    }
198    Ok(vec)
199}