fix Option<String> in props

This commit is contained in:
Evan Almloff 2024-01-24 16:40:14 -06:00
parent 30ef29d195
commit c3555a7ec0
6 changed files with 131 additions and 15 deletions

View file

@ -1,7 +1,3 @@
//! Example: README.md showcase
//!
//! The example from the README.md.
use dioxus::prelude::*;
fn main() {
@ -12,8 +8,20 @@ fn app() -> Element {
let mut count = use_signal(|| 0);
rsx! {
h1 { "High-Five counter: {count}" }
Child { count: "High-Five counter: {count}" }
Child { count: "count" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
}
}
#[derive(Props, Clone, PartialEq)]
struct ChildProps {
count: Option<String>,
}
fn Child(props: ChildProps) -> Element {
rsx! {
h1 { "{props.count.unwrap_or_default()}" }
}
}

View file

@ -3,8 +3,8 @@
//! However, it has been adopted to fit the Dioxus Props builder pattern.
//!
//! For Dioxus, we make a few changes:
//! - [ ] Automatically implement Into<Option> on the setters (IE the strip setter option)
//! - [ ] Automatically implement a default of none for optional fields (those explicitly wrapped with Option<T>)
//! - [x] Automatically implement Into<Option> on the setters (IE the strip setter option)
//! - [x] Automatically implement a default of none for optional fields (those explicitly wrapped with Option<T>)
use proc_macro2::TokenStream;
@ -956,6 +956,7 @@ Finally, call `.build()` to create the instance of `{name}`.
index_after_lifetime_in_generics,
syn::GenericArgument::Type(ty_generics_tuple.into()),
);
let (impl_generics, _, where_clause) = generics.split_for_impl();
let doc = match field.builder_attr.doc {
Some(ref doc) => quote!(#[doc = #doc]),
@ -963,11 +964,15 @@ Finally, call `.build()` to create the instance of `{name}`.
};
let arg_type = field_type;
// If the field is auto_into, we need to add a generic parameter to the builder for specialization
let mut marker = None;
let (arg_type, arg_expr) =
if field.builder_attr.auto_into || field.builder_attr.strip_option {
let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site());
marker = Some(marker_ident.clone());
(
quote!(impl ::core::convert::Into<#arg_type>),
quote!(#field_name.into()),
quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>),
quote!(#field_name.super_into()),
)
} else if field.builder_attr.from_displayable {
(
@ -998,7 +1003,7 @@ Finally, call `.build()` to create the instance of `{name}`.
impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
#doc
#[allow(clippy::type_complexity)]
pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
pub fn #field_name < #marker > (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
let #field_name = (#arg_expr,);
let ( #(#descructuring,)* ) = self.fields;
#builder_name {
@ -1018,7 +1023,7 @@ Finally, call `.build()` to create the instance of `{name}`.
note = #repeated_fields_error_message
)]
#[allow(clippy::type_complexity)]
pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {
pub fn #field_name< #marker > (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {
self
}
}

View file

@ -91,8 +91,9 @@ pub mod prelude {
remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
try_consume_context, use_drop, use_error_boundary, use_hook, use_hook_with_cleanup,
AnyValue, Attribute, Component, ComponentFunction, Element, ErrorBoundary, Event,
EventHandler, Fragment, HasAttributes, IntoAttributeValue, IntoDynNode, Properties,
Runtime, RuntimeGuard, ScopeId, ScopeState, Task, Template, TemplateAttribute,
TemplateNode, Throw, VNode, VNodeInner, VirtualDom,
EventHandler, Fragment, HasAttributes, IntoAttributeValue, IntoDynNode,
OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, SuperFrom,
SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner,
VirtualDom,
};
}

View file

@ -1,4 +1,4 @@
use std::any::TypeId;
use std::{any::TypeId, fmt::Arguments};
use crate::innerlude::*;
@ -118,3 +118,74 @@ impl<F: Fn() -> Element + Clone + 'static> ComponentFunction<(), EmptyMarker> fo
self()
}
}
/// A enhanced version of the `Into` trait that allows with more flexibility.
pub trait SuperInto<O, M = ()> {
/// Convert from a type to another type.
fn super_into(self) -> O;
}
impl<T, O, M> SuperInto<O, M> for T
where
O: SuperFrom<T, M>,
{
fn super_into(self) -> O {
O::super_from(self)
}
}
/// A enhanced version of the `From` trait that allows with more flexibility.
pub trait SuperFrom<T, M = ()> {
/// Convert from a type to another type.
fn super_from(_: T) -> Self;
}
// first implement for all types that are that implement the From trait
impl<T, O> SuperFrom<T, ()> for O
where
O: From<T>,
{
fn super_from(input: T) -> Self {
Self::from(input)
}
}
#[doc(hidden)]
pub struct OptionStringFromMarker;
impl<'a> SuperFrom<&'a str, OptionStringFromMarker> for Option<String> {
fn super_from(input: &'a str) -> Self {
Some(String::from(input))
}
}
#[doc(hidden)]
pub struct OptionArgumentsFromMarker;
impl<'a> SuperFrom<Arguments<'a>, OptionArgumentsFromMarker> for Option<String> {
fn super_from(input: Arguments<'a>) -> Self {
Some(input.to_string())
}
}
#[test]
#[allow(unused)]
fn from_props_compiles() {
// T -> T works
let option: i32 = 0i32.super_into();
let option: i32 = 0.super_into(); // Note we don't need type hints on all inputs
let option: i128 = 0.super_into();
let option: &'static str = "hello world".super_into();
// // T -> From<T> works
let option: i64 = 0i32.super_into();
let option: String = "hello world".super_into();
// T -> Option works
let option: Option<i32> = 0i32.super_into();
let option: Option<i32> = 0.super_into();
let option: Option<i128> = 0.super_into();
fn takes_option_string<M>(_: impl SuperInto<Option<String>, M>) {}
takes_option_string("hello world");
takes_option_string("hello world".to_string());
}

View file

@ -39,3 +39,6 @@ pub use read::*;
mod write;
pub use write::*;
mod props;
pub use props::*;

View file

@ -0,0 +1,28 @@
use crate::Signal;
use dioxus_core::prelude::*;
#[doc(hidden)]
pub struct SignalFromMarker<M>(std::marker::PhantomData<M>);
impl<T, O, M> SuperFrom<T, SignalFromMarker<M>> for Signal<O>
where
O: SuperFrom<T, M>,
{
fn super_from(input: T) -> Self {
Signal::new(O::super_from(input))
}
}
#[test]
#[allow(unused)]
fn into_signal_compiles() {
fn takes_signal_string<M>(_: impl SuperInto<Signal<String>, M>) {}
fn takes_option_signal_string<M>(_: impl SuperInto<Signal<Option<String>>, M>) {}
fn don_t_run() {
takes_signal_string("hello world");
takes_signal_string(Signal::new(String::from("hello world")));
takes_option_signal_string("hello world");
}
}