From 67dc6e6017b1b6ec035b5e766058110e48bfb547 Mon Sep 17 00:00:00 2001 From: Jon Kelley Date: Mon, 12 Sep 2022 22:49:04 -0700 Subject: [PATCH] feat: implement type magic to allow strings, format args, and other types directly in rsx (#550) * feat: implement type magic * chore: undo example * fix: let tests pass * chore: add generic to allow any nesting of iterators * Chore: remove comments * chore: update rsx usage * chore: use cleaner version of generic IntoVnode * chore: don't derive default for lfietimed thing * chore: remove latent comment * fix: accept a third parameter --- examples/rsx_usage.rs | 4 +- examples/simple_list.rs | 19 ++++ packages/autofmt/src/lib.rs | 2 +- packages/core/src/lazynodes.rs | 16 ++- packages/core/src/nodes.rs | 135 +++++++++---------------- packages/dioxus/tests/borrowedstate.rs | 2 +- packages/dioxus/tests/create_dom.rs | 2 +- 7 files changed, 80 insertions(+), 100 deletions(-) create mode 100644 examples/simple_list.rs diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 4f9ab24bb..b4085a2e4 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -203,8 +203,8 @@ fn app(cx: Scope) -> Element { self::lowercase_helper {} // helper functions - // Single values must be wrapped in braces or `Some` to satisfy `IntoIterator` - [helper(&cx, "hello world!")] + // Anything that implements IntoVnode can be dropped directly into Rsx + helper(&cx, "hello world!") } }) } diff --git a/examples/simple_list.rs b/examples/simple_list.rs new file mode 100644 index 000000000..d615e2371 --- /dev/null +++ b/examples/simple_list.rs @@ -0,0 +1,19 @@ +use dioxus::prelude::*; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx!( + // Use Map directly to lazily pull elements + (0..10).map(|f| rsx! { "{f}" }), + // Collect into an intermediate collection if necessary + ["a", "b", "c"] + .into_iter() + .map(|f| rsx! { "{f}" }) + .collect::>(), + // Use optionals + Some(rsx! { "Some" }), + )) +} diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 1eab6240f..7a14eae3e 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -16,7 +16,7 @@ mod util; /// /// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if /// multiple edits are applied in a single file without tracking text shifts. -#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)] +#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq, Hash)] pub struct FormattedBlock { /// The new contents of the block pub formatted: String, diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 8469a564b..23c74eca0 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -288,16 +288,14 @@ mod tests { } let caller = { - let it = (0..10) - .map(|i| { - let val = cx.props.inner.clone(); - LazyNodes::new(move |f| { - eprintln!("hell closure"); - let inner = DropInner { id: i }; - f.text(format_args!("hello world {:?}, {:?}", inner.id, val)) - }) + let it = (0..10).map(|i| { + let val = cx.props.inner.clone(); + LazyNodes::new(move |f| { + eprintln!("hell closure"); + let inner = DropInner { id: i }; + f.text(format_args!("hello world {:?}, {:?}", inner.id, val)) }) - .collect::>(); + }); LazyNodes::new(|f| { eprintln!("main closure"); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 1b19aa878..8bd91fdb1 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -2,7 +2,6 @@ //! //! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very* //! cheap and *very* fast to construct - building a full tree should be quick. - use crate::{ innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState}, lazynodes::LazyNodes, @@ -375,7 +374,6 @@ pub struct Listener<'bump> { pub type InternalHandler<'bump> = &'bump RefCell>>; type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>; - type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>; /// The callback type generated by the `rsx!` macro when an `on` field is specified for components. @@ -706,43 +704,11 @@ impl<'a> NodeFactory<'a> { } /// Create a new [`VNode::Fragment`] from any iterator - pub fn fragment_from_iter<'b, 'c>( + pub fn fragment_from_iter<'c, I, J>( self, - node_iter: impl IntoIterator + 'c> + 'b, + node_iter: impl IntoVNode<'a, I, J> + 'c, ) -> VNode<'a> { - let mut nodes = bumpalo::collections::Vec::new_in(self.bump); - - for node in node_iter { - nodes.push(node.into_vnode(self)); - } - - if nodes.is_empty() { - VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() })) - } else { - let children = nodes.into_bump_slice(); - - if cfg!(debug_assertions) - && children.len() > 1 - && children.last().unwrap().key().is_none() - { - // todo: make the backtrace prettier or remove it altogether - log::error!( - r#" - Warning: Each child in an array or iterator should have a unique "key" prop. - Not providing a key will lead to poor performance with lists. - See docs.rs/dioxus for more information. - ------------- - {:?} - "#, - backtrace::Backtrace::new() - ); - } - - VNode::Fragment(self.bump.alloc(VFragment { - children, - key: None, - })) - } + node_iter.into_vnode(self) } /// Create a new [`VNode`] from any iterator of children @@ -800,20 +766,11 @@ impl Debug for NodeFactory<'_> { /// /// As such, all node creation must go through the factory, which is only available in the component context. /// These strict requirements make it possible to manage lifetimes and state. -pub trait IntoVNode<'a> { +pub trait IntoVNode<'a, I = (), J = ()> { /// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; } -// For the case where a rendered VNode is passed into the rsx! macro through curly braces -impl<'a> IntoIterator for VNode<'a> { - type Item = VNode<'a>; - type IntoIter = std::iter::Once; - fn into_iter(self) -> Self::IntoIter { - std::iter::once(self) - } -} - // TODO: do we even need this? It almost seems better not to // // For the case where a rendered VNode is passed into the rsx! macro through curly braces impl<'a> IntoVNode<'a> for VNode<'a> { @@ -825,37 +782,7 @@ impl<'a> IntoVNode<'a> for VNode<'a> { // Conveniently, we also support "null" (nothing) passed in impl IntoVNode<'_> for () { fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.fragment_from_iter(None as Option) - } -} - -// Conveniently, we also support "None" -impl IntoVNode<'_> for Option<()> { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.fragment_from_iter(None as Option) - } -} - -impl<'a> IntoVNode<'a> for Option> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - self.unwrap_or_else(|| cx.fragment_from_iter(None as Option)) - } -} - -impl<'a> IntoVNode<'a> for Option> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - match self { - Some(lazy) => lazy.call(cx), - None => VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() })), - } - } -} - -impl<'a, 'b> IntoIterator for LazyNodes<'a, 'b> { - type Item = LazyNodes<'a, 'b>; - type IntoIter = std::iter::Once; - fn into_iter(self) -> Self::IntoIter { - std::iter::once(self) + VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() })) } } @@ -883,17 +810,53 @@ impl IntoVNode<'_> for Arguments<'_> { } } -impl<'a> IntoVNode<'a> for &Option> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - self.as_ref() - .map(|f| f.into_vnode(cx)) - .unwrap_or_else(|| cx.fragment_from_iter(None as Option)) - } -} - impl<'a> IntoVNode<'a> for &VNode<'a> { fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> { // borrowed nodes are strange self.decouple() } } + +// Note that we're using the E as a generic but this is never crafted anyways. +pub struct FromNodeIterator; +impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T +where + T: IntoIterator, + I: IntoVNode<'a, E>, +{ + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { + let mut nodes = bumpalo::collections::Vec::new_in(cx.bump); + + for node in self { + nodes.push(node.into_vnode(cx)); + } + + if nodes.is_empty() { + VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() })) + } else { + let children = nodes.into_bump_slice(); + + if cfg!(debug_assertions) + && children.len() > 1 + && children.last().unwrap().key().is_none() + { + // todo: make the backtrace prettier or remove it altogether + log::error!( + r#" + Warning: Each child in an array or iterator should have a unique "key" prop. + Not providing a key will lead to poor performance with lists. + See docs.rs/dioxus for more information. + ------------- + {:?} + "#, + backtrace::Backtrace::new() + ); + } + + VNode::Fragment(cx.bump.alloc(VFragment { + children, + key: None, + })) + } + } +} diff --git a/packages/dioxus/tests/borrowedstate.rs b/packages/dioxus/tests/borrowedstate.rs index 520cce8a6..905339793 100644 --- a/packages/dioxus/tests/borrowedstate.rs +++ b/packages/dioxus/tests/borrowedstate.rs @@ -8,7 +8,7 @@ fn test_borrowed_state() { } fn Parent(cx: Scope) -> Element { - let value = cx.use_hook(|| String::new()); + let value = cx.use_hook(String::new); cx.render(rsx! { div { diff --git a/packages/dioxus/tests/create_dom.rs b/packages/dioxus/tests/create_dom.rs index 33959e6a5..e66954207 100644 --- a/packages/dioxus/tests/create_dom.rs +++ b/packages/dioxus/tests/create_dom.rs @@ -157,7 +157,7 @@ fn create_components() { fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element { cx.render(rsx! { h1 {} - div { {&cx.props.children} } + div { &cx.props.children } p {} }) }