derive_deftly_macros/
beta.rs

1//! Handle `beta_deftly` template option, when `beta` cargo feature enabled
2//!
3//! For instructions on adding a beta feature,
4//! see [`beta::Enabled`].
5//!
6//! This is a bit annoying.  It has to be an ambient property,
7//! so that the syn `Parse` trait can be implemented.
8
9use super::prelude::*;
10
11use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
12
13/// Token indicating that beta feature(s) are or can be enabled
14///
15/// When adding a new beta feature:
16///
17///  * Put an instance of [`beta::Enabled`]
18///    in the appropriate piece of parsed template syntax,
19///    For example, in the [`SubstDetails`](super::syntax::SubstDetails)
20///    along with the `O::` markers.
21///
22///  * When parsing, obtain the value from [`Enabled::new_for_syntax`].
23///
24///  * Add a test case to `tests/minimal-ui/disabled.rs`
25///    which *omits* the `beta_deftly` option, and therefore fails,
26///    thus demonstrating that the feature gate works as intended.
27///
28#[derive(Debug, Copy, Clone, Eq, PartialEq)]
29#[non_exhaustive]
30pub struct Enabled {}
31
32pub type MaybeEnabled = Result<Enabled, ErrorGenerator<'static>>;
33
34#[derive(Copy, Clone)]
35enum ThreadState {
36    Unset,
37    Set(MaybeEnabled),
38}
39
40use ThreadState as TS;
41
42thread_local! {
43    static ENABLED: Cell<ThreadState> = Cell::new(TS::Unset);
44}
45
46/// Workaround for `LazyStatic<Cell>::get` MSRV of 1.73.0.
47fn threadlocal_get() -> ThreadState {
48    ENABLED.with(|c| c.get())
49}
50fn threadlocal_set(s: ThreadState) {
51    ENABLED.with(|c| c.set(s))
52}
53
54/// Call `f` with beta features enabled or not
55///
56/// Used by the parser for `TopTemplate`
57pub fn with_maybe_enabled<R>(
58    enabled: MaybeEnabled,
59    f: impl FnOnce() -> R,
60) -> R {
61    assert!(matches!(threadlocal_get(), TS::Unset));
62    threadlocal_set(TS::Set(enabled));
63    // Unwind safety: we re-throw the panic,
64    // so even if f or R wasn't, no-one observes any broken invariant.
65    let r = catch_unwind(AssertUnwindSafe(f));
66    threadlocal_set(TS::Unset);
67    match r {
68        Ok(r) => r,
69        Err(e) => resume_unwind(e),
70    }
71}
72
73impl Enabled {
74    /// If the cargo feature is enabled, return `Ok(Enabled)`
75    ///
76    /// Used when parsing the `beta_deftly` template option.
77    //
78    // (In this version of the source code it *always* returns Ok.
79    // Returning Err is done by beta_disabled.rs.)
80    pub fn new_for_dd_option(_: Span) -> syn::Result<Self> {
81        Ok(Enabled {})
82    }
83
84    /// If the cargo feature is enabled, return `Ok(Enabled)`
85    ///
86    /// Used for things that are inherently part of the modules feature,
87    /// such as `use`s and `define_derive_deftly_module!`.
88    //
89    // Remove when modules are no longer beta
90    //
91    // (In this version of the source code it *always* returns Ok.
92    // Returning Err is done by beta_disabled.rs.)
93    pub fn new_for_modules_feature(_: Span) -> syn::Result<Self> {
94        Ok(Enabled {})
95    }
96
97    /// If the cargo feature is enabled, return `Some`.
98    ///
99    /// Used when parsing template text imported from modules.
100    ///
101    /// The rule is that modules have their own `beta_deftly` option,
102    /// and beta features are allowed in module text iff the module
103    /// enabled them.  The importing template doesn't need to.
104    //
105    // (In this version of the source code it *always* returns Ok.
106    // Returning Err is done by beta_disabled.rs.)
107    pub fn new_for_imported_definitions() -> MaybeEnabled {
108        Ok(Enabled {})
109    }
110
111    /// If the `beta_deftly` template feature is enabled, return `Ok(Enabled)`
112    ///
113    /// Used when parsing beta syntax, in templates.
114    #[allow(dead_code)] // sometimes we might not have any beta features
115    pub fn new_for_syntax(span: Span) -> syn::Result<Self> {
116        match threadlocal_get() {
117            TS::Unset => {
118                Err(span.error("internal error! beta::ENABLED Unset"))
119            }
120            TS::Set(ue) => ue.map_err(|eh| eh(span)),
121        }
122    }
123
124    /// Makes `new_for_syntax` work properly within `f`, in test cases
125    #[cfg(test)]
126    #[allow(dead_code)]
127    pub fn test_with_parsing<R>(f: impl FnOnce() -> R) -> R {
128        with_maybe_enabled(Ok(Enabled {}), f)
129    }
130}