gpui_macros/gpui_macros.rs
1mod bench;
2mod derive_action;
3mod derive_app_context;
4mod derive_into_element;
5mod derive_render;
6mod derive_visual_context;
7mod property_test;
8mod register_action;
9mod styles;
10mod test;
11
12#[cfg(any(feature = "inspector", debug_assertions))]
13mod derive_inspector_reflection;
14
15use proc_macro::TokenStream;
16use syn::{DeriveInput, Ident};
17
18/// `Action` derive macro - see the trait documentation for details.
19#[proc_macro_derive(Action, attributes(action))]
20pub fn derive_action(input: TokenStream) -> TokenStream {
21 derive_action::derive_action(input)
22}
23
24/// This can be used to register an action with the GPUI runtime when you want to manually implement
25/// the `Action` trait. Typically you should use the `Action` derive macro or `actions!` macro
26/// instead.
27#[proc_macro]
28pub fn register_action(ident: TokenStream) -> TokenStream {
29 register_action::register_action(ident)
30}
31
32/// #[derive(IntoElement)] generates an `IntoElement` impl for any `RenderOnce`
33/// type, wrapping it in a `ViewElement` so it can be used as a child.
34#[proc_macro_derive(IntoElement)]
35pub fn derive_into_element(input: TokenStream) -> TokenStream {
36 derive_into_element::derive_into_element(input)
37}
38
39#[proc_macro_derive(Render)]
40#[doc(hidden)]
41pub fn derive_render(input: TokenStream) -> TokenStream {
42 derive_render::derive_render(input)
43}
44
45/// #[derive(AppContext)] is used to create a context out of anything that holds a `&mut App`
46/// Note that a `#[app]` attribute is required to identify the variable holding the &mut App.
47///
48/// Failure to add the attribute causes a compile error:
49///
50/// ```compile_fail
51/// # #[macro_use] extern crate gpui_macros;
52/// # #[macro_use] extern crate gpui;
53/// #[derive(AppContext)]
54/// struct MyContext<'a> {
55/// app: &'a mut gpui::App
56/// }
57/// ```
58#[proc_macro_derive(AppContext, attributes(app))]
59pub fn derive_app_context(input: TokenStream) -> TokenStream {
60 derive_app_context::derive_app_context(input)
61}
62
63/// #[derive(VisualContext)] is used to create a visual context out of anything that holds a `&mut Window` and
64/// implements `AppContext`
65/// Note that a `#[app]` and a `#[window]` attribute are required to identify the variables holding the &mut App,
66/// and &mut Window respectively.
67///
68/// Failure to add both attributes causes a compile error:
69///
70/// ```compile_fail
71/// # #[macro_use] extern crate gpui_macros;
72/// # #[macro_use] extern crate gpui;
73/// #[derive(VisualContext)]
74/// struct MyContext<'a, 'b> {
75/// #[app]
76/// app: &'a mut gpui::App,
77/// window: &'b mut gpui::Window
78/// }
79/// ```
80///
81/// ```compile_fail
82/// # #[macro_use] extern crate gpui_macros;
83/// # #[macro_use] extern crate gpui;
84/// #[derive(VisualContext)]
85/// struct MyContext<'a, 'b> {
86/// app: &'a mut gpui::App,
87/// #[window]
88/// window: &'b mut gpui::Window
89/// }
90/// ```
91#[proc_macro_derive(VisualContext, attributes(window, app))]
92pub fn derive_visual_context(input: TokenStream) -> TokenStream {
93 derive_visual_context::derive_visual_context(input)
94}
95
96/// Used by GPUI to generate the style helpers.
97#[proc_macro]
98#[doc(hidden)]
99pub fn style_helpers(input: TokenStream) -> TokenStream {
100 styles::style_helpers(input)
101}
102
103/// Generates methods for visibility styles.
104#[proc_macro]
105pub fn visibility_style_methods(input: TokenStream) -> TokenStream {
106 styles::visibility_style_methods(input)
107}
108
109/// Generates methods for margin styles.
110#[proc_macro]
111pub fn margin_style_methods(input: TokenStream) -> TokenStream {
112 styles::margin_style_methods(input)
113}
114
115/// Generates methods for padding styles.
116#[proc_macro]
117pub fn padding_style_methods(input: TokenStream) -> TokenStream {
118 styles::padding_style_methods(input)
119}
120
121/// Generates methods for position styles.
122#[proc_macro]
123pub fn position_style_methods(input: TokenStream) -> TokenStream {
124 styles::position_style_methods(input)
125}
126
127/// Generates methods for overflow styles.
128#[proc_macro]
129pub fn overflow_style_methods(input: TokenStream) -> TokenStream {
130 styles::overflow_style_methods(input)
131}
132
133/// Generates methods for cursor styles.
134#[proc_macro]
135pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
136 styles::cursor_style_methods(input)
137}
138
139/// Generates methods for border styles.
140#[proc_macro]
141pub fn border_style_methods(input: TokenStream) -> TokenStream {
142 styles::border_style_methods(input)
143}
144
145/// Generates methods for box shadow styles.
146#[proc_macro]
147pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
148 styles::box_shadow_style_methods(input)
149}
150
151/// `#[gpui::test]` can be used to annotate test functions that run with GPUI support.
152///
153/// It supports both synchronous and asynchronous tests, and can provide you with
154/// as many `TestAppContext` instances as you need.
155/// The output contains a `#[test]` annotation so this can be used with any existing
156/// test harness (`cargo test` or `cargo-nextest`).
157///
158/// ```
159/// #[gpui::test]
160/// async fn test_foo(mut cx: &TestAppContext) { }
161/// ```
162///
163/// In addition to passing a TestAppContext, you can also ask for a `StdRnd` instance.
164/// this will be seeded with the `SEED` environment variable and is used internally by
165/// the ForegroundExecutor and BackgroundExecutor to run tasks deterministically in tests.
166/// Using the same `StdRng` for behavior in your test will allow you to exercise a wide
167/// variety of scenarios and interleavings just by changing the seed.
168///
169/// # Arguments
170///
171/// - `#[gpui::test]` with no arguments runs once with the seed `0` or `SEED` env var if set.
172/// - `#[gpui::test(seed = 10)]` runs once with the seed `10`.
173/// - `#[gpui::test(seeds(10, 20, 30))]` runs three times with seeds `10`, `20`, and `30`.
174/// - `#[gpui::test(iterations = 5)]` runs five times, providing as seed the values in the range `0..5`.
175/// - `#[gpui::test(retries = 3)]` runs up to four times if it fails to try and make it pass.
176/// - `#[gpui::test(on_failure = "crate::test::report_failure")]` will call the specified function after the
177/// tests fail so that you can write out more detail about the failure.
178///
179/// You can combine `iterations = ...` with `seeds(...)`:
180/// - `#[gpui::test(iterations = 5, seed = 10)]` is equivalent to `#[gpui::test(seeds(0, 1, 2, 3, 4, 10))]`.
181/// - `#[gpui::test(iterations = 5, seeds(10, 20, 30)]` is equivalent to `#[gpui::test(seeds(0, 1, 2, 3, 4, 10, 20, 30))]`.
182/// - `#[gpui::test(seeds(10, 20, 30), iterations = 5]` is equivalent to `#[gpui::test(seeds(0, 1, 2, 3, 4, 10, 20, 30))]`.
183///
184/// # Environment Variables
185///
186/// - `SEED`: sets a seed for the first run
187/// - `ITERATIONS`: forces the value of the `iterations` argument
188#[proc_macro_attribute]
189pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
190 test::test(args, function)
191}
192
193/// `#[gpui::bench]` annotates a Criterion benchmark that runs with GPUI support.
194///
195/// Use `#[gpui::bench(inputs = some_iterable())]` on benchmarks that take an
196/// additional input argument; the generated benchmark uses Criterion's
197/// `bench_with_input`. `group`, `input_name`, and `sample_size` can customize
198/// the generated input benchmark group.
199///
200/// A benchmark that takes a `StdRng` parameter (by value, in any position) is run
201/// once per seed, with the RNG seeded from it. Seeds are chosen exactly as for
202/// `#[gpui::test]`: `seed = N`, `seeds(...)`, and `iterations = N` take the same
203/// meaning, and the `SEED` and `ITERATIONS` environment variables override them the
204/// same way, so one `SEED` reproduces a test or a benchmark. Each seed is its own
205/// Criterion benchmark, named `<input>/seed-<seed>` (or `<function>/seed-<seed>`
206/// without inputs), and the RNG is rebuilt for every Criterion routine call so all
207/// samples measure the same seed. The generated code seeds it through gpui's own
208/// `rand`, so the crate needs `rand` only to name `StdRng` in the signature.
209///
210/// ```ignore
211/// #[gpui::bench(inputs = families(), iterations = 6)]
212/// fn full_refresh(family: &Family, mut rng: StdRng, cx: &mut BenchAppContext) {
213/// let tree = family.sample(&mut rng);
214/// // ...
215/// }
216/// ```
217///
218/// The benchmark crate must add `criterion` and `gpui` (with its
219/// `test-support` feature) to its dev-dependencies and enable gpui's `bench`
220/// feature, since the generated code references all three.
221#[proc_macro_attribute]
222pub fn bench(args: TokenStream, function: TokenStream) -> TokenStream {
223 bench::bench(args, function)
224}
225
226/// A variant of `#[gpui::test]` that supports property-based testing.
227///
228/// A property test, much like a standard GPUI randomized test, allows testing
229/// claims of the form "for any possible X, Y should hold". For example:
230/// ```
231/// #[gpui::property_test]
232/// fn test_arithmetic(x: i32, y: i32) {
233/// assert!(x == y || x < y || x > y);
234/// }
235/// ```
236/// Standard GPUI randomized tests provide you with an instance of `StdRng` to
237/// generate random data in a controlled manner. Property-based tests have some
238/// advantages, however:
239/// - Shrinking - the harness also understands a notion of the "complexity" of a
240/// particular value. This allows it to find the "simplest possible value that
241/// causes the test to fail".
242/// - Ergonomics/clarity - the property-testing harness will automatically
243/// generate values, removing the need to fill the test body with generation
244/// logic.
245/// - Failure persistence - if a failing seed is identified, it is stored in a
246/// file, which can be checked in, and future runs will check these cases before
247/// future cases.
248///
249/// Property tests work best when all inputs can be generated up-front and kept
250/// in a simple data structure. Sometimes, this isn't possible - for example, if
251/// a test needs to make a random decision based on the current state of some
252/// structure. In this case, a standard GPUI randomized test may be more
253/// suitable.
254///
255/// ## Customizing random values
256///
257/// This macro is based on the [`#[proptest::property_test]`] macro, but handles
258/// some of the same GPUI-specific arguments as `#[gpui::test]`. Specifically,
259/// `&{mut,} TestAppContext` and `BackgroundExecutor` work as normal. `StdRng`
260/// arguments are **explicitly forbidden**, since they break shrinking, and are
261/// a common footgun.
262///
263/// All other arguments are forwarded to the underlying proptest macro.
264///
265/// Note: much of the following is copied from the proptest docs, specifically the
266/// [`#[proptest::property_test]`] macro docs.
267///
268/// Random values of type `T` are generated by a `Strategy<Value = T>` object.
269/// Some types have a canonical `Strategy` - these types also implement
270/// `Arbitrary`. Parameters to a `#[gpui::property_test]`, by default, use a
271/// type's `Arbitrary` implementation. If you'd like to provide a custom
272/// strategy, you can use `#[strategy = ...]` on the argument:
273/// ```
274/// #[gpui::property_test]
275/// fn int_test(#[strategy = 1..10] x: i32, #[strategy = "[a-zA-Z0-9]{20}"] s: String) {
276/// assert!(s.len() > (x as usize));
277/// }
278/// ```
279///
280/// For more information on writing custom `Strategy` and `Arbitrary`
281/// implementations, see [the proptest book][book], and the [`Strategy`] trait.
282///
283/// ## Scheduler
284///
285/// Similar to `#[gpui::test]`, this macro will choose random seeds for the test
286/// scheduler. It uses `.no_shrink()` to tell proptest that all seeds are
287/// roughly equivalent in terms of "complexity". If `$SEED` is set, it will
288/// affect **ONLY** the seed passed to the scheduler. To control other values,
289/// use custom `Strategy`s.
290///
291/// [`#[proptest::property_test]`]: https://docs.rs/proptest/latest/proptest/attr.property_test.html
292/// [book]: https://proptest-rs.github.io/proptest/intro.html
293/// [`Strategy`]: https://docs.rs/proptest/latest/proptest/strategy/trait.Strategy.html
294#[proc_macro_attribute]
295pub fn property_test(args: TokenStream, function: TokenStream) -> TokenStream {
296 property_test::test(args.into(), function.into()).into()
297}
298
299/// When added to a trait, `#[derive_inspector_reflection]` generates a module which provides
300/// enumeration and lookup by name of all methods that have the shape `fn method(self) -> Self`.
301/// This is used by the inspector so that it can use the builder methods in `Styled` and
302/// `StyledExt`.
303///
304/// The generated module will have the name `<snake_case_trait_name>_reflection` and contain the
305/// following functions:
306///
307/// ```ignore
308/// pub fn methods::<T: TheTrait + 'static>() -> Vec<gpui::inspector_reflection::FunctionReflection<T>>;
309///
310/// pub fn find_method::<T: TheTrait + 'static>() -> Option<gpui::inspector_reflection::FunctionReflection<T>>;
311/// ```
312///
313/// The `invoke` method on `FunctionReflection` will run the method. `FunctionReflection` also
314/// provides the method's documentation.
315#[cfg(any(feature = "inspector", debug_assertions))]
316#[proc_macro_attribute]
317pub fn derive_inspector_reflection(_args: TokenStream, input: TokenStream) -> TokenStream {
318 derive_inspector_reflection::derive_inspector_reflection(_args, input)
319}
320
321pub(crate) fn get_simple_attribute_field(ast: &DeriveInput, name: &'static str) -> Option<Ident> {
322 match &ast.data {
323 syn::Data::Struct(data_struct) => data_struct
324 .fields
325 .iter()
326 .find(|field| field.attrs.iter().any(|attr| attr.path().is_ident(name)))
327 .map(|field| field.ident.clone().unwrap()),
328 syn::Data::Enum(_) => None,
329 syn::Data::Union(_) => None,
330 }
331}