gpui_macros.rs

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