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}