Przeglądaj źródła

Merge pull request #2 from jkelleyrtp/jk/dyn_scope

Implement props
Jonathan Kelley 4 lat temu
rodzic
commit
ddaa55a04a

+ 1 - 1
Cargo.toml

@@ -4,7 +4,7 @@ members = [
     "packages/dioxus",
     "packages/core-macro",
     "packages/core",
-    "packages/web",
+    # "packages/web",
     # "packages/router",
     # "packages/ssr",
     # "packages/webview",

+ 1 - 0
README.md

@@ -11,6 +11,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
 
 
 ```rust
+#[fc]
 static Example: FC<()> = |ctx, props| {
     let (selection, set_selection) = use_state(&ctx, || "...?");
 

+ 34 - 0
notes/SOLVEDPROBLEMS.md

@@ -360,3 +360,37 @@ The DomTree object purely represents a viewable "key". It also forces components
 ## Events 
 
 Events are finally in! To do events properly, we are abstracting over the event source with synthetic events. This forces 3rd party renderers to create the appropriate cross-platform event 
+
+
+## Optional Props on Components
+
+A major goal here is ergonomics. Any field that is Option<T> should default to none. 
+
+```rust
+
+rsx! {
+    Example { /* args go here */ a: 10, b: 20 }
+}
+
+
+```
+
+```rust
+#[derive(Properties)]
+struct Props {
+
+}
+
+static Component: FC<Props> = |ctx, props| {
+
+}
+```
+
+or
+
+```rust
+#[fc]
+static Component: FC = |ctx, name: &str| {
+
+}
+```

+ 1 - 0
packages/core-macro/Cargo.toml

@@ -12,6 +12,7 @@ description = "Core macro for Dioxus Virtual DOM"
 proc-macro = true
 
 [dependencies]
+once_cell = "1.7.2"
 proc-macro-hack = "0.5.19"
 proc-macro2 = "1.0.6"
 quote = "1.0"

+ 3 - 0
packages/core-macro/examples/fc.rs

@@ -0,0 +1,3 @@
+use dioxus_core_macro::fc;
+
+fn main() {}

+ 122 - 105
packages/core-macro/src/fc.rs

@@ -9,80 +9,13 @@ use syn::{
     parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
 };
 
-pub fn function_component_impl(
-    // name: FunctionComponentName,
-    component: FunctionComponent,
-) -> syn::Result<TokenStream> {
-    // let FunctionComponentName { component_name } = name;
-
-    let FunctionComponent {
-        block,
-        props_type,
-        arg,
-        vis,
-        attrs,
-        name: function_name,
-        return_type,
-    } = component;
-
-    // if function_name == component_name {
-    //     return Err(syn::Error::new_spanned(
-    //         component_name,
-    //         "the component must not have the same name as the function",
-    //     ));
-    // }
-
-    let quoted = quote! {
-        #[doc(hidden)]
-        #[allow(non_camel_case_types)]
-        mod __component_blah {
-            use super::*;
-
-            #[derive(PartialEq)]
-            pub struct Props<'a> {
-                name: &'a str
-            }
-
-            pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
-                // Destructure the props into the parent scope
-                // todo: handle expansion of lifetimes
-                let Props {
-                    name
-                } = ctx.props;
-
-                #block
-            }
-        }
-        #[allow(non_snake_case)]
-        pub use __component_blah::component as #function_name;
-    };
-    // let quoted = quote! {
-    //     #[doc(hidden)]
-    //     #[allow(non_camel_case_types)]
-    //     #vis struct #function_name;
-
-    //     impl ::yew_functional::FunctionProvider for #function_name {
-    //         type TProps = #props_type;
-
-    //         fn run(#arg) -> #ret_type {
-    //             #block
-    //         }
-    //     }
-
-    //     #(#attrs)*
-    //     #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>;
-    // };
-    Ok(quoted)
-}
-
 /// A parsed version of the user's input
 pub struct FunctionComponent {
     // The actual contents of the function
     block: Box<Block>,
 
-    // The user's props type
-    props_type: Box<Type>,
-
+    // // The user's props type
+    // props_type: Box<Type>,
     arg: FnArg,
     vis: Visibility,
     attrs: Vec<Attribute>,
@@ -117,7 +50,7 @@ impl Parse for FunctionComponent {
             .unwrap_or_else(|| syn::parse_quote! { _: &() });
 
         // Extract the "context" object
-        let props_type = validate_context_arg(&first_arg)?;
+        // let props_type = validate_context_arg(&first_arg)?;
 
         /*
         Extract the rest of the function arguments into a struct body
@@ -159,7 +92,7 @@ impl Parse for FunctionComponent {
         let name = sig.ident;
 
         Ok(Self {
-            props_type,
+            // props_type,
             block,
             arg: first_arg,
             vis,
@@ -169,6 +102,89 @@ impl Parse for FunctionComponent {
         })
     }
 }
+impl ToTokens for FunctionComponent {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        // let FunctionComponentName { component_name } = name;
+
+        let FunctionComponent {
+            block,
+            // props_type,
+            arg,
+            vis,
+            attrs,
+            name: function_name,
+            return_type,
+        } = self;
+
+        // if function_name == component_name {
+        //     return Err(syn::Error::new_spanned(
+        //         component_name,
+        //         "the component must not have the same name as the function",
+        //     ));
+        // }
+
+        let quoted = quote! {
+
+
+            #[doc(hidden)]
+            #[allow(non_camel_case_types)]
+            #[derive(PartialEq)]
+            pub struct #function_name<'a> {
+                // and some other attrs
+                ___p: std::marker::PhantomData<&'a ()>
+            }
+
+            impl<'a> FC for #function_name<'a> {
+                fn render(ctx: Context<'_>, props: &#function_name<'a>) -> DomTree {
+                    let #function_name {
+                        ..
+                    } = props;
+
+                    #block
+                }
+            }
+
+            // mod __component_blah {
+            // use super::*;
+
+            // #[derive(PartialEq)]
+            // pub struct Props<'a> {
+            //     name: &'a str
+            // }
+
+            // pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
+            //     // Destructure the props into the parent scope
+            //     // todo: handle expansion of lifetimes
+            //     let Props {
+            //         name
+            //     } = ctx.props;
+
+            //     #block
+            // }
+            // }
+            // #[allow(non_snake_case)]
+            // pub use __component_blah::component as #function_name;
+        };
+
+        quoted.to_tokens(tokens);
+        // let quoted = quote! {
+        //     #[doc(hidden)]
+        //     #[allow(non_camel_case_types)]
+        //     #vis struct #function_name;
+
+        //     impl ::yew_functional::FunctionProvider for #function_name {
+        //         type TProps = #props_type;
+
+        //         fn run(#arg) -> #ret_type {
+        //             #block
+        //         }
+        //     }
+
+        //     #(#attrs)*
+        //     #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>;
+        // };
+    }
+}
 
 /// Ensure the user's input is actually a functional component
 pub fn ensure_fn_block(item: Item) -> syn::Result<ItemFn> {
@@ -228,7 +244,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result<Box<Type>> {
     match output {
         ReturnType::Default => Err(syn::Error::new_spanned(
             output,
-            "function components must return `dioxus::VNode`",
+            "function components must return a `DomTree`",
         )),
         ReturnType::Type(_, ty) => Ok(ty),
     }
@@ -268,39 +284,40 @@ pub fn validate_signature(sig: Signature) -> syn::Result<Signature> {
     Ok(sig)
 }
 
-pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
-    if let FnArg::Typed(arg) = first_arg {
-        // Input arg is a reference to an &mut Context
-        if let Type::Reference(ty) = &*arg.ty {
-            if ty.lifetime.is_some() {
-                return Err(syn::Error::new_spanned(
-                    &ty.lifetime,
-                    "reference must not have a lifetime",
-                ));
-            }
-
-            if ty.mutability.is_some() {
-                return Err(syn::Error::new_spanned(
-                    &ty.mutability,
-                    "reference must not be mutable",
-                ));
-            }
-
-            Ok(ty.elem.clone())
-        } else {
-            let msg = format!(
-                "expected a reference to a `Context` object (try: `&mut {}`)",
-                arg.ty.to_token_stream()
-            );
-            return Err(syn::Error::new_spanned(arg.ty.clone(), msg));
-        }
-    } else {
-        return Err(syn::Error::new_spanned(
-            first_arg,
-            "function components can't accept a receiver",
-        ));
-    }
-}
+// pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
+//     if let FnArg::Typed(arg) = first_arg {
+//         // if let Type::R
+//         // Input arg is a reference to an &mut Context
+//         // if let Type::Reference(ty) = &*arg.ty {
+//         //     if ty.lifetime.is_some() {
+//         //         return Err(syn::Error::new_spanned(
+//         //             &ty.lifetime,
+//         //             "reference must not have a lifetime",
+//         //         ));
+//         //     }
+
+//         //     if ty.mutability.is_some() {
+//         //         return Err(syn::Error::new_spanned(
+//         //             &ty.mutability,
+//         //             "reference must not be mutable",
+//         //         ));
+//         //     }
+
+//         //     Ok(ty.elem.clone())
+//         // } else {
+//         //     let msg = format!(
+//         //         "expected a reference to a `Context` object (try: `&mut {}`)",
+//         //         arg.ty.to_token_stream()
+//         //     );
+//         //     return Err(syn::Error::new_spanned(arg.ty.clone(), msg));
+//         // }
+//     } else {
+//         return Err(syn::Error::new_spanned(
+//             first_arg,
+//             "function components can't accept a receiver",
+//         ));
+//     }
+// }
 
 pub fn collect_inline_args() {}
 

+ 27 - 32
packages/core-macro/src/ifmt.rs

@@ -1,4 +1,3 @@
-use ::proc_macro::TokenStream;
 use ::quote::{quote, ToTokens};
 use ::std::ops::Not;
 use ::syn::{
@@ -6,10 +5,7 @@ use ::syn::{
     punctuated::Punctuated,
     *,
 };
-use proc_macro2::TokenStream as TokenStream2;
-
-// #[macro_use]
-// mod macros {
+use proc_macro2::TokenStream;
 
 // #[cfg(not(feature = "verbose-expansions"))]
 macro_rules! debug_input {
@@ -18,18 +14,6 @@ macro_rules! debug_input {
     };
 }
 
-// #[cfg(feature = "verbose-expansions")]
-macro_rules! debug_input {
-    ($expr:expr) => {
-        match $expr {
-            expr => {
-                eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr);
-                expr
-            }
-        }
-    };
-}
-
 // #[cfg(not(feature = "verbose-expansions"))]
 macro_rules! debug_output {
     ($expr:expr) => {
@@ -38,19 +22,30 @@ macro_rules! debug_output {
 }
 
 // #[cfg(feature = "verbose-expansions")]
-macro_rules! debug_output {
-    ($expr:expr) => {
-        match $expr {
-            expr => {
-                eprintln!("=>\n{}\n-------------------\n", expr);
-                expr
-            }
-        }
-    };
-}
+// macro_rules! debug_input {
+//     ($expr:expr) => {
+//         match $expr {
+//             expr => {
+//                 eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr);
+//                 expr
+//             }
+//         }
+//     };
+// }
+
+// #[cfg(feature = "verbose-expansions")]
+// macro_rules! debug_output {
+//     ($expr:expr) => {
+//         match $expr {
+//             expr => {
+//                 eprintln!("=>\n{}\n-------------------\n", expr);
+//                 expr
+//             }
+//         }
+//     };
 // }
 
-pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
+pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
     let IfmtInput {
         mut format_literal,
         mut positional_args,
@@ -121,7 +116,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
                 arg,
             ) {
                 Ok(segments) => segments.into_iter().collect(),
-                Err(err) => return err.to_compile_error().into(),
+                Err(err) => return Err(err),
             }
         };
         match segments.len() {
@@ -151,7 +146,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
                     format_args!("{}", positional_args.len()),
                 )
                 .expect("`usize` or `char` Display impl cannot panic");
-                let segments: Punctuated<TokenStream2, Token![.]> = segments
+                let segments: Punctuated<TokenStream, Token![.]> = segments
                     .into_iter()
                     .map(|it| match it {
                         Segment::Ident(ident) => ident.into_token_stream(),
@@ -172,13 +167,13 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
     });
     format_literal = LitStr::new(out_format_literal, format_literal.span());
 
-    TokenStream::from(debug_output!(quote! {
+    Ok(TokenStream::from(debug_output!(quote! {
         format_args!(
             #format_literal
             #(, #positional_args)*
             #(, #named_args)*
         )
-    }))
+    })))
 }
 
 #[allow(dead_code)] // dumb compiler does not see the struct being used...

+ 30 - 16
packages/core-macro/src/lib.rs

@@ -5,7 +5,9 @@ use syn::parse_macro_input;
 mod fc;
 mod htm;
 mod ifmt;
+mod props;
 mod rsxt;
+mod util;
 
 /// The html! macro makes it easy for developers to write jsx-style markup in their components.
 /// We aim to keep functional parity with html templates.
@@ -27,31 +29,43 @@ pub fn rsx(s: TokenStream) -> TokenStream {
     }
 }
 
+// #[proc_macro_attribute]
+// pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
+
 /// Label a function or static closure as a functional component.
 /// This macro reduces the need to create a separate properties struct.
+///
+/// Using this macro is fun and simple
+///
+/// ```ignore
+///
+/// #[fc]
+/// fn Example(ctx: Context, name: &str) -> DomTree {
+///     ctx.render(rsx! { h1 {"hello {name}"} })
+/// }
+/// ```
 #[proc_macro_attribute]
 pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
-    use fc::{function_component_impl, FunctionComponent};
-
-    let item = parse_macro_input!(item as FunctionComponent);
-
-    function_component_impl(item)
-        .unwrap_or_else(|err| err.to_compile_error())
-        .into()
+    match syn::parse::<fc::FunctionComponent>(item) {
+        Err(e) => e.to_compile_error().into(),
+        Ok(s) => s.to_token_stream().into(),
+    }
 }
 
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {
     use ifmt::*;
-
     let item = parse_macro_input!(input as IfmtInput);
+    format_args_f_impl(item)
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}
 
-    // #[allow(unused)]
-    // const FUNCTION_NAME: &str = "format_args_f";
-
-    // debug_input!(&input);
-
-    ifmt::format_args_f_impl(item)
-    // .unwrap_or_else(|err| err.to_compile_error())
-    // .into()
+#[proc_macro_derive(Props, attributes(builder))]
+pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let input = parse_macro_input!(input as syn::DeriveInput);
+    match props::impl_my_derive(&input) {
+        Ok(output) => output.into(),
+        Err(error) => error.to_compile_error().into(),
+    }
 }

+ 1198 - 0
packages/core-macro/src/props/mod.rs

@@ -0,0 +1,1198 @@
+//! This code mostly comes from idanarye/rust-typed-builder
+//!
+//! 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>)
+
+use proc_macro2::TokenStream;
+
+use syn::parse::Error;
+use syn::spanned::Spanned;
+use syn::{parse_macro_input, DeriveInput};
+
+use quote::quote;
+
+pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
+    let data = match &ast.data {
+        syn::Data::Struct(data) => match &data.fields {
+            syn::Fields::Named(fields) => {
+                let struct_info = struct_info::StructInfo::new(&ast, fields.named.iter())?;
+                let builder_creation = struct_info.builder_creation_impl()?;
+                let conversion_helper = struct_info.conversion_helper_impl()?;
+                let fields = struct_info
+                    .included_fields()
+                    .map(|f| struct_info.field_impl(f))
+                    .collect::<Result<Vec<_>, _>>()?;
+                let fields = quote!(#(#fields)*).into_iter();
+                let required_fields = struct_info
+                    .included_fields()
+                    .filter(|f| f.builder_attr.default.is_none())
+                    .map(|f| struct_info.required_field_impl(f))
+                    .collect::<Result<Vec<_>, _>>()?;
+                let build_method = struct_info.build_method_impl();
+
+                quote! {
+                    #builder_creation
+                    #conversion_helper
+                    #( #fields )*
+                    #( #required_fields )*
+                    #build_method
+                }
+            }
+            syn::Fields::Unnamed(_) => {
+                return Err(Error::new(
+                    ast.span(),
+                    "TypedBuilder is not supported for tuple structs",
+                ))
+            }
+            syn::Fields::Unit => {
+                return Err(Error::new(
+                    ast.span(),
+                    "TypedBuilder is not supported for unit structs",
+                ))
+            }
+        },
+        syn::Data::Enum(_) => {
+            return Err(Error::new(
+                ast.span(),
+                "TypedBuilder is not supported for enums",
+            ))
+        }
+        syn::Data::Union(_) => {
+            return Err(Error::new(
+                ast.span(),
+                "TypedBuilder is not supported for unions",
+            ))
+        }
+    };
+    Ok(data)
+}
+
+mod util {
+    use quote::ToTokens;
+
+    pub fn path_to_single_string(path: &syn::Path) -> Option<String> {
+        if path.leading_colon.is_some() {
+            return None;
+        }
+        let mut it = path.segments.iter();
+        let segment = it.next()?;
+        if it.next().is_some() {
+            // Multipart path
+            return None;
+        }
+        if segment.arguments != syn::PathArguments::None {
+            return None;
+        }
+        Some(segment.ident.to_string())
+    }
+
+    pub fn expr_to_single_string(expr: &syn::Expr) -> Option<String> {
+        if let syn::Expr::Path(path) = &*expr {
+            path_to_single_string(&path.path)
+        } else {
+            None
+        }
+    }
+
+    pub fn ident_to_type(ident: syn::Ident) -> syn::Type {
+        let mut path = syn::Path {
+            leading_colon: None,
+            segments: Default::default(),
+        };
+        path.segments.push(syn::PathSegment {
+            ident,
+            arguments: Default::default(),
+        });
+        syn::Type::Path(syn::TypePath { qself: None, path })
+    }
+
+    pub fn empty_type() -> syn::Type {
+        syn::TypeTuple {
+            paren_token: Default::default(),
+            elems: Default::default(),
+        }
+        .into()
+    }
+
+    pub fn type_tuple(elems: impl Iterator<Item = syn::Type>) -> syn::TypeTuple {
+        let mut result = syn::TypeTuple {
+            paren_token: Default::default(),
+            elems: elems.collect(),
+        };
+        if !result.elems.empty_or_trailing() {
+            result.elems.push_punct(Default::default());
+        }
+        result
+    }
+
+    pub fn empty_type_tuple() -> syn::TypeTuple {
+        syn::TypeTuple {
+            paren_token: Default::default(),
+            elems: Default::default(),
+        }
+    }
+
+    pub fn make_punctuated_single<T, P: Default>(value: T) -> syn::punctuated::Punctuated<T, P> {
+        let mut punctuated = syn::punctuated::Punctuated::new();
+        punctuated.push(value);
+        punctuated
+    }
+
+    pub fn modify_types_generics_hack<F>(
+        ty_generics: &syn::TypeGenerics,
+        mut mutator: F,
+    ) -> syn::AngleBracketedGenericArguments
+    where
+        F: FnMut(&mut syn::punctuated::Punctuated<syn::GenericArgument, syn::token::Comma>),
+    {
+        let mut abga: syn::AngleBracketedGenericArguments =
+            syn::parse(ty_generics.clone().into_token_stream().into()).unwrap_or_else(|_| {
+                syn::AngleBracketedGenericArguments {
+                    colon2_token: None,
+                    lt_token: Default::default(),
+                    args: Default::default(),
+                    gt_token: Default::default(),
+                }
+            });
+        mutator(&mut abga.args);
+        abga
+    }
+
+    pub fn strip_raw_ident_prefix(mut name: String) -> String {
+        if name.starts_with("r#") {
+            name.replace_range(0..2, "");
+        }
+        name
+    }
+}
+
+mod field_info {
+    use proc_macro2::TokenStream;
+    use quote::quote;
+    use syn::parse::Error;
+    use syn::spanned::Spanned;
+
+    use super::util::{
+        expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix,
+    };
+
+    #[derive(Debug)]
+    pub struct FieldInfo<'a> {
+        pub ordinal: usize,
+        pub name: &'a syn::Ident,
+        pub generic_ident: syn::Ident,
+        pub ty: &'a syn::Type,
+        pub builder_attr: FieldBuilderAttr,
+    }
+
+    impl<'a> FieldInfo<'a> {
+        pub fn new(
+            ordinal: usize,
+            field: &syn::Field,
+            field_defaults: FieldBuilderAttr,
+        ) -> Result<FieldInfo, Error> {
+            if let Some(ref name) = field.ident {
+                Ok(FieldInfo {
+                    ordinal,
+                    name: &name,
+                    generic_ident: syn::Ident::new(
+                        &format!("__{}", strip_raw_ident_prefix(name.to_string())),
+                        proc_macro2::Span::call_site(),
+                    ),
+                    ty: &field.ty,
+                    builder_attr: field_defaults.with(&field.attrs)?,
+                })
+            } else {
+                Err(Error::new(field.span(), "Nameless field in struct"))
+            }
+        }
+
+        pub fn generic_ty_param(&self) -> syn::GenericParam {
+            syn::GenericParam::Type(self.generic_ident.clone().into())
+        }
+
+        pub fn type_ident(&self) -> syn::Type {
+            ident_to_type(self.generic_ident.clone())
+        }
+
+        pub fn tuplized_type_ty_param(&self) -> syn::Type {
+            let mut types = syn::punctuated::Punctuated::default();
+            types.push(self.ty.clone());
+            types.push_punct(Default::default());
+            syn::TypeTuple {
+                paren_token: Default::default(),
+                elems: types,
+            }
+            .into()
+        }
+
+        pub fn type_from_inside_option(&self) -> Option<&syn::Type> {
+            let path = if let syn::Type::Path(type_path) = self.ty {
+                if type_path.qself.is_some() {
+                    return None;
+                } else {
+                    &type_path.path
+                }
+            } else {
+                return None;
+            };
+            let segment = path.segments.last()?;
+            if segment.ident != "Option" {
+                return None;
+            }
+            let generic_params =
+                if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments {
+                    generic_params
+                } else {
+                    return None;
+                };
+            if let syn::GenericArgument::Type(ty) = generic_params.args.first()? {
+                Some(ty)
+            } else {
+                None
+            }
+        }
+    }
+
+    #[derive(Debug, Default, Clone)]
+    pub struct FieldBuilderAttr {
+        pub default: Option<syn::Expr>,
+        pub setter: SetterSettings,
+    }
+
+    #[derive(Debug, Default, Clone)]
+    pub struct SetterSettings {
+        pub doc: Option<syn::Expr>,
+        pub skip: bool,
+        pub auto_into: bool,
+        pub strip_option: bool,
+    }
+
+    impl FieldBuilderAttr {
+        pub fn with(mut self, attrs: &[syn::Attribute]) -> Result<Self, Error> {
+            let mut skip_tokens = None;
+            for attr in attrs {
+                if path_to_single_string(&attr.path).as_deref() != Some("builder") {
+                    continue;
+                }
+
+                if attr.tokens.is_empty() {
+                    continue;
+                }
+
+                let as_expr: syn::Expr = syn::parse2(attr.tokens.clone())?;
+                match as_expr {
+                    syn::Expr::Paren(body) => {
+                        self.apply_meta(*body.expr)?;
+                    }
+                    syn::Expr::Tuple(body) => {
+                        for expr in body.elems.into_iter() {
+                            self.apply_meta(expr)?;
+                        }
+                    }
+                    _ => {
+                        return Err(Error::new_spanned(attr.tokens.clone(), "Expected (<...>)"));
+                    }
+                }
+                // Stash its span for later (we don’t yet know if it’ll be an error)
+                if self.setter.skip && skip_tokens.is_none() {
+                    skip_tokens = Some(attr.tokens.clone());
+                }
+            }
+
+            if self.setter.skip && self.default.is_none() {
+                return Err(Error::new_spanned(
+                    skip_tokens.unwrap(),
+                    "#[builder(skip)] must be accompanied by default or default_code",
+                ));
+            }
+
+            Ok(self)
+        }
+
+        pub fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
+            match expr {
+                syn::Expr::Assign(assign) => {
+                    let name = expr_to_single_string(&assign.left)
+                        .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
+                    match name.as_str() {
+                        "default" => {
+                            self.default = Some(*assign.right);
+                            Ok(())
+                        }
+                        "default_code" => {
+                            if let syn::Expr::Lit(syn::ExprLit {
+                                lit: syn::Lit::Str(code),
+                                ..
+                            }) = *assign.right
+                            {
+                                use std::str::FromStr;
+                                let tokenized_code = TokenStream::from_str(&code.value())?;
+                                self.default = Some(
+                                    syn::parse(tokenized_code.into())
+                                        .map_err(|e| Error::new_spanned(code, format!("{}", e)))?,
+                                );
+                            } else {
+                                return Err(Error::new_spanned(assign.right, "Expected string"));
+                            }
+                            Ok(())
+                        }
+                        _ => Err(Error::new_spanned(
+                            &assign,
+                            format!("Unknown parameter {:?}", name),
+                        )),
+                    }
+                }
+                syn::Expr::Path(path) => {
+                    let name = path_to_single_string(&path.path)
+                        .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
+                    match name.as_str() {
+                        "default" => {
+                            self.default =
+                                Some(syn::parse(quote!(Default::default()).into()).unwrap());
+                            Ok(())
+                        }
+                        _ => Err(Error::new_spanned(
+                            &path,
+                            format!("Unknown parameter {:?}", name),
+                        )),
+                    }
+                }
+                syn::Expr::Call(call) => {
+                    let subsetting_name = if let syn::Expr::Path(path) = &*call.func {
+                        path_to_single_string(&path.path)
+                    } else {
+                        None
+                    }
+                    .ok_or_else(|| {
+                        let call_func = &call.func;
+                        let call_func = quote!(#call_func);
+                        Error::new_spanned(
+                            &call.func,
+                            format!("Illegal builder setting group {}", call_func),
+                        )
+                    })?;
+                    match subsetting_name.as_ref() {
+                        "setter" => {
+                            for arg in call.args {
+                                self.setter.apply_meta(arg)?;
+                            }
+                            Ok(())
+                        }
+                        _ => Err(Error::new_spanned(
+                            &call.func,
+                            format!("Illegal builder setting group name {}", subsetting_name),
+                        )),
+                    }
+                }
+                syn::Expr::Unary(syn::ExprUnary {
+                    op: syn::UnOp::Not(_),
+                    expr,
+                    ..
+                }) => {
+                    if let syn::Expr::Path(path) = *expr {
+                        let name = path_to_single_string(&path.path)
+                            .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
+                        match name.as_str() {
+                            "default" => {
+                                self.default = None;
+                                Ok(())
+                            }
+                            _ => Err(Error::new_spanned(path, "Unknown setting".to_owned())),
+                        }
+                    } else {
+                        Err(Error::new_spanned(
+                            expr,
+                            "Expected simple identifier".to_owned(),
+                        ))
+                    }
+                }
+                _ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
+            }
+        }
+    }
+
+    impl SetterSettings {
+        fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
+            match expr {
+                syn::Expr::Assign(assign) => {
+                    let name = expr_to_single_string(&assign.left)
+                        .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
+                    match name.as_str() {
+                        "doc" => {
+                            self.doc = Some(*assign.right);
+                            Ok(())
+                        }
+                        _ => Err(Error::new_spanned(
+                            &assign,
+                            format!("Unknown parameter {:?}", name),
+                        )),
+                    }
+                }
+                syn::Expr::Path(path) => {
+                    let name = path_to_single_string(&path.path)
+                        .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
+                    macro_rules! handle_fields {
+                    ( $( $flag:expr, $field:ident, $already:expr; )* ) => {
+                        match name.as_str() {
+                            $(
+                                $flag => {
+                                    if self.$field {
+                                        Err(Error::new(path.span(), concat!("Illegal setting - field is already ", $already)))
+                                    } else {
+                                        self.$field = true;
+                                        Ok(())
+                                    }
+                                }
+                            )*
+                            _ => Err(Error::new_spanned(
+                                    &path,
+                                    format!("Unknown setter parameter {:?}", name),
+                            ))
+                        }
+                    }
+                }
+                    handle_fields!(
+                        "skip", skip, "skipped";
+                        "into", auto_into, "calling into() on the argument";
+                        "strip_option", strip_option, "putting the argument in Some(...)";
+                    )
+                }
+                syn::Expr::Unary(syn::ExprUnary {
+                    op: syn::UnOp::Not(_),
+                    expr,
+                    ..
+                }) => {
+                    if let syn::Expr::Path(path) = *expr {
+                        let name = path_to_single_string(&path.path)
+                            .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
+                        match name.as_str() {
+                            "doc" => {
+                                self.doc = None;
+                                Ok(())
+                            }
+                            "skip" => {
+                                self.skip = false;
+                                Ok(())
+                            }
+                            "auto_into" => {
+                                self.auto_into = false;
+                                Ok(())
+                            }
+                            "strip_option" => {
+                                self.strip_option = false;
+                                Ok(())
+                            }
+                            _ => Err(Error::new_spanned(path, "Unknown setting".to_owned())),
+                        }
+                    } else {
+                        Err(Error::new_spanned(
+                            expr,
+                            "Expected simple identifier".to_owned(),
+                        ))
+                    }
+                }
+                _ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
+            }
+        }
+    }
+}
+
+mod struct_info {
+    use proc_macro2::TokenStream;
+    use quote::quote;
+    use syn::parse::Error;
+
+    use super::field_info::{FieldBuilderAttr, FieldInfo};
+    use super::util::{
+        empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,
+        modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple,
+    };
+
+    #[derive(Debug)]
+    pub struct StructInfo<'a> {
+        pub vis: &'a syn::Visibility,
+        pub name: &'a syn::Ident,
+        pub generics: &'a syn::Generics,
+        pub fields: Vec<FieldInfo<'a>>,
+
+        pub builder_attr: TypeBuilderAttr,
+        pub builder_name: syn::Ident,
+        pub conversion_helper_trait_name: syn::Ident,
+        pub core: syn::Ident,
+    }
+
+    impl<'a> StructInfo<'a> {
+        pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
+            self.fields.iter().filter(|f| !f.builder_attr.setter.skip)
+        }
+
+        pub fn new(
+            ast: &'a syn::DeriveInput,
+            fields: impl Iterator<Item = &'a syn::Field>,
+        ) -> Result<StructInfo<'a>, Error> {
+            let builder_attr = TypeBuilderAttr::new(&ast.attrs)?;
+            let builder_name = strip_raw_ident_prefix(format!("{}Builder", ast.ident));
+            Ok(StructInfo {
+                vis: &ast.vis,
+                name: &ast.ident,
+                generics: &ast.generics,
+                fields: fields
+                    .enumerate()
+                    .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone()))
+                    .collect::<Result<_, _>>()?,
+                builder_attr,
+                builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()),
+                conversion_helper_trait_name: syn::Ident::new(
+                    &format!("{}_Optional", builder_name),
+                    proc_macro2::Span::call_site(),
+                ),
+                core: syn::Ident::new(
+                    &format!("{}_core", builder_name),
+                    proc_macro2::Span::call_site(),
+                ),
+            })
+        }
+
+        fn modify_generics<F: FnMut(&mut syn::Generics)>(&self, mut mutator: F) -> syn::Generics {
+            let mut generics = self.generics.clone();
+            mutator(&mut generics);
+            generics
+        }
+
+        pub fn builder_creation_impl(&self) -> Result<TokenStream, Error> {
+            let StructInfo {
+                ref vis,
+                ref name,
+                ref builder_name,
+                ..
+            } = *self;
+            let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
+            let all_fields_param = syn::GenericParam::Type(
+                syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
+            );
+            let b_generics = self.modify_generics(|g| {
+                g.params.insert(0, all_fields_param.clone());
+            });
+            let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type()));
+            let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| {
+                args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into()));
+            });
+            let phantom_generics = self.generics.params.iter().map(|param| match param {
+                syn::GenericParam::Lifetime(lifetime) => {
+                    let lifetime = &lifetime.lifetime;
+                    quote!(core::marker::PhantomData<&#lifetime ()>)
+                }
+                syn::GenericParam::Type(ty) => {
+                    let ty = &ty.ident;
+                    quote!(core::marker::PhantomData<#ty>)
+                }
+                syn::GenericParam::Const(_cnst) => {
+                    quote!()
+                }
+            });
+            let builder_method_doc = match self.builder_attr.builder_method_doc {
+                Some(ref doc) => quote!(#doc),
+                None => {
+                    let doc = format!(
+                        "
+Create a builder for building `{name}`.
+On the builder, call {setters} to set the values of the fields.
+Finally, call `.build()` to create the instance of `{name}`.
+                    ",
+                        name = self.name,
+                        setters = {
+                            let mut result = String::new();
+                            let mut is_first = true;
+                            for field in self.included_fields() {
+                                use std::fmt::Write;
+                                if is_first {
+                                    is_first = false;
+                                } else {
+                                    write!(&mut result, ", ").unwrap();
+                                }
+                                write!(&mut result, "`.{}(...)`", field.name).unwrap();
+                                if field.builder_attr.default.is_some() {
+                                    write!(&mut result, "(optional)").unwrap();
+                                }
+                            }
+                            result
+                        }
+                    );
+                    quote!(#doc)
+                }
+            };
+            let builder_type_doc = if self.builder_attr.doc {
+                match self.builder_attr.builder_type_doc {
+                    Some(ref doc) => quote!(#[doc = #doc]),
+                    None => {
+                        let doc = format!(
+                        "Builder for [`{name}`] instances.\n\nSee [`{name}::builder()`] for more info.",
+                        name = name
+                    );
+                        quote!(#[doc = #doc])
+                    }
+                }
+            } else {
+                quote!(#[doc(hidden)])
+            };
+
+            let (b_generics_impl, b_generics_ty, b_generics_where_extras_predicates) =
+                b_generics.split_for_impl();
+            let mut b_generics_where: syn::WhereClause = syn::parse2(quote! {
+                where TypedBuilderFields: Clone
+            })?;
+            if let Some(predicates) = b_generics_where_extras_predicates {
+                b_generics_where
+                    .predicates
+                    .extend(predicates.predicates.clone());
+            }
+
+            Ok(quote! {
+                impl #impl_generics #name #ty_generics #where_clause {
+                    #[doc = #builder_method_doc]
+                    #[allow(dead_code)]
+                    #vis fn builder() -> #builder_name #generics_with_empty {
+                        #builder_name {
+                            fields: #empties_tuple,
+                            _phantom: core::default::Default::default(),
+                        }
+                    }
+                }
+
+                #[must_use]
+                #builder_type_doc
+                #[allow(dead_code, non_camel_case_types, non_snake_case)]
+                #vis struct #builder_name #b_generics {
+                    fields: #all_fields_param,
+                    _phantom: (#( #phantom_generics ),*),
+                }
+
+                impl #b_generics_impl Clone for #builder_name #b_generics_ty #b_generics_where {
+                    fn clone(&self) -> Self {
+                        Self {
+                            fields: self.fields.clone(),
+                            _phantom: Default::default(),
+                        }
+                    }
+                }
+            })
+        }
+
+        // TODO: once the proc-macro crate limitation is lifted, make this an util trait of this
+        // crate.
+        pub fn conversion_helper_impl(&self) -> Result<TokenStream, Error> {
+            let trait_name = &self.conversion_helper_trait_name;
+            Ok(quote! {
+                #[doc(hidden)]
+                #[allow(dead_code, non_camel_case_types, non_snake_case)]
+                pub trait #trait_name<T> {
+                    fn into_value<F: FnOnce() -> T>(self, default: F) -> T;
+                }
+
+                impl<T> #trait_name<T> for () {
+                    fn into_value<F: FnOnce() -> T>(self, default: F) -> T {
+                        default()
+                    }
+                }
+
+                impl<T> #trait_name<T> for (T,) {
+                    fn into_value<F: FnOnce() -> T>(self, _: F) -> T {
+                        self.0
+                    }
+                }
+            })
+        }
+
+        pub fn field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
+            let StructInfo {
+                ref builder_name, ..
+            } = *self;
+
+            let descructuring = self.included_fields().map(|f| {
+                if f.ordinal == field.ordinal {
+                    quote!(_)
+                } else {
+                    let name = f.name;
+                    quote!(#name)
+                }
+            });
+            let reconstructing = self.included_fields().map(|f| f.name);
+
+            let &FieldInfo {
+                name: ref field_name,
+                ty: ref field_type,
+                ..
+            } = field;
+            let mut ty_generics: Vec<syn::GenericArgument> = self
+                .generics
+                .params
+                .iter()
+                .map(|generic_param| match generic_param {
+                    syn::GenericParam::Type(type_param) => {
+                        let ident = type_param.ident.clone();
+                        syn::parse(quote!(#ident).into()).unwrap()
+                    }
+                    syn::GenericParam::Lifetime(lifetime_def) => {
+                        syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())
+                    }
+                    syn::GenericParam::Const(const_param) => {
+                        let ident = const_param.ident.clone();
+                        syn::parse(quote!(#ident).into()).unwrap()
+                    }
+                })
+                .collect();
+            let mut target_generics_tuple = empty_type_tuple();
+            let mut ty_generics_tuple = empty_type_tuple();
+            let generics = self.modify_generics(|g| {
+                let index_after_lifetime_in_generics = g
+                    .params
+                    .iter()
+                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
+                    .count();
+                for f in self.included_fields() {
+                    if f.ordinal == field.ordinal {
+                        ty_generics_tuple.elems.push_value(empty_type());
+                        target_generics_tuple
+                            .elems
+                            .push_value(f.tuplized_type_ty_param());
+                    } else {
+                        g.params
+                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());
+                        let generic_argument: syn::Type = f.type_ident();
+                        ty_generics_tuple.elems.push_value(generic_argument.clone());
+                        target_generics_tuple.elems.push_value(generic_argument);
+                    }
+                    ty_generics_tuple.elems.push_punct(Default::default());
+                    target_generics_tuple.elems.push_punct(Default::default());
+                }
+            });
+            let mut target_generics = ty_generics.clone();
+            let index_after_lifetime_in_generics = target_generics
+                .iter()
+                .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
+                .count();
+            target_generics.insert(
+                index_after_lifetime_in_generics,
+                syn::GenericArgument::Type(target_generics_tuple.into()),
+            );
+            ty_generics.insert(
+                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.setter.doc {
+                Some(ref doc) => quote!(#[doc = #doc]),
+                None => quote!(),
+            };
+
+            // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of
+            // nesting is different so we have to do this little dance.
+            let arg_type = if field.builder_attr.setter.strip_option {
+                let internal_type = field.type_from_inside_option().ok_or_else(|| {
+                    Error::new_spanned(
+                        &field_type,
+                        "can't `strip_option` - field is not `Option<...>`",
+                    )
+                })?;
+                internal_type
+            } else {
+                field_type
+            };
+            let (arg_type, arg_expr) = if field.builder_attr.setter.auto_into {
+                (
+                    quote!(impl core::convert::Into<#arg_type>),
+                    quote!(#field_name.into()),
+                )
+            } else {
+                (quote!(#arg_type), quote!(#field_name))
+            };
+            let arg_expr = if field.builder_attr.setter.strip_option {
+                quote!(Some(#arg_expr))
+            } else {
+                arg_expr
+            };
+
+            let repeated_fields_error_type_name = syn::Ident::new(
+                &format!(
+                    "{}_Error_Repeated_field_{}",
+                    builder_name,
+                    strip_raw_ident_prefix(field_name.to_string())
+                ),
+                proc_macro2::Span::call_site(),
+            );
+            let repeated_fields_error_message = format!("Repeated field {}", field_name);
+
+            Ok(quote! {
+                #[allow(dead_code, non_camel_case_types, missing_docs)]
+                impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
+                    #doc
+                    pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
+                        let #field_name = (#arg_expr,);
+                        let ( #(#descructuring,)* ) = self.fields;
+                        #builder_name {
+                            fields: ( #(#reconstructing,)* ),
+                            _phantom: self._phantom,
+                        }
+                    }
+                }
+                #[doc(hidden)]
+                #[allow(dead_code, non_camel_case_types, non_snake_case)]
+                pub enum #repeated_fields_error_type_name {}
+                #[doc(hidden)]
+                #[allow(dead_code, non_camel_case_types, missing_docs)]
+                impl #impl_generics #builder_name < #( #target_generics ),* > #where_clause {
+                    #[deprecated(
+                        note = #repeated_fields_error_message
+                    )]
+                    pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {
+                        self
+                    }
+                }
+            })
+        }
+
+        pub fn required_field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
+            let StructInfo {
+                ref name,
+                ref builder_name,
+                ..
+            } = self;
+
+            let FieldInfo {
+                name: ref field_name,
+                ..
+            } = field;
+            let mut builder_generics: Vec<syn::GenericArgument> = self
+                .generics
+                .params
+                .iter()
+                .map(|generic_param| match generic_param {
+                    syn::GenericParam::Type(type_param) => {
+                        let ident = &type_param.ident;
+                        syn::parse(quote!(#ident).into()).unwrap()
+                    }
+                    syn::GenericParam::Lifetime(lifetime_def) => {
+                        syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())
+                    }
+                    syn::GenericParam::Const(const_param) => {
+                        let ident = &const_param.ident;
+                        syn::parse(quote!(#ident).into()).unwrap()
+                    }
+                })
+                .collect();
+            let mut builder_generics_tuple = empty_type_tuple();
+            let generics = self.modify_generics(|g| {
+                let index_after_lifetime_in_generics = g
+                    .params
+                    .iter()
+                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
+                    .count();
+                for f in self.included_fields() {
+                    if f.builder_attr.default.is_some() {
+                        // `f` is not mandatory - it does not have it's own fake `build` method, so `field` will need
+                        // to warn about missing `field` whether or not `f` is set.
+                        assert!(
+                            f.ordinal != field.ordinal,
+                            "`required_field_impl` called for optional field {}",
+                            field.name
+                        );
+                        g.params
+                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());
+                        builder_generics_tuple.elems.push_value(f.type_ident());
+                    } else if f.ordinal < field.ordinal {
+                        // Only add a `build` method that warns about missing `field` if `f` is set. If `f` is not set,
+                        // `f`'s `build` method will warn, since it appears earlier in the argument list.
+                        builder_generics_tuple
+                            .elems
+                            .push_value(f.tuplized_type_ty_param());
+                    } else if f.ordinal == field.ordinal {
+                        builder_generics_tuple.elems.push_value(empty_type());
+                    } else {
+                        // `f` appears later in the argument list after `field`, so if they are both missing we will
+                        // show a warning for `field` and not for `f` - which means this warning should appear whether
+                        // or not `f` is set.
+                        g.params
+                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());
+                        builder_generics_tuple.elems.push_value(f.type_ident());
+                    }
+
+                    builder_generics_tuple.elems.push_punct(Default::default());
+                }
+            });
+
+            let index_after_lifetime_in_generics = builder_generics
+                .iter()
+                .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
+                .count();
+            builder_generics.insert(
+                index_after_lifetime_in_generics,
+                syn::GenericArgument::Type(builder_generics_tuple.into()),
+            );
+            let (impl_generics, _, where_clause) = generics.split_for_impl();
+            let (_, ty_generics, _) = self.generics.split_for_impl();
+
+            let early_build_error_type_name = syn::Ident::new(
+                &format!(
+                    "{}_Error_Missing_required_field_{}",
+                    builder_name,
+                    strip_raw_ident_prefix(field_name.to_string())
+                ),
+                proc_macro2::Span::call_site(),
+            );
+            let early_build_error_message = format!("Missing required field {}", field_name);
+
+            Ok(quote! {
+                #[doc(hidden)]
+                #[allow(dead_code, non_camel_case_types, non_snake_case)]
+                pub enum #early_build_error_type_name {}
+                #[doc(hidden)]
+                #[allow(dead_code, non_camel_case_types, missing_docs, clippy::panic)]
+                impl #impl_generics #builder_name < #( #builder_generics ),* > #where_clause {
+                    #[deprecated(
+                        note = #early_build_error_message
+                    )]
+                    pub fn build(self, _: #early_build_error_type_name) -> #name #ty_generics {
+                        panic!();
+                    }
+                }
+            })
+        }
+
+        pub fn build_method_impl(&self) -> TokenStream {
+            let StructInfo {
+                ref name,
+                ref builder_name,
+                ..
+            } = *self;
+
+            let generics = self.modify_generics(|g| {
+                let index_after_lifetime_in_generics = g
+                    .params
+                    .iter()
+                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
+                    .count();
+                for field in self.included_fields() {
+                    if field.builder_attr.default.is_some() {
+                        let trait_ref = syn::TraitBound {
+                            paren_token: None,
+                            lifetimes: None,
+                            modifier: syn::TraitBoundModifier::None,
+                            path: syn::PathSegment {
+                                ident: self.conversion_helper_trait_name.clone(),
+                                arguments: syn::PathArguments::AngleBracketed(
+                                    syn::AngleBracketedGenericArguments {
+                                        colon2_token: None,
+                                        lt_token: Default::default(),
+                                        args: make_punctuated_single(syn::GenericArgument::Type(
+                                            field.ty.clone(),
+                                        )),
+                                        gt_token: Default::default(),
+                                    },
+                                ),
+                            }
+                            .into(),
+                        };
+                        let mut generic_param: syn::TypeParam = field.generic_ident.clone().into();
+                        generic_param.bounds.push(trait_ref.into());
+                        g.params
+                            .insert(index_after_lifetime_in_generics, generic_param.into());
+                    }
+                }
+            });
+            let (impl_generics, _, _) = generics.split_for_impl();
+
+            let (_, ty_generics, where_clause) = self.generics.split_for_impl();
+
+            let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| {
+                args.insert(
+                    0,
+                    syn::GenericArgument::Type(
+                        type_tuple(self.included_fields().map(|field| {
+                            if field.builder_attr.default.is_some() {
+                                field.type_ident()
+                            } else {
+                                field.tuplized_type_ty_param()
+                            }
+                        }))
+                        .into(),
+                    ),
+                );
+            });
+
+            let descructuring = self.included_fields().map(|f| f.name);
+
+            let helper_trait_name = &self.conversion_helper_trait_name;
+            // The default of a field can refer to earlier-defined fields, which we handle by
+            // writing out a bunch of `let` statements first, which can each refer to earlier ones.
+            // This means that field ordering may actually be significant, which isn’t ideal. We could
+            // relax that restriction by calculating a DAG of field default dependencies and
+            // reordering based on that, but for now this much simpler thing is a reasonable approach.
+            let assignments = self.fields.iter().map(|field| {
+                let name = &field.name;
+                if let Some(ref default) = field.builder_attr.default {
+                    if field.builder_attr.setter.skip {
+                        quote!(let #name = #default;)
+                    } else {
+                        quote!(let #name = #helper_trait_name::into_value(#name, || #default);)
+                    }
+                } else {
+                    quote!(let #name = #name.0;)
+                }
+            });
+            let field_names = self.fields.iter().map(|field| field.name);
+            let doc = if self.builder_attr.doc {
+                match self.builder_attr.build_method_doc {
+                    Some(ref doc) => quote!(#[doc = #doc]),
+                    None => {
+                        // I’d prefer “a” or “an” to “its”, but determining which is grammatically
+                        // correct is roughly impossible.
+                        let doc =
+                            format!("Finalise the builder and create its [`{}`] instance", name);
+                        quote!(#[doc = #doc])
+                    }
+                }
+            } else {
+                quote!()
+            };
+            quote!(
+                #[allow(dead_code, non_camel_case_types, missing_docs)]
+                impl #impl_generics #builder_name #modified_ty_generics #where_clause {
+                    #doc
+                    pub fn build(self) -> #name #ty_generics {
+                        let ( #(#descructuring,)* ) = self.fields;
+                        #( #assignments )*
+                        #name {
+                            #( #field_names ),*
+                        }
+                    }
+                }
+            )
+        }
+    }
+
+    #[derive(Debug, Default)]
+    pub struct TypeBuilderAttr {
+        /// Whether to show docs for the `TypeBuilder` type (rather than hiding them).
+        pub doc: bool,
+
+        /// Docs on the `Type::builder()` method.
+        pub builder_method_doc: Option<syn::Expr>,
+
+        /// Docs on the `TypeBuilder` type. Specifying this implies `doc`, but you can just specify
+        /// `doc` instead and a default value will be filled in here.
+        pub builder_type_doc: Option<syn::Expr>,
+
+        /// Docs on the `TypeBuilder.build()` method. Specifying this implies `doc`, but you can just
+        /// specify `doc` instead and a default value will be filled in here.
+        pub build_method_doc: Option<syn::Expr>,
+
+        pub field_defaults: FieldBuilderAttr,
+    }
+
+    impl TypeBuilderAttr {
+        pub fn new(attrs: &[syn::Attribute]) -> Result<TypeBuilderAttr, Error> {
+            let mut result = TypeBuilderAttr::default();
+            for attr in attrs {
+                if path_to_single_string(&attr.path).as_deref() != Some("builder") {
+                    continue;
+                }
+
+                if attr.tokens.is_empty() {
+                    continue;
+                }
+                let as_expr: syn::Expr = syn::parse2(attr.tokens.clone())?;
+
+                match as_expr {
+                    syn::Expr::Paren(body) => {
+                        result.apply_meta(*body.expr)?;
+                    }
+                    syn::Expr::Tuple(body) => {
+                        for expr in body.elems.into_iter() {
+                            result.apply_meta(expr)?;
+                        }
+                    }
+                    _ => {
+                        return Err(Error::new_spanned(attr.tokens.clone(), "Expected (<...>)"));
+                    }
+                }
+            }
+
+            Ok(result)
+        }
+
+        fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
+            match expr {
+                syn::Expr::Assign(assign) => {
+                    let name = expr_to_single_string(&assign.left)
+                        .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
+                    match name.as_str() {
+                        "builder_method_doc" => {
+                            self.builder_method_doc = Some(*assign.right);
+                            Ok(())
+                        }
+                        "builder_type_doc" => {
+                            self.builder_type_doc = Some(*assign.right);
+                            self.doc = true;
+                            Ok(())
+                        }
+                        "build_method_doc" => {
+                            self.build_method_doc = Some(*assign.right);
+                            self.doc = true;
+                            Ok(())
+                        }
+                        _ => Err(Error::new_spanned(
+                            &assign,
+                            format!("Unknown parameter {:?}", name),
+                        )),
+                    }
+                }
+                syn::Expr::Path(path) => {
+                    let name = path_to_single_string(&path.path)
+                        .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
+                    match name.as_str() {
+                        "doc" => {
+                            self.doc = true;
+                            Ok(())
+                        }
+                        _ => Err(Error::new_spanned(
+                            &path,
+                            format!("Unknown parameter {:?}", name),
+                        )),
+                    }
+                }
+                syn::Expr::Call(call) => {
+                    let subsetting_name = if let syn::Expr::Path(path) = &*call.func {
+                        path_to_single_string(&path.path)
+                    } else {
+                        None
+                    }
+                    .ok_or_else(|| {
+                        let call_func = &call.func;
+                        let call_func = quote!(#call_func);
+                        Error::new_spanned(
+                            &call.func,
+                            format!("Illegal builder setting group {}", call_func),
+                        )
+                    })?;
+                    match subsetting_name.as_str() {
+                        "field_defaults" => {
+                            for arg in call.args {
+                                self.field_defaults.apply_meta(arg)?;
+                            }
+                            Ok(())
+                        }
+                        _ => Err(Error::new_spanned(
+                            &call.func,
+                            format!("Illegal builder setting group name {}", subsetting_name),
+                        )),
+                    }
+                }
+                _ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
+            }
+        }
+    }
+}

+ 217 - 88
packages/core-macro/src/rsxt.rs

@@ -88,6 +88,7 @@ impl ToTokens for RsxRender {
 enum Node {
     Element(Element),
     Text(TextNode),
+    Component(Component),
 }
 
 impl ToTokens for ToToksCtx<&Node> {
@@ -95,28 +96,29 @@ impl ToTokens for ToToksCtx<&Node> {
         match &self.inner {
             Node::Element(el) => self.recurse(el).to_tokens(tokens),
             Node::Text(txt) => self.recurse(txt).to_tokens(tokens),
+            Node::Component(c) => self.recurse(c).to_tokens(tokens),
         }
     }
 }
 
-// impl Node {
-//     fn peek(s: ParseStream) -> bool {
-//         (s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr)
-//     }
-// }
-
 impl Parse for Node {
     fn parse(s: ParseStream) -> Result<Self> {
-        let fork = s.fork();
-
-        let ret = if let Ok(text) = fork.parse::<TextNode>() {
-            s.advance_to(&fork);
-            Ok(Self::Text(text))
-        } else if let Ok(el) = s.parse::<Element>() {
-            Ok(Self::Element(el))
+        let fork1 = s.fork();
+        let fork2 = s.fork();
+
+        // todo: map issues onto the second fork if any arise
+        // it's unlikely that textnodes or components would fail?
+
+        let ret = if let Ok(text) = fork1.parse::<TextNode>() {
+            s.advance_to(&fork1);
+            Self::Text(text)
+        } else if let Ok(element) = fork2.parse::<Element>() {
+            s.advance_to(&fork2);
+            Self::Element(element)
+        } else if let Ok(comp) = s.parse::<Component>() {
+            Self::Component(comp)
         } else {
-            // TODO: Span information
-            panic!("Not a valid child node");
+            return Err(Error::new(s.span(), "Failed to parse as a valid child"));
         };
 
         // consume comma if it exists
@@ -124,7 +126,153 @@ impl Parse for Node {
         if s.peek(Token![,]) {
             let _ = s.parse::<Token![,]>();
         }
-        ret
+        Ok(ret)
+    }
+}
+
+struct Component {
+    name: Ident,
+    body: Vec<ComponentField>,
+    // attrs: Vec<Attr>,
+    children: MaybeExpr<Vec<Node>>,
+}
+
+impl ToTokens for ToToksCtx<&Component> {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let name = &self.inner.name;
+
+        // let mut toks = quote! {};
+
+        // for attr in self.inner.attrs.iter() {
+        //     self.recurse(attr).to_tokens(&mut toks);
+        // }
+
+        let mut builder = quote! {
+            fc_to_builder(#name)
+        };
+
+        for field in &self.inner.body {
+            builder.append_all(quote! {#field});
+        }
+
+        builder.append_all(quote! {
+            .build()
+        });
+
+        // panic!("tokens are {:#?}", toks);
+
+        // no children right now
+
+        // match &self.inner.children {
+        //     MaybeExpr::Expr(expr) => tokens.append_all(quote! {
+        //         .children(#expr)
+        //     }),
+        //     MaybeExpr::Literal(nodes) => {
+        //         let mut children = nodes.iter();
+        //         if let Some(child) = children.next() {
+        //             let mut inner_toks = TokenStream2::new();
+        //             self.recurse(child).to_tokens(&mut inner_toks);
+        //             while let Some(child) = children.next() {
+        //                 quote!(,).to_tokens(&mut inner_toks);
+        //                 self.recurse(child).to_tokens(&mut inner_toks);
+        //             }
+        //             tokens.append_all(quote! {
+        //                 .children([#inner_toks])
+        //             });
+        //         }
+        //     }
+        // }
+        // tokens.append_all(quote! {
+        //     .finish()
+        // });
+        let toks = tokens.append_all(quote! {
+            dioxus::builder::virtual_child(ctx, #name, #builder)
+        });
+    }
+}
+
+impl Parse for Component {
+    fn parse(s: ParseStream) -> Result<Self> {
+        // TODO: reject anything weird/nonstandard
+        // we want names *only*
+        let name = Ident::parse_any(s)?;
+
+        if crate::util::is_valid_tag(&name.to_string()) {
+            return Err(Error::new(
+                name.span(),
+                "Components cannot share names with valid HTML tags",
+            ));
+        }
+
+        // parse the guts
+        let content: ParseBuffer;
+        syn::braced!(content in s);
+
+        let mut body: Vec<ComponentField> = Vec::new();
+        let mut children: Vec<Node> = Vec::new();
+
+        // parse_element_content(content, &mut attrs, &mut children);
+
+        'parsing: loop {
+            // [1] Break if empty
+            if content.is_empty() {
+                break 'parsing;
+            }
+
+            if let Ok(field) = content.parse::<ComponentField>() {
+                body.push(field);
+            }
+        }
+
+        // let body = content.parse()?;
+
+        // eventually we'll parse the attrs
+        // let mut attrs: Vec<Attr> = vec![];
+        let mut children: Vec<Node> = vec![];
+        // parse_element_content(content, &mut attrs, &mut children);
+
+        let children = MaybeExpr::Literal(children);
+
+        Ok(Self {
+            name,
+            body,
+            // attrs,
+            children,
+        })
+    }
+}
+
+// the struct's fields info
+pub struct ComponentField {
+    name: Ident,
+    content: Expr,
+}
+
+impl Parse for ComponentField {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut name = Ident::parse_any(input)?;
+        let name_str = name.to_string();
+        input.parse::<Token![:]>()?;
+        let content = input.parse()?;
+
+        // consume comma if it exists
+        // we don't actually care if there *are* commas between attrs
+        if input.peek(Token![,]) {
+            let _ = input.parse::<Token![,]>();
+        }
+
+        Ok(Self { name, content })
+    }
+}
+
+impl ToTokens for &ComponentField {
+    // impl ToTokens for ToToksCtx<&ComponentField> {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let name = &self.name;
+        let content = &self.content;
+        tokens.append_all(quote! {
+            .#name(#content)
+        })
     }
 }
 
@@ -142,12 +290,12 @@ struct Element {
 
 impl ToTokens for ToToksCtx<&Element> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
-        // todo!()
-        // // let ctx = self.ctx;
         let name = &self.inner.name.to_string();
+
         tokens.append_all(quote! {
             dioxus::builder::ElementBuilder::new(ctx, #name)
         });
+
         for attr in self.inner.attrs.iter() {
             self.recurse(attr).to_tokens(tokens);
         }
@@ -182,89 +330,70 @@ impl Parse for Element {
         // we want names *only*
         let name = Ident::parse_any(s)?;
 
+        if !crate::util::is_valid_tag(&name.to_string()) {
+            return Err(Error::new(name.span(), "Not a valid Html tag"));
+        }
+
         // parse the guts
         let content: ParseBuffer;
         syn::braced!(content in s);
 
-        let mut attrs = vec![];
+        let mut attrs: Vec<Attr> = vec![];
         let mut children: Vec<Node> = vec![];
+        parse_element_content(content, &mut attrs, &mut children);
 
-        'parsing: loop {
-            // todo move this around into a more functional style
-            // [1] Break if empty
-            // [2] Try to consume an attr (with comma)
-            // [3] Try to consume a child node (with comma)
-            // [4] Try to consume brackets as anything thats Into<Node>
-            // [last] Fail if none worked
-
-            // [1] Break if empty
-            if content.is_empty() {
-                break 'parsing;
-            }
+        let children = MaybeExpr::Literal(children);
 
-            // [2] Try to consume an attr
-            let fork = content.fork();
-            if let Ok(attr) = fork.parse::<Attr>() {
-                // make sure to advance or your computer will become a spaceheater :)
-                content.advance_to(&fork);
-                attrs.push(attr);
-                continue 'parsing;
-            }
+        Ok(Self {
+            name,
+            attrs,
+            children,
+        })
+    }
+}
 
-            // [3] Try to consume a child node
-            let fork = content.fork();
-            if let Ok(node) = fork.parse::<Node>() {
-                // make sure to advance or your computer will become a spaceheater :)
-                content.advance_to(&fork);
-                children.push(node);
-                continue 'parsing;
-            }
+// used by both vcomponet and velement to parse the contents of the elements into attras and children
+fn parse_element_content(content: ParseBuffer, attrs: &mut Vec<Attr>, children: &mut Vec<Node>) {
+    'parsing: loop {
+        // todo move this around into a more functional style
+        // [1] Break if empty
+        // [2] Try to consume an attr (with comma)
+        // [3] Try to consume a child node (with comma)
+        // [4] Try to consume brackets as anything thats Into<Node>
+        // [last] Fail if none worked
+
+        // [1] Break if empty
+        if content.is_empty() {
+            break 'parsing;
+        }
 
-            // [4] TODO: Parsing brackets
-            // let fork = content.fork();
-            // if let Ok(el) = fork.parse() {
-            //     children.push(el);
-            //     continue 'parsing;
-            // }
+        // [2] Try to consume an attr
+        let fork = content.fork();
+        if let Ok(attr) = fork.parse::<Attr>() {
+            // make sure to advance or your computer will become a spaceheater :)
+            content.advance_to(&fork);
+            attrs.push(attr);
+            continue 'parsing;
+        }
 
-            // todo: pass a descriptive error onto the offending tokens
-            panic!("Entry is not an attr or element\n {}", content)
+        // [3] Try to consume a child node
+        let fork = content.fork();
+        if let Ok(node) = fork.parse::<Node>() {
+            // make sure to advance or your computer will become a spaceheater :)
+            content.advance_to(&fork);
+            children.push(node);
+            continue 'parsing;
         }
 
-        let children = MaybeExpr::Literal(children);
-        // let children = MaybeExpr::Literal(Vec::new());
-        // // Contents of an element can either be a brace (in which case we just copy verbatim), or a
-        // // sequence of nodes.
-        // let children = if s.peek(token::Brace) {
-        //     // expr
-        //     let content;
-        //     syn::braced!(content in s);
-        //     MaybeExpr::Expr(content.parse()?)
-        // } else {
-        //     // nodes
-        //     let mut children = vec![];
-        //     while !(s.peek(Token![<]) && s.peek2(Token![/])) {
-        //         children.push(s.parse()?);
-        //     }
-        //     MaybeExpr::Literal(children)
-        // };
-
-        // // closing element
-        // s.parse::<Token![<]>()?;
-        // s.parse::<Token![/]>()?;
-        // let close = Ident::parse_any(s)?;
-        // if close.to_string() != name.to_string() {
-        //     return Err(Error::new_spanned(
-        //         close,
-        //         "closing element does not match opening",
-        //     ));
+        // [4] TODO: Parsing brackets
+        // let fork = content.fork();
+        // if let Ok(el) = fork.parse() {
+        //     children.push(el);
+        //     continue 'parsing;
         // }
-        // s.parse::<Token![>]>()?;
-        Ok(Self {
-            name,
-            attrs,
-            children,
-        })
+
+        // todo: pass a descriptive error onto the offending tokens
+        panic!("Entry is not an attr or element\n {}", content)
     }
 }
 

+ 137 - 0
packages/core-macro/src/util.rs

@@ -0,0 +1,137 @@
+// use lazy_static::lazy_static;
+use once_cell::sync::Lazy;
+use std::collections::hash_set::HashSet;
+
+static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
+    [
+        "a",
+        "abbr",
+        "address",
+        "area",
+        "article",
+        "aside",
+        "audio",
+        "b",
+        "base",
+        "bdi",
+        "bdo",
+        "big",
+        "blockquote",
+        "body",
+        "br",
+        "button",
+        "canvas",
+        "caption",
+        "cite",
+        "code",
+        "col",
+        "colgroup",
+        "command",
+        "data",
+        "datalist",
+        "dd",
+        "del",
+        "details",
+        "dfn",
+        "dialog",
+        "div",
+        "dl",
+        "dt",
+        "em",
+        "embed",
+        "fieldset",
+        "figcaption",
+        "figure",
+        "footer",
+        "form",
+        "h1",
+        "h2",
+        "h3",
+        "h4",
+        "h5",
+        "h6",
+        "head",
+        "header",
+        "hr",
+        "html",
+        "i",
+        "iframe",
+        "img",
+        "input",
+        "ins",
+        "kbd",
+        "keygen",
+        "label",
+        "legend",
+        "li",
+        "link",
+        "main",
+        "map",
+        "mark",
+        "menu",
+        "menuitem",
+        "meta",
+        "meter",
+        "nav",
+        "noscript",
+        "object",
+        "ol",
+        "optgroup",
+        "option",
+        "output",
+        "p",
+        "param",
+        "picture",
+        "pre",
+        "progress",
+        "q",
+        "rp",
+        "rt",
+        "ruby",
+        "s",
+        "samp",
+        "script",
+        "section",
+        "select",
+        "small",
+        "source",
+        "span",
+        "strong",
+        "style",
+        "sub",
+        "summary",
+        "sup",
+        "table",
+        "tbody",
+        "td",
+        "textarea",
+        "tfoot",
+        "th",
+        "thead",
+        "time",
+        "title",
+        "tr",
+        "track",
+        "u",
+        "ul",
+        "var",
+        "video",
+        "wbr",
+    ]
+    .iter()
+    .cloned()
+    .collect()
+});
+
+/// Whether or not this tag is valid
+///
+/// ```
+/// use html_validation::is_valid_tag;
+///
+/// assert_eq!(is_valid_tag("br"), true);
+///
+/// assert_eq!(is_valid_tag("random"), false);
+/// ```
+pub fn is_valid_tag(tag: &str) -> bool {
+    VALID_TAGS.contains(tag)
+}

+ 38 - 10
packages/core/examples/borrowed.rs

@@ -7,12 +7,15 @@
 
 fn main() {}
 
+use std::fmt::Debug;
+
 use dioxus_core::prelude::*;
 
 struct Props {
     items: Vec<ListItem>,
 }
 
+#[derive(PartialEq)]
 struct ListItem {
     name: String,
     age: u32,
@@ -21,19 +24,20 @@ struct ListItem {
 fn app(ctx: Context, props: &Props) -> DomTree {
     let (f, setter) = use_state(&ctx, || 0);
 
-    ctx.render(move |b| {
-        let mut root = builder::ElementBuilder::new(b, "div");
+    ctx.render(move |c| {
+        let mut root = builder::ElementBuilder::new(c, "div");
         for child in &props.items {
             // notice that the child directly borrows from our vec
             // this makes lists very fast (simply views reusing lifetimes)
+            // <ChildItem item=child hanldler=setter />
             root = root.child(builder::virtual_child(
-                b.bump,
-                ChildProps {
-                    item: child,
-                    item_handler: setter,
-                },
-                // <ChildItem item=child hanldler=setter />
-                child_item,
+                c,
+                ChildItem,
+                // create the props with nothing but the fc<T>
+                fc_to_builder(ChildItem)
+                    .item(child)
+                    .item_handler(setter)
+                    .build(),
             ));
         }
         root.finish()
@@ -41,7 +45,16 @@ fn app(ctx: Context, props: &Props) -> DomTree {
 }
 
 type StateSetter<T> = dyn Fn(T);
+// struct StateSetter<T>(dyn Fn(T));
+
+// impl<T> PartialEq for StateSetter<T> {
+//     fn eq(&self, other: &Self) -> bool {
+//         self as *const _ == other as *const _
+//     }
+// }
 
+// props should derive a partialeq implementation automatically, but implement ptr compare for & fields
+#[derive(Props)]
 struct ChildProps<'a> {
     // Pass down complex structs
     item: &'a ListItem,
@@ -50,7 +63,22 @@ struct ChildProps<'a> {
     item_handler: &'a StateSetter<i32>,
 }
 
-fn child_item(ctx: Context, props: &ChildProps) -> DomTree {
+impl PartialEq for ChildProps<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        // assume the dyn fn is never stable -
+        // wrap with use_callback if it's an issue for you
+        false
+    }
+}
+
+impl<'a> Properties for ChildProps<'a> {
+    type Builder = ChildPropsBuilder<'a, ((), ())>;
+    fn builder() -> <Self as Properties>::Builder {
+        ChildProps::builder()
+    }
+}
+
+fn ChildItem(ctx: Context, props: &ChildProps) -> DomTree {
     todo!()
     //     ctx.render(rsx! {
     //         div {

+ 36 - 0
packages/core/examples/fc.rs

@@ -0,0 +1,36 @@
+use dioxus_core::component::fc_to_builder;
+use dioxus_core::prelude::*;
+use dioxus_core_macro::fc;
+
+use std::marker::PhantomData;
+
+static BLAH: FC<()> = |ctx, props| {
+    let g = "asd".to_string();
+    ctx.render(rsx! {
+        div {
+            SomeComponent {
+                some_field: g
+            }
+        }
+    })
+};
+
+#[derive(PartialEq, Props)]
+pub struct ExampleProps {
+    some_field: String,
+}
+
+static SomeComponent: FC<ExampleProps> = |ctx, props| {
+    ctx.render(rsx! {
+        div { }
+    })
+};
+
+fn main() {}
+
+impl Properties for ExampleProps {
+    type Builder = ExamplePropsBuilder<((),)>;
+    fn builder() -> Self::Builder {
+        ExampleProps::builder()
+    }
+}

+ 12 - 24
packages/core/examples/props.rs

@@ -1,32 +1,20 @@
-use std::marker::PhantomData;
+use dioxus_core_macro::Props;
 
-fn main() {}
+#[derive(Debug, Props)]
+struct SomeProps {
+    a: i32,
 
-trait Props<'parent> {}
-
-struct SomeProps<'p> {
-    text: &'p str,
+    // automatically do the default (none) and automatically Into<T>
+    #[builder(default, setter(strip_option))]
+    b: Option<i32>,
 }
 
-impl<'p> Props<'p> for SomeProps<'p> {}
+// have we committed to the trait style yet?
 
-struct OutputNode<'a> {
-    _p: PhantomData<&'a ()>,
-}
+fn main() {
+    let g: SomeProps = SomeProps::builder().a(10).b(10).build();
 
-// combine reference to self (borrowed from self) and referenfce to parent (borrowed from parent)
-// borrow chain looks like 'p + 's -> 'p + 's -> 'p + 's
-// always adding new lifetimes from self into the mix
-// what does a "self" lifetime mean?
-// a "god" gives us our data
-// the god's lifetime is tied to Context, and the borrowed props object
-// for the sake of simplicity, we just clobber lifetimes.
-// user functions are just lies and we abuse lifetimes.
-// everything is managed at runtime because that's how we make something ergonomc
-// lifetime management in dioxus is just cheating around the rules
-// our kind god manages lifetimes for us so we don't have to, thanks god
-fn something<'s>(_props: &'s SomeProps<'s>) -> OutputNode<'s> {
-    todo!()
+    let r = g.b.unwrap_or_else(|| 10);
 }
 
-// type BC<'p, P: Props<'p>> = for<'a, 'b, 'c> fn(&'a P<'b>) -> OutputNode<'c>;
+fn auto_into_some() {}

+ 41 - 21
packages/core/examples/rsx_usage.rs

@@ -3,33 +3,53 @@ use dioxus_core::prelude::*;
 
 fn main() {}
 
+trait SProps {}
+
+trait Comp {
+    type Props;
+}
+
+impl<T> Comp for FC<T> {
+    type Props = T;
+}
+
+fn test() {
+    // let g: FC<ButtonProps> = CustomButton;
+}
+
+trait Render {
+    fn render(ctx: Context, props: &Self) -> DomTree;
+}
 // include as much as you might accept
-struct ButtonProps<'a> {
+struct Button<'a> {
     onhover: Option<&'a dyn Fn()>,
-    // // A list of any attrs
-    // attrs: AttrList<'a>,
 }
 
-fn CustomButton(ctx: Context, props: ButtonProps) -> DomTree {
-    let onfocus = move |evt: ()| log::debug!("Focused");
-
-    // todo!()
-    ctx.render(rsx! {
-        button {
-            // ..props.attrs,
-            class: "abc123",
-            // style: { a: 2, b: 3, c: 4 },
-            onclick: move |evt| {
-                // log::info("hello world");
-            },
-            // Custom1 { a: 123 }
-            // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
-            // Custom3 { a: "sometext goes here" }
-            // Custom4 { onclick: |evt| log::info("click") }
-        }
-    })
+impl Render for Button<'_> {
+    fn render(ctx: Context, props: &Self) -> DomTree {
+        let onfocus = move |evt: ()| log::debug!("Focused");
+
+        // todo!()
+        ctx.render(rsx! {
+            button {
+                // ..props.attrs,
+                class: "abc123",
+                // style: { a: 2, b: 3, c: 4 },
+                onclick: move |evt| {
+                    // log::info("hello world");
+                },
+                // Custom1 { a: 123 }
+                // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
+                // Custom3 { a: "sometext goes here" }
+                // Custom4 { onclick: |evt| log::info("click") }
+            }
+        })
+    }
 }
 
+// #[fc]
+// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> DomTree {}
+
 // h1 {
 //     tag: "type", abc: 123, class: "big small wide short",
 //     "title1"

+ 13 - 6
packages/core/examples/step.rs

@@ -1,20 +1,19 @@
-use dioxus_core::prelude::*;
+use dioxus_core::{component::Properties, prelude::*};
 
 fn main() -> Result<(), ()> {
-    let p1 = Props { name: "bob".into() };
+    let p1 = SomeProps { name: "bob".into() };
 
     let mut vdom = VirtualDom::new_with_props(Example, p1);
-    vdom.update_props(|p: &mut Props| {});
 
     Ok(())
 }
 
-#[derive(Debug, PartialEq)]
-struct Props {
+#[derive(Debug, PartialEq, Props)]
+struct SomeProps {
     name: String,
 }
 
-static Example: FC<Props> = |ctx, _props| {
+static Example: FC<SomeProps> = |ctx, _props| {
     ctx.render(html! {
         <div>
             <h1> "hello world!" </h1>
@@ -24,3 +23,11 @@ static Example: FC<Props> = |ctx, _props| {
         </div>
     })
 };
+
+// toodo: derive this
+impl Properties for SomeProps {
+    type Builder = SomePropsBuilder<((),)>;
+    fn builder() -> Self::Builder {
+        SomeProps::builder()
+    }
+}

+ 22 - 58
packages/core/src/component.rs

@@ -1,69 +1,33 @@
 //! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible
 //! for components to be used within Nodes.
+//!
+//! Note - using the builder pattern does not required the Properties trait to be implemented - the only thing that matters is
+//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
+//! that ensures compile-time required and optional fields on props.
 
+use crate::innerlude::FC;
 pub type ScopeIdx = generational_arena::Index;
 
-/// The `Component` trait refers to any struct or funciton that can be used as a component
-/// We automatically implement Component for FC<T>
-// pub trait Component {
-//     type Props: Properties<'static>;
-//     fn builder(&'static self) -> Self::Props;
-// }
-
-// // Auto implement component for a FC
-// // Calling the FC is the same as "rendering" it
-// impl<P: Properties<'static>> Component for FC<P> {
-//     type Props = P;
-
-//     fn builder(&self) -> Self::Props {
-//         todo!()
-//     }
-// }
-
-/// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields.
-/// Components take a "properties" object
-// pub trait Properties<'a>
-// where
-//     Self: Debug,
-// {
-//     fn call(&self, ptr: *const ()) {}
-// }
-
-// // Auto implement for no-prop components
-// impl<'a> Properties<'a> for () {
-//     fn call(&self, ptr: *const ()) {}
-// }
-
-#[cfg(test)]
-mod tests {
-    use crate::prelude::bumpalo::Bump;
-    use crate::prelude::*;
+pub trait Properties: PartialEq {
+    type Builder;
+    fn builder() -> Self::Builder;
+}
 
-    fn test_static_fn<'a, P>(b: &'a Bump, r: FC<P>) -> VNode<'a> {
-        todo!()
+pub struct EmptyBuilder;
+impl EmptyBuilder {
+    pub fn build(self) -> () {
+        ()
     }
+}
 
-    static TestComponent: FC<()> = |ctx, props| {
-        //
-
-        ctx.render(html! {
-            <div>
-
-            </div>
-        })
-    };
-
-    static TestComponent2: FC<()> = |ctx, props| {
-        //
-        ctx.render(|ctx| VNode::text("blah"))
-    };
-
-    #[test]
-    fn ensure_types_work() {
-        let bump = Bump::new();
+impl Properties for () {
+    type Builder = EmptyBuilder;
 
-        // Happiness! The VNodes are now allocated onto the bump vdom
-        let _ = test_static_fn(&bump, TestComponent);
-        let _ = test_static_fn(&bump, TestComponent2);
+    fn builder() -> Self::Builder {
+        EmptyBuilder {}
     }
 }
+
+pub fn fc_to_builder<T: Properties>(f: FC<T>) -> T::Builder {
+    T::builder()
+}

+ 1 - 1
packages/core/src/context.rs

@@ -99,7 +99,7 @@ impl<'a> Context<'a> {
     ///     ctx.render(lazy_tree)
     /// }
     ///```
-    pub fn render(self, lazy_nodes: impl FnOnce(&NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
+    pub fn render(self, lazy_nodes: impl FnOnce(&'_ NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
         let ctx = NodeCtx {
             bump: self.bump,
             scope: self.scope,

+ 11 - 5
packages/core/src/debug_renderer.rs

@@ -21,19 +21,25 @@ impl DebugRenderer {
     pub fn log_dom(&self) {}
 }
 
+#[cfg(old)]
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::prelude::*;
+    use crate::scope::Properties;
 
     #[test]
     fn ensure_creation() -> Result<(), ()> {
-        let mut dom = VirtualDom::new(|ctx, props| {
-            //
-            ctx.render(html! { <div>"hello world" </div> })
-        });
+        #[derive(PartialEq)]
+        struct Creation {}
+        impl FC for Creation {
+            fn render(ctx: Context, props: &Self) -> DomTree {
+                ctx.render(html! { <div>"hello world" </div> })
+            }
+        }
+
+        let mut dom = VirtualDom::new_with_props(Creation {});
 
-        // dom.progress()?;
         Ok(())
     }
 }

+ 9 - 27
packages/core/src/diff.rs

@@ -53,23 +53,19 @@ use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc};
 /// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
 /// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
 /// subscriptions and props changes.
-pub struct DiffMachine<'a, 'b> {
+pub struct DiffMachine<'a> {
     pub change_list: EditMachine<'a>,
 
-    pub vdom: &'b VirtualDom,
-    pub cur_idx: ScopeIdx,
     pub diffed: FxHashSet<ScopeIdx>,
     pub need_to_diff: FxHashSet<ScopeIdx>,
 }
 
-impl<'a, 'b> DiffMachine<'a, 'b> {
-    pub fn new(vdom: &'b VirtualDom, bump: &'a Bump, idx: ScopeIdx) -> Self {
+impl<'a> DiffMachine<'a> {
+    pub fn new(bump: &'a Bump) -> Self {
         Self {
-            cur_idx: idx,
             change_list: EditMachine::new(bump),
             diffed: FxHashSet::default(),
             need_to_diff: FxHashSet::default(),
-            vdom,
         }
     }
 
@@ -118,10 +114,10 @@ impl<'a, 'b> DiffMachine<'a, 'b> {
             }
 
             (VNode::Component(cold), VNode::Component(cnew)) => {
-                if cold.comp != cnew.comp {
-                    // queue an event to mount this new component
-                    return;
-                }
+                // if cold.comp != cnew.comp {
+                //     // queue an event to mount this new component
+                //     return;
+                // }
 
                 // compare props.... somehow.....
 
@@ -843,23 +839,9 @@ impl<'a, 'b> DiffMachine<'a, 'b> {
                 }
 
                 listeners.iter().enumerate().for_each(|(id, listener)| {
-                    // if let Some(index) = self.current_idx {
-                    self.change_list.new_event_listener(
-                        listener.event,
-                        listener.scope,
-                        listener.id,
-                    );
-                    // } else {
-                    // Don't panic
-                    // Used for testing
-                    //     log::trace!("Failed to set listener, create was not called in the context of the virtual dom");
-                    // }
+                    self.change_list
+                        .new_event_listener(listener.event, listener.scope, listener.id)
                 });
-                // for l in listeners {
-                // unsafe {
-                //     registry.add(l);
-                // }
-                // }
 
                 for attr in attributes {
                     self.change_list

+ 25 - 25
packages/core/src/hooks.rs

@@ -183,30 +183,30 @@ mod use_reducer_def {
             Decr,
         }
 
-        #[allow(unused)]
-        static Example: FC<()> = |ctx, props| {
-            let (count, reduce) = use_reducer(
-                &ctx,
-                || 0,
-                |count, action| match action {
-                    Actions::Incr => *count += 1,
-                    Actions::Decr => *count -= 1,
-                },
-            );
-
-            ctx.render(rsx! {
-                div {
-                    h1 {"Count: {count}"}
-                    button {
-                        "Increment"
-                        onclick: move |_| reduce(Actions::Incr)
-                    }
-                    button {
-                        "Decrement"
-                        onclick: move |_| reduce(Actions::Decr)
-                    }
-                }
-            })
-        };
+        // #[allow(unused)]
+        // static Example: FC<()> = |ctx, props| {
+        //     let (count, reduce) = use_reducer(
+        //         &ctx,
+        //         || 0,
+        //         |count, action| match action {
+        //             Actions::Incr => *count += 1,
+        //             Actions::Decr => *count -= 1,
+        //         },
+        //     );
+
+        //     ctx.render(rsx! {
+        //         div {
+        //             h1 {"Count: {count}"}
+        //             button {
+        //                 "Increment"
+        //                 onclick: move |_| reduce(Actions::Incr)
+        //             }
+        //             button {
+        //                 "Decrement"
+        //                 onclick: move |_| reduce(Actions::Decr)
+        //             }
+        //         }
+        //     })
+        // };
     }
 }

+ 6 - 4
packages/core/src/lib.rs

@@ -90,6 +90,7 @@ pub mod builder {
 pub(crate) mod innerlude {
     // pub(crate) use crate::component::Properties;
 
+    pub(crate) use crate::component::Properties;
     pub(crate) use crate::context::Context;
     pub(crate) use crate::error::{Error, Result};
     use crate::nodes;
@@ -122,13 +123,14 @@ pub(crate) mod innerlude {
     // Re-export the FC macro
     pub use crate as dioxus;
     pub use crate::nodebuilder as builder;
-    pub use dioxus_core_macro::{fc, html, rsx};
+    pub use dioxus_core_macro::{html, rsx};
+    // pub use dioxus_core_macro::{fc, html, rsx};
 }
 
 /// Re-export common types for ease of development use.
 /// Essential when working with the html! macro
 pub mod prelude {
-    // pub use crate::component::Properties;
+    pub use crate::component::{fc_to_builder, Properties};
     pub use crate::context::Context;
     use crate::nodes;
     pub use crate::virtual_dom::VirtualDom;
@@ -150,8 +152,8 @@ pub mod prelude {
     pub use crate as dioxus;
     pub use crate::nodebuilder as builder;
     // pub use dioxus_core_macro::fc;
-    pub use dioxus_core_macro::format_args_f;
-    pub use dioxus_core_macro::{fc, html, rsx};
+
+    pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
 
     pub use crate::component::ScopeIdx;
     pub use crate::diff::DiffMachine;

+ 9 - 2
packages/core/src/nodebuilder.rs

@@ -5,7 +5,7 @@ use std::{borrow::BorrowMut, ops::Deref};
 use crate::{
     context::NodeCtx,
     events::VirtualEvent,
-    innerlude::VComponent,
+    innerlude::{VComponent, FC},
     nodes::{Attribute, Listener, NodeKey, VNode},
     prelude::VElement,
 };
@@ -524,7 +524,14 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
 //     }
 // }
 
-pub fn virtual_child<'a, T>(_bump: &'a Bump, _props: T, _f: crate::innerlude::FC<T>) -> VNode<'a> {
+// _f: crate::innerlude::FC<T>,
+// _props: T
+pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>(
+    ctx: &'b NodeCtx<'a>,
+    f: FC<T>,
+    p: T,
+) -> VNode<'a> {
+    // crate::nodes::VComponent
     todo!()
     // VNode::Component()
 }

+ 40 - 10
packages/core/src/nodes.rs

@@ -296,8 +296,13 @@ mod vtext {
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 mod vcomponent {
-    use crate::innerlude::{Context, ScopeIdx, FC};
-    use std::{any::TypeId, cell::RefCell, marker::PhantomData, rc::Rc};
+    use crate::innerlude::{Context, Properties, ScopeIdx, FC};
+    use std::{
+        any::{Any, TypeId},
+        cell::RefCell,
+        marker::PhantomData,
+        rc::Rc,
+    };
 
     use super::DomTree;
 
@@ -306,16 +311,18 @@ mod vcomponent {
     #[derive(Debug)]
     pub struct VComponent<'src> {
         _p: PhantomData<&'src ()>,
-        pub(crate) props: Box<dyn std::any::Any>,
-        pub(crate) props_type: TypeId,
-        pub(crate) comp: *const (),
-        pub(crate) caller: Caller,
-
+        // pub(crate) props: Box<dyn std::any::Any>,
+        // pub(crate) props_type: TypeId,
+        // pub(crate) comp: *const (),
+        // pub(crate) caller: Caller,
+        pub(crate) comparator: Comparator,
         // once a component gets mounted, its parent gets a stable address.
         // this way we can carry the scope index from between renders
         // genius, really!
-        pub assigned_scope: StableScopeAddres,
+        // pub assigned_scope: StableScopeAddres,
     }
+    pub struct Comparator(Box<dyn Fn(&dyn Any) -> bool>);
+    // pub struct Comparator<'src>(&'src dyn Fn(&dyn Any) -> bool);
 
     pub struct Caller(Box<dyn Fn(Context) -> DomTree>);
     impl std::fmt::Debug for Caller {
@@ -324,12 +331,35 @@ mod vcomponent {
         }
     }
 
+    impl std::fmt::Debug for Comparator {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            todo!()
+        }
+    }
+
     impl<'a> VComponent<'a> {
         // use the type parameter on props creation and move it into a portable context
         // this lets us keep scope generic *and* downcast its props when we need to:
         // - perform comparisons when diffing (memoization)
         // -
-        pub fn new<P>(comp: FC<P>, props: P) -> Self {
+        pub fn new<P: Properties + 'static>(caller: FC<P>, comp: P) -> Self {
+            todo!()
+            // let p = Rc::new(props);
+
+            // let props_comparator = move |new_props: &dyn Any| -> bool {
+            //     new_props
+            //         .downcast_ref::<P>()
+            //         .map(|new_p| p.as_ref() == new_p)
+            //         .unwrap_or_else(|| {
+            //             log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL");
+            //             false
+            //         })
+            // };
+
+            // Self {
+            //     _p: PhantomData,
+            //     comparator: Comparator(Box::new(props_comparator)),
+            // }
             // let caller = move |ctx: Context| {
             //     let t = comp(ctx, &props);
             //     t
@@ -355,7 +385,7 @@ mod vcomponent {
             //     }
             // }
 
-            todo!()
+            // todo!()
             // Self {
             //     _p: PhantomData {},
             //     props,

+ 69 - 70
packages/core/src/scope.rs

@@ -11,10 +11,6 @@ use std::{
     ops::Deref,
 };
 
-pub trait Properties: PartialEq {}
-// just for now
-impl<T: PartialEq> Properties for T {}
-
 pub trait Scoped {
     fn run(&mut self);
     fn compare_props(&self, new: &dyn std::any::Any) -> bool;
@@ -37,10 +33,12 @@ pub struct Scope<P: Properties> {
     // our own index
     pub myidx: ScopeIdx,
 
-    pub caller: FC<P>,
-
+    // the props
     pub props: P,
 
+    // and the actual render function
+    pub caller: FC<P>,
+
     // ==========================
     // slightly unsafe stuff
     // ==========================
@@ -87,10 +85,10 @@ pub fn create_scoped<P: Properties + 'static>(
     let frames = ActiveFrame::from_frames(old_frame, new_frame);
 
     Box::new(Scope {
+        caller,
         myidx,
         hook_arena,
         hooks,
-        caller,
         frames,
         listeners,
         parent,
@@ -125,6 +123,7 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
 
         // Note that the actual modification of the vnode head element occurs during this call
         // let _: DomTree = caller(ctx, props);
+        // let _: DomTree = P::render (ctx, &self.props);
         let _: DomTree = (self.caller)(ctx, &self.props);
 
         /*
@@ -259,95 +258,95 @@ impl ActiveFrame {
     }
 }
 
+#[cfg(old)]
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::prelude::*;
 
-    static ListenerTest: FC<()> = |ctx, props| {
-        ctx.render(html! {
-            <div onclick={|_| println!("Hell owlrld")}>
-                "hello"
-            </div>
-        })
-    };
+    // static ListenerTest: FC<()> = |ctx, props| {
+    //     ctx.render(html! {
+    //         <div onclick={|_| println!("Hell owlrld")}>
+    //             "hello"
+    //         </div>
+    //     })
+    // };
 
     #[test]
     fn test_scope() {
-        let example: FC<()> = |ctx, props| {
-            use crate::builder::*;
-            ctx.render(|ctx| {
-                builder::ElementBuilder::new(ctx, "div")
-                    .child(text("a"))
-                    .finish()
-            })
-        };
+        #[derive(PartialEq)]
+        struct Example {}
+        impl FC for Example {
+            fn render(ctx: Context<'_>, _: &Self) -> DomTree {
+                use crate::builder::*;
+                ctx.render(|ctx| {
+                    builder::ElementBuilder::new(ctx, "div")
+                        .child(text("a"))
+                        .finish()
+                })
+            }
+        }
 
-        let props = ();
-        let parent = None;
         let mut nodes = generational_arena::Arena::new();
         nodes.insert_with(|myidx| {
-            let scope = create_scoped(example, props, myidx, parent);
+            let scope = create_scoped(Example {}, myidx, None);
         });
     }
 
-    #[derive(Debug)]
-    struct ExampleProps<'src> {
-        name: &'src String,
-    }
+    use crate::{builder::*, hooks::use_ref};
 
-    #[derive(Debug)]
+    #[derive(Debug, PartialEq)]
     struct EmptyProps<'src> {
         name: &'src String,
     }
 
-    use crate::{builder::*, hooks::use_ref};
-
-    fn example_fc<'a>(ctx: Context<'a>, props: &'a EmptyProps) -> DomTree {
-        let (content, _): (&'a String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
-
-        let childprops: ExampleProps<'a> = ExampleProps { name: content };
-        ctx.render(move |c| {
-            builder::ElementBuilder::new(c, "div")
-                .child(text(props.name))
-                .child(virtual_child::<ExampleProps>(
-                    c.bump,
-                    childprops,
-                    child_example,
-                ))
-                .finish()
-        })
+    impl FC for EmptyProps<'_> {
+        fn render(ctx: Context, props: &Self) -> DomTree {
+            let (content, _): (&String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
+
+            let childprops: ExampleProps<'_> = ExampleProps { name: content };
+            todo!()
+            // ctx.render(move |c| {
+            //     builder::ElementBuilder::new(c, "div")
+            //         .child(text(props.name))
+            //         .child(virtual_child(c, childprops))
+            //         .finish()
+            // })
+        }
     }
 
-    fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree {
-        ctx.render(move |ctx| {
-            builder::ElementBuilder::new(ctx, "div")
-                .child(text(props.name))
-                .finish()
-        })
+    #[derive(Debug, PartialEq)]
+    struct ExampleProps<'src> {
+        name: &'src String,
     }
 
-    static CHILD: FC<ExampleProps> = |ctx, props: &'_ ExampleProps| {
-        ctx.render(move |ctx| {
-            builder::ElementBuilder::new(ctx, "div")
-                .child(text(props.name))
-                .finish()
-        })
-    };
+    impl FC for ExampleProps<'_> {
+        fn render(ctx: Context, props: &Self) -> DomTree {
+            ctx.render(move |ctx| {
+                builder::ElementBuilder::new(ctx, "div")
+                    .child(text(props.name))
+                    .finish()
+            })
+        }
+    }
 
     #[test]
     fn test_borrowed_scope() {
-        let example: FC<EmptyProps> = |ctx, props| {
-            ctx.render(move |b| {
-                builder::ElementBuilder::new(b, "div")
-                    .child(virtual_child(
-                        b.bump,
-                        ExampleProps { name: props.name },
-                        CHILD,
-                    ))
-                    .finish()
-            })
-        };
+        #[derive(Debug, PartialEq)]
+        struct Example {
+            name: String,
+        }
+
+        impl FC for Example {
+            fn render(ctx: Context, props: &Self) -> DomTree {
+                todo!()
+                // ctx.render(move |c| {
+                //     builder::ElementBuilder::new(c, "div")
+                //         .child(virtual_child(c, ExampleProps { name: &props.name }))
+                //         .finish()
+                // })
+            }
+        }
 
         let source_text = "abcd123".to_string();
         let props = ExampleProps { name: &source_text };

+ 16 - 16
packages/core/src/virtual_dom.rs

@@ -1,6 +1,6 @@
 // use crate::{changelist::EditList, nodes::VNode};
 
-use crate::{innerlude::*, scope::Properties};
+use crate::innerlude::*;
 use crate::{
     patch::Edit,
     scope::{create_scoped, Scoped},
@@ -63,19 +63,19 @@ impl VirtualDom {
     pub fn new(root: FC<()>) -> Self {
         Self::new_with_props(root, ())
     }
-
     /// Start a new VirtualDom instance with a dependent props.
     /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
     ///
     /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
     /// to toss out the entire tree.
     pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
-        let mut components = Arena::new();
+        // let mut components = Arena::new();
+        // let mut components = Arena::new();
 
         // Create a reference to the component in the arena
         // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
         // This puts the dom in a usable state on creation, rather than being potentially invalid
-        let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
+        // let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
 
         todo!()
         // Self {
@@ -106,19 +106,16 @@ impl VirtualDom {
 
         let b = Bump::new();
 
-        let mut diff_machine = DiffMachine::new(self, &b, self.base_scope);
+        let mut diff_machine = DiffMachine::new(&self.diff_bump);
         // let mut diff_machine = DiffMachine::new(self, &self.diff_bump, self.base_scope);
 
-        todo!()
-        // let component = self.components.get(self.base_scope).unwrap();
-
-        // diff_machine.diff_node(component.old_frame(), component.new_frame());
-
-        // let edits = diff_machine.consume();
+        // todo!()
 
+        let component = self.components.get(self.base_scope).unwrap();
+        diff_machine.diff_node(component.old_frame(), component.new_frame());
+        let edits = diff_machine.consume();
         // self.diff_bump = b;
-
-        // Ok(edits)
+        Ok(edits)
     }
 
     /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
@@ -187,6 +184,9 @@ impl VirtualDom {
     }
 }
 
-// struct LockedEdits<'src> {
-//     edits:
-// }
+enum LifeCycleEvent {
+    // Mount {
+//     props: &dyn Properties,
+// // f: FC<dyn Properties>,
+// },
+}

+ 1 - 1
packages/dioxus/src/lib.rs

@@ -3,7 +3,7 @@ pub mod prelude {
     pub use dioxus_core_macro::fc;
 }
 
-use dioxus_core::prelude::FC;
+// use dioxus_core::prelude::FC;
 
 // Re-export core completely
 pub use dioxus_core as core;

+ 1 - 0
packages/web/Cargo.toml

@@ -22,6 +22,7 @@ pretty_env_logger = "0.4.0"
 console_error_panic_hook = "0.1.6"
 generational-arena = "0.2.8"
 wasm-bindgen-test = "0.3.21"
+once_cell = "1.7.2"
 # html-validation = { path = "../html-validation", version = "0.1.1" }
 
 [dependencies.web-sys]

+ 16 - 2
packages/web/examples/infer.rs

@@ -9,6 +9,7 @@ fn main() {
     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
 }
 
+// this is a component
 static Example: FC<()> = |ctx, _props| {
     let (event, set_event) = use_state(&ctx, || None);
     let event = format!("{:#?}", event);
@@ -21,14 +22,12 @@ static Example: FC<()> = |ctx, _props| {
         div {  
             
             class: "py-12 px-4 w-full max-w-2xl mx-auto bg-red-100"
-            // class: "py-12 px-4 text-center w-full max-w-2xl mx-auto bg-red-100"
             span { 
                 class: "text-sm font-semibold"
                 "Dioxus Example: Synthetic Events"
             }            
             button {
                 class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
-                
                 "press me"
             }
             pre {
@@ -36,6 +35,21 @@ static Example: FC<()> = |ctx, _props| {
                 id: "json"
                 "{event}"
             }
+            Example2 { name: "{event}" }
+        }
+    })
+};
+
+
+#[derive(Debug, PartialEq)]
+struct Props {
+    name: String
+}
+
+static Example2: FC<Props> = |ctx, props| {
+    ctx.render(rsx!{
+        div {
+            h1 {"hello {props.name}"}
         }
     })
 };