Quellcode durchsuchen

Feat: revert FC changes (like the old style).

This commit reverts to the old style of props + FC. The old style is desirable
because people comfortable with react can automatically be comfortable with
dioxus. It's also nice in that the same props can be used to drive two different
components - something the trait version couldn't do. Now, our trait bound forces
implementations to have the #[derive(Props)] flag. This will need to implement the
Properties trait as well as PartialEq (using ptr::equal for closure fields).
Jonathan Kelley vor 4 Jahren
Ursprung
Commit
7158bc3

+ 10 - 0
packages/core-macro/src/lib.rs

@@ -5,6 +5,7 @@ use syn::parse_macro_input;
 mod fc;
 mod htm;
 mod ifmt;
+mod props;
 mod rsxt;
 mod util;
 
@@ -59,3 +60,12 @@ pub fn format_args_f(input: TokenStream) -> TokenStream {
         .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 (<...>=<...>)")),
+            }
+        }
+    }
+}

+ 12 - 7
packages/core-macro/src/rsxt.rs

@@ -198,14 +198,19 @@ impl Parse for Component {
         // parse the guts
         let content: ParseBuffer;
         syn::braced!(content in s);
-        let mut fields: Vec<Field> = Vec::new();
 
-        'parsing: loop {
-            // [1] Break if empty
-            if content.is_empty() {
-                break 'parsing;
-            }
-        }
+        let mut body: Vec<Field> = Vec::new();
+        let mut attrs = 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;
+        //     }
+        // }
 
         // let body = content.parse()?;
 

+ 30 - 5
packages/core/examples/borrowed.rs

@@ -7,12 +7,15 @@
 
 fn main() {}
 
-use dioxus_core::prelude::*;
+use std::fmt::Debug;
+
+use dioxus_core::{component::Properties, prelude::*};
 
 struct Props {
     items: Vec<ListItem>,
 }
 
+#[derive(PartialEq)]
 struct ListItem {
     name: String,
     age: u32,
@@ -21,18 +24,18 @@ 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,
+                c,
                 ChildProps {
                     item: child,
                     item_handler: setter,
                 },
-                // <ChildItem item=child hanldler=setter />
                 child_item,
             ));
         }
@@ -41,7 +44,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,6 +62,19 @@ struct ChildProps<'a> {
     item_handler: &'a StateSetter<i32>,
 }
 
+impl PartialEq for ChildProps<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        todo!()
+    }
+}
+
+impl<'a> Properties for ChildProps<'a> {
+    type Builder = ChildPropsBuilder<'a, ((), ())>;
+    fn builder() -> <Self as Properties>::Builder {
+        ChildProps::builder()
+    }
+}
+
 fn child_item(ctx: Context, props: &ChildProps) -> DomTree {
     todo!()
     //     ctx.render(rsx! {

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

@@ -1,5 +1,5 @@
 use dioxus_core::prelude::*;
-use dioxus_core::scope::FC;
+
 use dioxus_core_macro::fc;
 
 use std::marker::PhantomData;
@@ -22,7 +22,7 @@ use std::marker::PhantomData;
 
 // always try to fill in with Default
 
-#[fc]
+// #[fc]
 fn Example(ctx: Context, a: &str, b: &str, c: &str) -> DomTree {
     ctx.render(rsx! {
         div {
@@ -33,7 +33,7 @@ fn Example(ctx: Context, a: &str, b: &str, c: &str) -> DomTree {
     })
 }
 
-#[fc]
+// #[fc]
 fn SomeComponent(ctx: Context, a: &str, b: &str) -> DomTree {
     todo!()
 }

+ 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() {}

+ 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()
+    }
+}

+ 23 - 59
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::*;
-
-    // fn test_static_fn<'a, P>(b: &'a Bump, r: FC<P>) -> VNode<'a> {
-    //     todo!()
-    // }
-
-    // static TestComponent: FC<()> = |ctx, props| {
-    //     //
-
-    //     ctx.render(html! {
-    //         <div>
+pub trait Properties: PartialEq {
+    type Builder;
+    fn builder() -> Self::Builder;
+}
 
-    //         </div>
-    //     })
-    // };
+pub struct EmptyBuilder;
+impl EmptyBuilder {
+    pub fn build() -> () {
+        ()
+    }
+}
 
-    // static TestComponent2: FC<()> = |ctx, props| {
-    //     //
-    //     ctx.render(|ctx| VNode::text("blah"))
-    // };
+impl Properties for () {
+    type Builder = EmptyBuilder;
 
-    // #[test]
-    // fn ensure_types_work() {
-    //     let bump = Bump::new();
+    fn builder() -> Self::Builder {
+        EmptyBuilder {}
+    }
+}
 
-    //     // Happiness! The VNodes are now allocated onto the bump vdom
-    //     let _ = test_static_fn(&bump, TestComponent);
-    //     let _ = test_static_fn(&bump, TestComponent2);
-    // }
+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,

+ 2 - 1
packages/core/src/debug_renderer.rs

@@ -21,11 +21,12 @@ impl DebugRenderer {
     pub fn log_dom(&self) {}
 }
 
+#[cfg(old)]
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::prelude::*;
-    use crate::scope::FC;
+    use crate::scope::Properties;
 
     #[test]
     fn ensure_creation() -> Result<(), ()> {

+ 5 - 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;
@@ -106,7 +107,7 @@ pub(crate) mod innerlude {
     // pub use nodes::iterables::IterableNodes;
     /// This type alias is an internal way of abstracting over the static functions that represent components.
 
-    // pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
+    pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
 
     mod fc2 {}
     // pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
@@ -137,7 +138,7 @@ pub mod prelude {
 
     // pub use nodes::iterables::IterableNodes;
     /// This type alias is an internal way of abstracting over the static functions that represent components.
-    // pub use crate::innerlude::FC;
+    pub use crate::innerlude::FC;
 
     // TODO @Jon, fix this
     // hack the VNode type until VirtualNode is fixed in the macro crate
@@ -151,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::{html, rsx};
+
+    pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
 
     pub use crate::component::ScopeIdx;
     pub use crate::diff::DiffMachine;

+ 6 - 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,
 };
@@ -526,7 +526,11 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
 
 // _f: crate::innerlude::FC<T>,
 // _props: T
-pub fn virtual_child<'a, T: crate::scope::FC>(ctx: &'a NodeCtx<'a>, p: T) -> VNode<'a> {
+pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>(
+    ctx: &'b NodeCtx<'a>,
+    p: T,
+    f: FC<T>,
+) -> VNode<'a> {
     todo!()
     // VNode::Component()
 }

+ 2 - 5
packages/core/src/nodes.rs

@@ -296,10 +296,7 @@ mod vtext {
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 mod vcomponent {
-    use crate::{
-        innerlude::{Context, ScopeIdx},
-        scope::FC,
-    };
+    use crate::innerlude::{Context, Properties, ScopeIdx, FC};
     use std::{
         any::{Any, TypeId},
         cell::RefCell,
@@ -345,7 +342,7 @@ mod vcomponent {
         // this lets us keep scope generic *and* downcast its props when we need to:
         // - perform comparisons when diffing (memoization)
         // -
-        pub fn new<P: FC + 'static>(comp: P) -> Self {
+        pub fn new<P: Properties + 'static>(caller: FC<P>, comp: P) -> Self {
             todo!()
             // let p = Rc::new(props);
 

+ 12 - 9
packages/core/src/scope.rs

@@ -11,10 +11,6 @@ use std::{
     ops::Deref,
 };
 
-pub trait FC: PartialEq {
-    fn render(ctx: Context, props: &Self) -> DomTree;
-}
-
 pub trait Scoped {
     fn run(&mut self);
     fn compare_props(&self, new: &dyn std::any::Any) -> bool;
@@ -30,15 +26,19 @@ pub trait Scoped {
 ///
 /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
 /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
-pub struct Scope<P: FC> {
+pub struct Scope<P: Properties> {
     // Map to the parent
     pub parent: Option<ScopeIdx>,
 
     // our own index
     pub myidx: ScopeIdx,
 
+    // the props
     pub props: P,
 
+    // and the actual render function
+    pub caller: FC<P>,
+
     // ==========================
     // slightly unsafe stuff
     // ==========================
@@ -61,7 +61,8 @@ pub struct Scope<P: FC> {
 
 // instead of having it as a trait method, we use a single function
 // todo: do the unsafety magic stuff to erase the type of p
-pub fn create_scoped<P: FC + 'static>(
+pub fn create_scoped<P: Properties + 'static>(
+    caller: FC<P>,
     props: P,
     myidx: ScopeIdx,
     parent: Option<ScopeIdx>,
@@ -84,6 +85,7 @@ pub fn create_scoped<P: FC + 'static>(
     let frames = ActiveFrame::from_frames(old_frame, new_frame);
 
     Box::new(Scope {
+        caller,
         myidx,
         hook_arena,
         hooks,
@@ -94,7 +96,7 @@ pub fn create_scoped<P: FC + 'static>(
     })
 }
 
-impl<P: FC + 'static> Scoped for Scope<P> {
+impl<P: Properties + 'static> Scoped for Scope<P> {
     /// Create a new context and run the component with references from the Virtual Dom
     /// This function downcasts the function pointer based on the stored props_type
     ///
@@ -121,8 +123,8 @@ impl<P: FC + '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);
+        // let _: DomTree = P::render (ctx, &self.props);
+        let _: DomTree = (self.caller)(ctx, &self.props);
 
         /*
         SAFETY ALERT
@@ -256,6 +258,7 @@ impl ActiveFrame {
     }
 }
 
+#[cfg(old)]
 #[cfg(test)]
 mod tests {
     use super::*;

+ 5 - 6
packages/core/src/virtual_dom.rs

@@ -1,6 +1,6 @@
 // use crate::{changelist::EditList, nodes::VNode};
 
-use crate::{innerlude::*, scope::FC};
+use crate::innerlude::*;
 use crate::{
     patch::Edit,
     scope::{create_scoped, Scoped},
@@ -60,17 +60,16 @@ impl VirtualDom {
     ///
     /// This means that the root component must either consumes its own context, or statics are used to generate the page.
     /// The root component can access things like routing in its context.
-    pub fn new(root: impl Fn(Context) -> DomTree) -> Self {
-        todo!()
-        // Self::new_with_props(root)
+    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: FC + 'static>(root: P) -> Self {
+    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();
 
         // Create a reference to the component in the arena