feat: components pass thru children
This commit is contained in:
1 parent
b6c0bce89c
commit
c6a6c3d0f8
17 files changed
+581
-298
No files matched your search
+31
-14
@@ -3,7 +3,7 @@ use crate::mutations::Mutation::*;
|
||||
use crate::nodes::VNode;
|
||||
use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
|
||||
use crate::virtualdom::VirtualDom;
|
||||
use crate::TemplateAttribute;
|
||||
use crate::{AttributeValue, TemplateAttribute};
|
||||
|
||||
impl VirtualDom {
|
||||
/// Create this template and write its mutations
|
||||
@@ -52,25 +52,41 @@ impl VirtualDom {
|
||||
TemplateNode::DynamicText { .. } => 1,
|
||||
};
|
||||
|
||||
let mut cur_route = None;
|
||||
|
||||
// we're on top of a node that has a dynamic attribute for a descendant
|
||||
// Set that attribute now before the stack gets in a weird state
|
||||
while let Some(loc) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
|
||||
while let Some(attr) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
|
||||
if cur_route.is_none() {
|
||||
cur_route = Some((self.next_element(template), &attr.path[1..]));
|
||||
}
|
||||
|
||||
// Attach all the elementIDs to the nodes with dynamic content
|
||||
let id = self.next_element(template);
|
||||
let (id, path) = cur_route.unwrap();
|
||||
|
||||
mutations.push(AssignId {
|
||||
path: &loc.path[1..],
|
||||
id,
|
||||
});
|
||||
mutations.push(AssignId { path, id });
|
||||
attr.mounted_element.set(id);
|
||||
|
||||
loc.mounted_element.set(id);
|
||||
match attr.value {
|
||||
AttributeValue::Text(value) => {
|
||||
mutations.push(SetAttribute {
|
||||
name: attr.name,
|
||||
value,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
for attr in loc.attrs.iter() {
|
||||
mutations.push(SetAttribute {
|
||||
name: attr.name,
|
||||
value: attr.value,
|
||||
id,
|
||||
});
|
||||
AttributeValue::Listener(_) => {
|
||||
//
|
||||
}
|
||||
|
||||
AttributeValue::Float(_) => todo!(),
|
||||
AttributeValue::Int(_) => todo!(),
|
||||
AttributeValue::Bool(_) => todo!(),
|
||||
AttributeValue::Any(_) => todo!(),
|
||||
|
||||
// Optional attributes
|
||||
AttributeValue::None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +110,7 @@ impl VirtualDom {
|
||||
node: &'a TemplateNode<'static>,
|
||||
) {
|
||||
match *node {
|
||||
// Todo: create the children's template
|
||||
TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder),
|
||||
TemplateNode::Text(value) => mutations.push(CreateText { value }),
|
||||
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{any::Any, cell::Cell};
|
||||
|
||||
use crate::{arena::ElementId, nodes::Listener, scopes::ScopeId, virtualdom::VirtualDom};
|
||||
use crate::{arena::ElementId, scopes::ScopeId, virtualdom::VirtualDom, Attribute, AttributeValue};
|
||||
|
||||
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
|
||||
///
|
||||
@@ -106,12 +106,12 @@ impl VirtualDom {
|
||||
|
||||
let location = unsafe { &mut *path.template }
|
||||
.dynamic_attrs
|
||||
.iter_mut()
|
||||
.iter()
|
||||
.position(|attr| attr.mounted_element.get() == event.element)?;
|
||||
|
||||
let mut index = Some((path.template, location));
|
||||
|
||||
let mut listeners = Vec::<&mut Listener>::new();
|
||||
let mut listeners = Vec::<&Attribute>::new();
|
||||
|
||||
while let Some((raw_parent, dyn_index)) = index {
|
||||
let parent = unsafe { &mut *raw_parent };
|
||||
@@ -120,17 +120,18 @@ impl VirtualDom {
|
||||
listeners.extend(
|
||||
parent
|
||||
.dynamic_attrs
|
||||
.iter_mut()
|
||||
.iter()
|
||||
.filter(|attr| is_path_ascendant(attr.path, path))
|
||||
.map(|f| f.listeners.iter_mut().filter(|f| f.name == event.name))
|
||||
.flatten(),
|
||||
.filter(|attr| attr.name == event.name),
|
||||
);
|
||||
|
||||
index = parent.parent;
|
||||
}
|
||||
|
||||
for listener in listeners {
|
||||
(listener.callback)(&event.event);
|
||||
if let AttributeValue::Listener(listener) = &listener.value {
|
||||
(listener.borrow_mut())(&event.event)
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use std::{cell::Cell, fmt::Arguments};
|
||||
|
||||
use bumpalo::Bump;
|
||||
|
||||
use crate::{
|
||||
any_props::{AnyProps, VComponentProps},
|
||||
arena::ElementId,
|
||||
innerlude::{DynamicNode, DynamicNodeKind},
|
||||
LazyNodes, ScopeState, VNode,
|
||||
Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode,
|
||||
};
|
||||
|
||||
impl ScopeState {
|
||||
@@ -50,6 +53,56 @@ impl ScopeState {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Attribute`]
|
||||
pub fn attr<'a>(
|
||||
&'a self,
|
||||
name: &'static str,
|
||||
val: impl IntoAttributeValue<'a>,
|
||||
namespace: Option<&'static str>,
|
||||
is_volatile: bool,
|
||||
) -> Attribute<'a> {
|
||||
Attribute {
|
||||
name,
|
||||
namespace,
|
||||
mounted_element: Cell::new(ElementId(0)),
|
||||
path: &[0],
|
||||
value: val.into_value(self.bump()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`VNode::Component`]
|
||||
pub fn component<'a, P>(
|
||||
&'a self,
|
||||
component: fn(Scope<'a, P>) -> Element,
|
||||
props: P,
|
||||
fn_name: &'static str,
|
||||
) -> DynamicNode<'a>
|
||||
where
|
||||
P: Properties + 'a,
|
||||
{
|
||||
let props = self.bump().alloc(props);
|
||||
let detached = unsafe { std::mem::transmute(component) };
|
||||
let vcomp = VComponentProps::new(detached, P::memoize, props);
|
||||
let as_dyn = self.bump().alloc(vcomp) as &mut dyn AnyProps;
|
||||
let detached_dyn: *mut dyn AnyProps = unsafe { std::mem::transmute(as_dyn) };
|
||||
|
||||
// todo: clean up borrowed props
|
||||
// if !P::IS_STATIC {
|
||||
// let vcomp = &*vcomp;
|
||||
// let vcomp = unsafe { std::mem::transmute(vcomp) };
|
||||
// self.scope.items.borrow_mut().borrowed_props.push(vcomp);
|
||||
// }
|
||||
|
||||
DynamicNode {
|
||||
path: &[],
|
||||
kind: DynamicNodeKind::Component {
|
||||
name: fn_name,
|
||||
can_memoize: P::IS_STATIC,
|
||||
props: detached_dyn,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoVnode<'a, A = ()> {
|
||||
@@ -61,3 +114,56 @@ impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
|
||||
self.call(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVnode<'a> for VNode<'a> {
|
||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> {
|
||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||
VNode {
|
||||
node_id: self.node_id.clone(),
|
||||
parent: self.parent,
|
||||
template: self.template,
|
||||
root_ids: self.root_ids,
|
||||
dynamic_nodes: self.dynamic_nodes,
|
||||
dynamic_attrs: self.dynamic_attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that can be converted into an attribute value
|
||||
pub trait IntoAttributeValue<'a> {
|
||||
/// Convert into an attribute value
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for &'a str {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Text(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for f32 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Float(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for i32 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Int(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for bool {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Bool(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut str_buf = bumpalo::collections::String::new_in(bump);
|
||||
str_buf.write_fmt(self).unwrap();
|
||||
AttributeValue::Text(str_buf.into_bump_str())
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ mod garbage;
|
||||
mod lazynodes;
|
||||
mod mutations;
|
||||
mod nodes;
|
||||
mod properties;
|
||||
mod scope_arena;
|
||||
mod scopes;
|
||||
mod virtualdom;
|
||||
@@ -20,10 +21,10 @@ pub(crate) mod innerlude {
|
||||
pub use crate::element::Element;
|
||||
pub use crate::events::*;
|
||||
pub use crate::future_container::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
// pub use crate::properties::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::properties::*;
|
||||
pub use crate::scopes::*;
|
||||
pub use crate::virtualdom::*;
|
||||
|
||||
@@ -66,6 +67,7 @@ pub(crate) mod innerlude {
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder,
|
||||
// AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
|
||||
Attribute,
|
||||
AttributeValue,
|
||||
@@ -74,8 +76,8 @@ pub use crate::innerlude::{
|
||||
Element,
|
||||
EventPriority,
|
||||
LazyNodes,
|
||||
Listener,
|
||||
NodeFactory,
|
||||
Properties,
|
||||
Scope,
|
||||
ScopeId,
|
||||
ScopeState,
|
||||
@@ -96,9 +98,9 @@ pub use crate::innerlude::{
|
||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||
pub mod prelude {
|
||||
pub use crate::innerlude::{
|
||||
Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes, Listener,
|
||||
NodeFactory, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
|
||||
UiEvent, VNode, VirtualDom,
|
||||
fc_to_builder, Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes,
|
||||
NodeFactory, Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute,
|
||||
TemplateNode, UiEvent, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ pub struct Renderer<'a> {
|
||||
#[derive(Debug)]
|
||||
pub enum Mutation<'a> {
|
||||
SetAttribute {
|
||||
name: &'static str,
|
||||
name: &'a str,
|
||||
value: &'a str,
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
+21
-20
@@ -1,5 +1,9 @@
|
||||
use crate::{any_props::AnyProps, arena::ElementId};
|
||||
use std::{any::Any, cell::Cell, hash::Hasher};
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
hash::Hasher,
|
||||
};
|
||||
|
||||
pub type TemplateId = &'static str;
|
||||
|
||||
@@ -8,17 +12,16 @@ pub struct VNode<'a> {
|
||||
// The ID assigned for the root of this template
|
||||
pub node_id: Cell<ElementId>,
|
||||
|
||||
// When rendered, this template will be linked to its parent
|
||||
// When rendered, this template will be linked to its parent manually
|
||||
pub parent: Option<(*mut VNode<'static>, usize)>,
|
||||
|
||||
pub template: Template<'static>,
|
||||
|
||||
pub root_ids: &'a [Cell<ElementId>],
|
||||
|
||||
/// All the dynamic nodes for a template
|
||||
pub dynamic_nodes: &'a mut [DynamicNode<'a>],
|
||||
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
||||
|
||||
pub dynamic_attrs: &'a mut [AttributeLocation<'a>],
|
||||
pub dynamic_attrs: &'a [Attribute<'a>],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -75,6 +78,7 @@ pub enum DynamicNodeKind<'a> {
|
||||
// IE in caps or with underscores
|
||||
Component {
|
||||
name: &'static str,
|
||||
can_memoize: bool,
|
||||
props: *mut dyn AnyProps,
|
||||
},
|
||||
|
||||
@@ -104,18 +108,12 @@ pub enum TemplateAttribute<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
pub struct AttributeLocation<'a> {
|
||||
pub mounted_element: Cell<ElementId>,
|
||||
pub attrs: &'a mut [Attribute<'a>],
|
||||
pub listeners: &'a mut [Listener<'a>],
|
||||
pub path: &'static [u8],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Attribute<'a> {
|
||||
pub name: &'static str,
|
||||
pub value: &'a str,
|
||||
pub name: &'a str,
|
||||
pub value: AttributeValue<'a>,
|
||||
pub namespace: Option<&'static str>,
|
||||
pub mounted_element: Cell<ElementId>,
|
||||
pub path: &'static [u8],
|
||||
}
|
||||
|
||||
pub enum AttributeValue<'a> {
|
||||
@@ -123,7 +121,15 @@ pub enum AttributeValue<'a> {
|
||||
Float(f32),
|
||||
Int(i32),
|
||||
Bool(bool),
|
||||
Listener(RefCell<&'a mut dyn FnMut(&dyn Any)>),
|
||||
Any(&'a dyn AnyValue),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> AttributeValue<'a> {
|
||||
fn is_listener(&self) -> bool {
|
||||
matches!(self, AttributeValue::Listener(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnyValue {
|
||||
@@ -142,11 +148,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Listener<'a> {
|
||||
pub name: &'static str,
|
||||
pub callback: &'a mut dyn FnMut(&dyn Any),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_are_the_sizes() {
|
||||
dbg!(std::mem::size_of::<VNode>());
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
use crate::innerlude::*;
|
||||
|
||||
pub struct FragmentProps<'a>(Element<'a>);
|
||||
pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
|
||||
impl<'a> FragmentBuilder<'a, false> {
|
||||
pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
|
||||
FragmentBuilder(children)
|
||||
}
|
||||
}
|
||||
impl<'a, const A: bool> FragmentBuilder<'a, A> {
|
||||
pub fn build(self) -> FragmentProps<'a> {
|
||||
FragmentProps(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the children elements passed into the component
|
||||
///
|
||||
/// This enables patterns where a component is passed children from its parent.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
|
||||
/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
|
||||
/// on the props that takes Context.
|
||||
///
|
||||
/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
|
||||
/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
|
||||
/// props are valid for the static lifetime.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// CustomCard {
|
||||
/// h1 {}
|
||||
/// p {}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// #[derive(PartialEq, Props)]
|
||||
/// struct CardProps {
|
||||
/// children: Element
|
||||
/// }
|
||||
///
|
||||
/// fn CustomCard(cx: Scope<CardProps>) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// div {
|
||||
/// h1 {"Title card"}
|
||||
/// {cx.props.children}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
impl<'a> Properties for FragmentProps<'a> {
|
||||
type Builder = FragmentBuilder<'a, false>;
|
||||
const IS_STATIC: bool = false;
|
||||
fn builder() -> Self::Builder {
|
||||
FragmentBuilder(None)
|
||||
}
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// /// Create inline fragments using Component syntax.
|
||||
// ///
|
||||
// /// ## Details
|
||||
// ///
|
||||
// /// Fragments capture a series of children without rendering extra nodes.
|
||||
// ///
|
||||
// /// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
|
||||
// /// a key is needed to identify each item.
|
||||
// ///
|
||||
// /// ## Example
|
||||
// ///
|
||||
// /// ```rust, ignore
|
||||
// /// rsx!{
|
||||
// /// Fragment { key: "abc" }
|
||||
// /// }
|
||||
// /// ```
|
||||
// ///
|
||||
// /// ## Usage
|
||||
// ///
|
||||
// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
// /// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
|
||||
// ///
|
||||
// /// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||
// ///
|
||||
// /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
// #[allow(non_upper_case_globals, non_snake_case)]
|
||||
// pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
// let i = cx.props.0.as_ref().map(|f| f.decouple());
|
||||
// cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
|
||||
// }
|
||||
|
||||
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
|
||||
///
|
||||
/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
|
||||
/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
|
||||
/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
|
||||
///
|
||||
/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
|
||||
/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
|
||||
///
|
||||
/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
|
||||
/// we advise the use of smart pointers in cases where memoization is important.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// For props that are 'static:
|
||||
/// ```rust, ignore
|
||||
/// #[derive(Props, PartialEq)]
|
||||
/// struct MyProps {
|
||||
/// data: String
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// For props that borrow:
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// #[derive(Props)]
|
||||
/// struct MyProps<'a >{
|
||||
/// data: &'a str
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Properties: Sized {
|
||||
/// The type of the builder for this component.
|
||||
/// Used to create "in-progress" versions of the props.
|
||||
type Builder;
|
||||
|
||||
/// An indication if these props are can be memoized automatically.
|
||||
const IS_STATIC: bool;
|
||||
|
||||
/// Create a builder for this component.
|
||||
fn builder() -> Self::Builder;
|
||||
|
||||
/// Memoization can only happen if the props are valid for the 'static lifetime
|
||||
///
|
||||
/// # Safety
|
||||
/// The user must know if their props are static, but if they make a mistake, UB happens
|
||||
/// Therefore it's unsafe to memoize.
|
||||
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||
}
|
||||
|
||||
impl Properties for () {
|
||||
type Builder = EmptyBuilder;
|
||||
const IS_STATIC: bool = true;
|
||||
fn builder() -> Self::Builder {
|
||||
EmptyBuilder {}
|
||||
}
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
|
||||
// that the macros use to anonymously complete prop construction.
|
||||
pub struct EmptyBuilder;
|
||||
impl EmptyBuilder {
|
||||
pub fn build(self) {}
|
||||
}
|
||||
|
||||
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
|
||||
/// to initialize a component's props.
|
||||
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
|
||||
T::builder()
|
||||
}
|
||||
Reference in new issue
Block a user