diff --git a/Cargo.toml b/Cargo.toml index 44d161884..0841d0b62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ "packages/dioxus", "packages/core-macro", "packages/core", - "packages/web", + # "packages/web", # "packages/router", # "packages/ssr", # "packages/webview", diff --git a/README.md b/README.md index 60b7d80d2..83bb39720 100644 --- a/README.md +++ b/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, || "...?"); diff --git a/notes/SOLVEDPROBLEMS.md b/notes/SOLVEDPROBLEMS.md index 5329ffb74..8c471eb81 100644 --- a/notes/SOLVEDPROBLEMS.md +++ b/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 should default to none. + +```rust + +rsx! { + Example { /* args go here */ a: 10, b: 20 } +} + + +``` + +```rust +#[derive(Properties)] +struct Props { + +} + +static Component: FC = |ctx, props| { + +} +``` + +or + +```rust +#[fc] +static Component: FC = |ctx, name: &str| { + +} +``` diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index fe1de60f3..7a2b6b574 100644 --- a/packages/core-macro/Cargo.toml +++ b/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" diff --git a/packages/core-macro/examples/fc.rs b/packages/core-macro/examples/fc.rs new file mode 100644 index 000000000..033a454fb --- /dev/null +++ b/packages/core-macro/examples/fc.rs @@ -0,0 +1,3 @@ +use dioxus_core_macro::fc; + +fn main() {} diff --git a/packages/core-macro/src/fc.rs b/packages/core-macro/src/fc.rs index c7b87ce62..055efd38f 100644 --- a/packages/core-macro/src/fc.rs +++ b/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 { - // 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, - // The user's props type - props_type: Box, - + // // The user's props type + // props_type: Box, arg: FnArg, vis: Visibility, attrs: Vec, @@ -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 { @@ -228,7 +244,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result> { 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 { Ok(sig) } -pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result> { - 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", - )); - } +// pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result> { +// 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", - )); - } +// // 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", - )); - } -} +// // 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() {} diff --git a/packages/core-macro/src/ifmt.rs b/packages/core-macro/src/ifmt.rs index d0e341eb3..816c1f400 100644 --- a/packages/core-macro/src/ifmt.rs +++ b/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 +// } +// } +// }; // } -pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { +// #[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) -> Result { 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 = segments + let segments: Punctuated = 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... diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index e9b24c0be..48fb5c12d 100644 --- a/packages/core-macro/src/lib.rs +++ b/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::(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); - - // #[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() + 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(), + } } diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs new file mode 100644 index 000000000..af7497394 --- /dev/null +++ b/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