Skip to main content

candela/
cfg.rs

1//! Compile-time configuration: the names `@cfg(...)` tests.
2//!
3//! An item or statement written after `@cfg(predicate)` is compiled only when
4//! the predicate holds, and is dropped before name resolution otherwise, so the
5//! code it holds never has to resolve. candela names no configuration of its
6//! own. The program embedding it decides which flags are on, the way a host
7//! compiling for a browser turns on `web`:
8//!
9//! ```no_run
10//! use candela::Cfg;
11//!
12//! let mut cfg = Cfg::new();
13//! cfg.enable("web");
14//! cfg.enable_value("target", "wasm32");
15//! let engine = candela::Engine::new().with_cfg(cfg);
16//! ```
17//!
18//! A compile that does not go through an [`crate::Engine`] runs inside
19//! [`Cfg::scope`] instead. A flag nobody enabled is false.
20
21use rustc_hash::FxHashSet;
22use smol_strc::SmolStr;
23use std::cell::RefCell;
24
25/// The flags and `key = "value"` pairs a compile treats as true.
26#[derive(Clone, Debug, Default, PartialEq, Eq)]
27pub struct Cfg {
28    flags: FxHashSet<SmolStr>,
29    values: FxHashSet<(SmolStr, SmolStr)>,
30}
31
32impl Cfg {
33    /// A configuration with nothing enabled: every `@cfg(flag)` is false.
34    #[must_use]
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Turns `flag` on, so `@cfg(flag)` holds.
40    pub fn enable(&mut self, flag: &str) {
41        self.flags.insert(SmolStr::new(flag));
42    }
43
44    /// Turns the pair on, so `@cfg(key = "value")` holds. A key may carry
45    /// several values at once; each one is its own pair.
46    pub fn enable_value(&mut self, key: &str, value: &str) {
47        self.values.insert((SmolStr::new(key), SmolStr::new(value)));
48    }
49
50    /// Whether `@cfg(flag)` holds.
51    #[must_use]
52    pub fn is_enabled(&self, flag: &str) -> bool {
53        self.flags.contains(flag)
54    }
55
56    /// Whether `@cfg(key = "value")` holds.
57    #[must_use]
58    pub fn has_value(&self, key: &str, value: &str) -> bool {
59        self.values
60            .contains(&(SmolStr::new(key), SmolStr::new(value)))
61    }
62
63    /// Runs `f` with this configuration active, and returns what it returns.
64    ///
65    /// Anything compiled inside `f` reads its `@cfg` attributes against this
66    /// configuration, including the files it imports. The previous one is
67    /// restored afterwards, including when `f` unwinds.
68    pub fn scope<R>(&self, f: impl FnOnce() -> R) -> R {
69        let _active = Active::push(self.clone());
70        f()
71    }
72}
73
74/// Each name enables one flag.
75impl<S: AsRef<str>> FromIterator<S> for Cfg {
76    fn from_iter<I: IntoIterator<Item = S>>(iter: I) -> Self {
77        let mut cfg = Self::new();
78        for flag in iter {
79            cfg.enable(flag.as_ref());
80        }
81        cfg
82    }
83}
84
85thread_local! {
86    /// The configurations installed by the [`Cfg::scope`] calls currently on
87    /// the stack; the last one is the active one.
88    static ACTIVE: RefCell<Vec<Cfg>> = const { RefCell::new(Vec::new()) };
89}
90
91/// Pops the configuration it pushed when it is dropped, so a compile that
92/// unwinds leaves the stack as it found it.
93struct Active;
94
95impl Active {
96    fn push(cfg: Cfg) -> Self {
97        ACTIVE.with(|stack| stack.borrow_mut().push(cfg));
98        Self
99    }
100}
101
102impl Drop for Active {
103    fn drop(&mut self) {
104        ACTIVE.with(|stack| {
105            stack.borrow_mut().pop();
106        });
107    }
108}
109
110/// The configuration a compile starting now reads: the innermost
111/// [`Cfg::scope`], or nothing enabled outside of one.
112pub(crate) fn active() -> Cfg {
113    ACTIVE.with(|stack| stack.borrow().last().cloned().unwrap_or_default())
114}