derive_deftly_macros/
beta.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! Handle `beta_deftly` template option, when `beta` cargo feature enabled
//!
//! For instructions on adding a beta feature,
//! see [`beta::Enabled`].
//!
//! This is a bit annoying.  It has to be an ambient property,
//! so that the syn `Parse` trait can be implemented.

use super::prelude::*;

use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};

/// Token indicating that beta feature(s) are or can be enabled
///
/// When adding a new beta feature:
///
///  * Put an instance of [`beta::Enabled`]
///    in the appropriate piece of parsed template syntax,
///    For example, in the [`SubstDetails`](super::syntax::SubstDetails)
///    along with the `O::` markers.
///
///  * When parsing, obtain the value from [`Enabled::new_for_syntax`].
///
///  * Add a test case to `tests/minimal-ui/disabled.rs`
///    which *omits* the `beta_deftly` option, and therefore fails,
///    thus demonstrating that the feature gate works as intended.
///
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct Enabled {}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum ThreadState {
    Unset,
    Set(Option<Enabled>),
}

use ThreadState as TS;

thread_local! {
    static ENABLED: Cell<ThreadState> = Cell::new(TS::Unset);
}

/// Workaround for `LazyStatic<Cell>::get` MSRV of 1.73.0.
fn threadlocal_get() -> ThreadState {
    ENABLED.with(|c| c.get())
}
fn threadlocal_set(s: ThreadState) {
    ENABLED.with(|c| c.set(s))
}

/// Call `f` with beta features enabled or not
///
/// Used by the parser for `TopTemplate`
pub fn with_maybe_enabled<R>(
    enabled: Option<Enabled>,
    f: impl FnOnce() -> R,
) -> R {
    assert_eq!(threadlocal_get(), TS::Unset);
    threadlocal_set(TS::Set(enabled));
    // Unwind safety: we re-throw the panic,
    // so even if f or R wasn't, no-one observes any broken invariant.
    let r = catch_unwind(AssertUnwindSafe(f));
    threadlocal_set(TS::Unset);
    match r {
        Ok(r) => r,
        Err(e) => resume_unwind(e),
    }
}

impl Enabled {
    /// If the cargo feature is enabled, return `Ok(Enabled)`
    ///
    /// Used when parsing the `beta_deftly` template option.
    //
    // (In this version of the source code it *always* returns Ok.
    // Returning Err is done by beta_disabled.rs.)
    pub fn new_for_dd_option(_: Span) -> syn::Result<Self> {
        Ok(Enabled {})
    }

    /// If the `beta_deftly` template feature is enabled, return `Ok(Enabled)`
    ///
    /// Used when parsing beta syntax, in templates.
    #[allow(dead_code)] // sometimes we might not have any beta features
    pub fn new_for_syntax(span: Span) -> syn::Result<Self> {
        match threadlocal_get() {
            TS::Unset => {
                Err(span.error("internal error! beta::ENABLED Unset"))
            }
            TS::Set(ue) => ue.ok_or_else(|| {
                span.error(
 "beta derive-deftly feature used, without `beta_deftly` template option"
            )
            }),
        }
    }

    /// Makes `new_for_syntax` work properly within `f`, in test cases
    #[cfg(test)]
    #[allow(dead_code)]
    pub fn test_with_parsing<R>(f: impl FnOnce() -> R) -> R {
        with_maybe_enabled(Some(Enabled {}), f)
    }
}