diff --git a/examples/form.rs b/examples/form.rs index 6e84c1e..a5f29aa 100644 --- a/examples/form.rs +++ b/examples/form.rs @@ -1,6 +1,6 @@ use iocraft::prelude::*; -#[props] +#[derive(Default, Props)] struct FormFieldProps { label: String, value: Option>, @@ -37,7 +37,7 @@ fn FormField(props: &FormFieldProps) -> impl Into> { } } -#[props] +#[derive(Default, Props)] struct FormProps<'a> { first_name_out: Option<&'a mut String>, last_name_out: Option<&'a mut String>, diff --git a/examples/table.rs b/examples/table.rs index faceee8..f06fbab 100644 --- a/examples/table.rs +++ b/examples/table.rs @@ -17,7 +17,7 @@ impl User { } } -#[props] +#[derive(Default, Props)] struct UsersTableProps<'a> { users: Option<&'a Vec>, } diff --git a/packages/iocraft-macros/src/lib.rs b/packages/iocraft-macros/src/lib.rs index 044f925..316b087 100644 --- a/packages/iocraft-macros/src/lib.rs +++ b/packages/iocraft-macros/src/lib.rs @@ -127,18 +127,18 @@ pub fn element(input: TokenStream) -> TokenStream { quote!(#element).into() } -struct ParsedCovariant { +struct ParsedProps { def: ItemStruct, } -impl Parse for ParsedCovariant { +impl Parse for ParsedProps { fn parse(input: ParseStream) -> Result { let def: ItemStruct = input.parse()?; Ok(Self { def }) } } -impl ToTokens for ParsedCovariant { +impl ToTokens for ParsedProps { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let def = &self.def; let name = &def.ident; @@ -231,43 +231,18 @@ impl ToTokens for ParsedCovariant { } tokens.extend(quote! { - unsafe impl #generics ::iocraft::Covariant for #name #bracketed_generic_names #where_clause {} + unsafe impl #generics ::iocraft::Props for #name #bracketed_generic_names #where_clause {} }); } } -/// Makes a struct as being covariant. If the struct is not actually covariant, compilation will fail. -#[proc_macro_derive(Covariant)] +/// Makes a struct available for use as component properties. +/// +/// Most importantly, this marks a struct as being +/// [covariant](https://doc.rust-lang.org/nomicon/subtyping.html). If the struct is not actually +/// covariant, compilation will fail. +#[proc_macro_derive(Props)] pub fn derive_covariant_type(item: TokenStream) -> TokenStream { - let props = parse_macro_input!(item as ParsedCovariant); - quote!(#props).into() -} - -struct ParsedProps { - props: ItemStruct, -} - -impl Parse for ParsedProps { - fn parse(input: ParseStream) -> Result { - let props: ItemStruct = input.parse()?; - Ok(Self { props }) - } -} - -impl ToTokens for ParsedProps { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let props = &self.props; - - tokens.extend(quote! { - #[derive(Default, ::iocraft::Covariant)] - #props - }); - } -} - -/// Defines a struct containing properties to be accepted by components. -#[proc_macro_attribute] -pub fn props(_attr: TokenStream, item: TokenStream) -> TokenStream { let props = parse_macro_input!(item as ParsedProps); quote!(#props).into() } diff --git a/packages/iocraft-macros/tests/component.rs b/packages/iocraft-macros/tests/component.rs index f0e531e..044df53 100644 --- a/packages/iocraft-macros/tests/component.rs +++ b/packages/iocraft-macros/tests/component.rs @@ -1,14 +1,14 @@ #![allow(dead_code)] use iocraft::{components::Box, AnyElement, Hooks}; -use iocraft_macros::{component, element, props}; +use iocraft_macros::{component, element, Props}; #[component] fn MyComponent() -> impl Into> { element!(Box) } -#[props] +#[derive(Default, Props)] struct MyProps { foo: String, } diff --git a/packages/iocraft-macros/tests/element.rs b/packages/iocraft-macros/tests/element.rs index 76a5ce2..67f7daa 100644 --- a/packages/iocraft-macros/tests/element.rs +++ b/packages/iocraft-macros/tests/element.rs @@ -1,11 +1,11 @@ #![allow(dead_code)] -use iocraft::{element, AnyElement, Component, Covariant, Element, Percent}; +use iocraft::{element, AnyElement, Component, Element, Percent, Props}; #[derive(Default)] struct MyComponent; -#[derive(Covariant, Default)] +#[derive(Default, Props)] struct MyComponentProps { foo: String, percent: Percent, @@ -22,7 +22,7 @@ impl Component for MyComponent { struct MyContainer; -#[derive(Covariant, Default)] +#[derive(Default, Props)] struct MyContainerProps { children: Vec>, } diff --git a/packages/iocraft-macros/tests/covariant.rs b/packages/iocraft-macros/tests/props.rs similarity index 73% rename from packages/iocraft-macros/tests/covariant.rs rename to packages/iocraft-macros/tests/props.rs index e6ac3a6..2a72f62 100644 --- a/packages/iocraft-macros/tests/covariant.rs +++ b/packages/iocraft-macros/tests/props.rs @@ -1,22 +1,22 @@ #![allow(dead_code)] -use iocraft_macros::Covariant; +use iocraft_macros::Props; -#[derive(Covariant)] +#[derive(Props)] struct Unit; -#[derive(Covariant)] +#[derive(Props)] struct BasicStruct { foo: i32, } -#[derive(Covariant)] +#[derive(Props)] struct StructWithLifetime<'lt> { foo: &'lt i32, bar: &'lt mut i32, } -#[derive(Covariant)] +#[derive(Props)] struct StructWithLifetimeAndConsts<'lt, const N: usize, const M: usize> { foo: &'lt i32, bar: &'lt mut i32, @@ -24,12 +24,12 @@ struct StructWithLifetimeAndConsts<'lt, const N: usize, const M: usize> { qux: [i32; M], } -#[derive(Covariant)] +#[derive(Props)] struct StructWithTypeGeneric { foo: T, } -#[derive(Covariant)] +#[derive(Props)] struct StructWithLifetimeAndTypeGeneric<'lt, T> { foo: &'lt T, } diff --git a/packages/iocraft-macros/tests/with_layout_style_props.rs b/packages/iocraft-macros/tests/with_layout_style_props.rs index 7eb0e70..169e36e 100644 --- a/packages/iocraft-macros/tests/with_layout_style_props.rs +++ b/packages/iocraft-macros/tests/with_layout_style_props.rs @@ -1,20 +1,20 @@ use iocraft::Display; -use iocraft_macros::{props, with_layout_style_props}; +use iocraft_macros::{with_layout_style_props, Props}; #[with_layout_style_props] -#[props] +#[derive(Default, Props)] struct MyProps { foo: String, } #[with_layout_style_props] -#[props] +#[derive(Default, Props)] struct MyPropsWithLifetime<'lt> { foo: Option<&'lt str>, } #[with_layout_style_props] -#[props] +#[derive(Default, Props)] struct MyPropsWithTypeGeneric { foo: Option, } diff --git a/packages/iocraft/src/component.rs b/packages/iocraft/src/component.rs index 7bd85d1..ca1428d 100644 --- a/packages/iocraft/src/component.rs +++ b/packages/iocraft/src/component.rs @@ -2,7 +2,7 @@ use crate::{ context::ContextStack, element::{ElementKey, ElementType}, hook::{AnyHook, Hook, Hooks}, - props::{AnyProps, Covariant}, + props::{AnyProps, Props}, render::{ComponentDrawer, ComponentUpdater, UpdateContext}, }; use futures::future::poll_fn; @@ -72,7 +72,7 @@ impl ComponentHelperExt for ComponentHelper { /// level component type definitions. pub trait Component: Any + Unpin { /// The type of properties that the component accepts. - type Props<'a>: Covariant + type Props<'a>: Props where Self: 'a; diff --git a/packages/iocraft/src/components/box.rs b/packages/iocraft/src/components/box.rs index 120f2cb..b7965af 100644 --- a/packages/iocraft/src/components/box.rs +++ b/packages/iocraft/src/components/box.rs @@ -1,6 +1,6 @@ use crate::{ - AnyElement, CanvasTextStyle, Color, Component, ComponentDrawer, ComponentUpdater, Covariant, - Edges, Hooks, + AnyElement, CanvasTextStyle, Color, Component, ComponentDrawer, ComponentUpdater, Edges, Hooks, + Props, }; use iocraft_macros::with_layout_style_props; use taffy::{LengthPercentage, Rect}; @@ -137,7 +137,7 @@ impl BorderStyle { /// The props which can be passed to the [`Box`] component. #[with_layout_style_props] -#[derive(Covariant, Default)] +#[derive(Default, Props)] pub struct BoxProps<'a> { /// The elements to render inside of the box. pub children: Vec>, diff --git a/packages/iocraft/src/components/context_provider.rs b/packages/iocraft/src/components/context_provider.rs index 1162c91..6aa6ba4 100644 --- a/packages/iocraft/src/components/context_provider.rs +++ b/packages/iocraft/src/components/context_provider.rs @@ -1,7 +1,7 @@ -use crate::{AnyElement, Component, ComponentUpdater, Context, Covariant, Hooks}; +use crate::{AnyElement, Component, ComponentUpdater, Context, Hooks, Props}; /// The props which can be passed to the [`ContextProvider`] component. -#[derive(Covariant, Default)] +#[derive(Default, Props)] pub struct ContextProviderProps<'a> { /// The children of the component. pub children: Vec>, diff --git a/packages/iocraft/src/components/text.rs b/packages/iocraft/src/components/text.rs index 337ce7c..475238e 100644 --- a/packages/iocraft/src/components/text.rs +++ b/packages/iocraft/src/components/text.rs @@ -1,5 +1,5 @@ use crate::{ - CanvasTextStyle, Color, Component, ComponentDrawer, ComponentUpdater, Covariant, Hooks, Weight, + CanvasTextStyle, Color, Component, ComponentDrawer, ComponentUpdater, Hooks, Props, Weight, }; use taffy::{AvailableSpace, Size}; use unicode_width::UnicodeWidthStr; @@ -37,7 +37,7 @@ pub enum TextDecoration { } /// The props which can be passed to the [`Text`] component. -#[derive(Default, Covariant)] +#[derive(Default, Props)] pub struct TextProps { /// The color to make the text. pub color: Option, diff --git a/packages/iocraft/src/components/text_input.rs b/packages/iocraft/src/components/text_input.rs index 4359c7d..f3eef76 100644 --- a/packages/iocraft/src/components/text_input.rs +++ b/packages/iocraft/src/components/text_input.rs @@ -1,6 +1,6 @@ use crate::{ - CanvasTextStyle, Color, Component, ComponentDrawer, ComponentUpdater, Covariant, Handler, - Hooks, KeyCode, KeyEvent, KeyEventKind, TerminalEvent, TerminalEvents, + CanvasTextStyle, Color, Component, ComponentDrawer, ComponentUpdater, Handler, Hooks, KeyCode, + KeyEvent, KeyEventKind, Props, TerminalEvent, TerminalEvents, }; use futures::stream::Stream; use std::{ @@ -10,7 +10,7 @@ use std::{ use unicode_width::UnicodeWidthStr; /// The props which can be passed to the [`TextInput`] component. -#[derive(Default, Covariant)] +#[derive(Default, Props)] pub struct TextInputProps { /// The color to make the text. pub color: Option, diff --git a/packages/iocraft/src/lib.rs b/packages/iocraft/src/lib.rs index ea6f1f7..d955cbb 100644 --- a/packages/iocraft/src/lib.rs +++ b/packages/iocraft/src/lib.rs @@ -11,7 +11,7 @@ //! - Output colored and styled UIs to the terminal or ASCII output anywhere else. //! - Create animated or interactive elements with event handling and hooks. //! - Build fullscreen terminal applications with ease. -//! - Pass [props](macro@props) and [context](crate::components::ContextProvider) by reference to avoid unnecessary cloning. +//! - Pass [props](crate::Props) and [context](crate::components::ContextProvider) by reference to avoid unnecessary cloning. //! //! ## Getting Started //! diff --git a/packages/iocraft/src/props.rs b/packages/iocraft/src/props.rs index 2a2b9bf..42ff80b 100644 --- a/packages/iocraft/src/props.rs +++ b/packages/iocraft/src/props.rs @@ -1,16 +1,51 @@ use std::marker::PhantomData; -/// This trait marks a type as being covariant. +/// This trait makes a struct available for use as component properties. +/// +/// # Examples +/// +/// ``` +/// # use iocraft::prelude::*; +/// #[derive(Default, Props)] +/// struct MyProps { +/// foo: String, +/// } +/// ``` +/// +/// Unowned data is okay too: +/// +/// ``` +/// # use iocraft::prelude::*; +/// #[derive(Default, Props)] +/// struct MyProps<'a> { +/// foo: &'a str, +/// } +/// ``` +/// +/// However, a field that would make the struct +/// [invariant](https://doc.rust-lang.org/nomicon/subtyping.html) is not okay and will not compile: +/// +/// ```compile_fail +/// # use iocraft::prelude::*; +/// # struct MyType<'a> { +/// # _foo: &'a str, +/// # } +/// #[derive(Default, Props)] +/// struct MyProps<'a, 'b> { +/// foo: &'a mut MyType<'b>, +/// } +/// ``` /// /// # Safety /// -/// If the type is not actually covariant, then the safety of the program is compromised. You can -/// use the `#[derive(Covariant)]` macro to implement this trait safely. If the type is not -/// actually covariant, the derive macro will not compile. -pub unsafe trait Covariant {} +/// This requires the type to be [covariant](https://doc.rust-lang.org/nomicon/subtyping.html). If +/// implemented for a type that is not actually covariant, then the safety of the program is +/// compromised. You can use the `#[derive(Props)]` macro to implement this trait safely. If the +/// type is not actually covariant, the derive macro will give you an error at compile-time. +pub unsafe trait Props {} #[doc(hidden)] -#[derive(Clone, Copy, iocraft_macros::Covariant, Default)] +#[derive(Clone, Copy, iocraft_macros::Props, Default)] pub struct NoProps; struct DropRawImpl { @@ -37,7 +72,7 @@ pub struct AnyProps<'a> { } impl<'a> AnyProps<'a> { - pub(crate) fn owned(props: T) -> Self { + pub(crate) fn owned(props: T) -> Self { let raw = Box::into_raw(Box::new(props)); Self { raw: raw as *mut (), @@ -48,7 +83,7 @@ impl<'a> AnyProps<'a> { } } - pub(crate) fn borrowed(props: &'a mut T) -> Self { + pub(crate) fn borrowed(props: &'a mut T) -> Self { Self { raw: props as *const T as *mut (), drop: None, @@ -56,11 +91,11 @@ impl<'a> AnyProps<'a> { } } - pub(crate) unsafe fn downcast_ref_unchecked(&self) -> &T { + pub(crate) unsafe fn downcast_ref_unchecked(&self) -> &T { unsafe { &*(self.raw as *const T) } } - pub(crate) unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { + pub(crate) unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { unsafe { &mut *(self.raw as *mut T) } }