feat: flatten props attrs

This commit is contained in:
Jonathan Kelley 2022-01-10 02:57:03 -05:00
parent f4132d1874
commit d2372717bd
4 changed files with 79 additions and 118 deletions

View file

@ -0,0 +1,37 @@
//! Example: README.md showcase
//!
//! The example from the README.md.
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Button {
a: "asd".to_string(),
c: Some("asd".to_string()),
d: "asd".to_string(),
}
})
}
#[derive(Props, PartialEq)]
struct ButtonProps {
a: String,
#[props(default)]
b: Option<String>,
#[props(default)]
c: Option<String>,
#[props(default)]
d: Option<String>,
}
fn Button(cx: Scope<ButtonProps>) -> Element {
todo!()
}

View file

@ -267,11 +267,6 @@ mod field_info {
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct FieldBuilderAttr { pub struct FieldBuilderAttr {
pub default: Option<syn::Expr>, pub default: Option<syn::Expr>,
pub setter: SetterSettings,
}
#[derive(Debug, Clone, Default)]
pub struct SetterSettings {
pub doc: Option<syn::Expr>, pub doc: Option<syn::Expr>,
pub skip: bool, pub skip: bool,
pub auto_into: bool, pub auto_into: bool,
@ -305,12 +300,12 @@ mod field_info {
} }
} }
// Stash its span for later (we dont yet know if itll be an error) // Stash its span for later (we dont yet know if itll be an error)
if self.setter.skip && skip_tokens.is_none() { if self.skip && skip_tokens.is_none() {
skip_tokens = Some(attr.tokens.clone()); skip_tokens = Some(attr.tokens.clone());
} }
} }
if self.setter.skip && self.default.is_none() { if self.skip && self.default.is_none() {
return Err(Error::new_spanned( return Err(Error::new_spanned(
skip_tokens.unwrap(), skip_tokens.unwrap(),
"#[props(skip)] must be accompanied by default or default_code", "#[props(skip)] must be accompanied by default or default_code",
@ -331,6 +326,10 @@ mod field_info {
self.default = Some(*assign.right); self.default = Some(*assign.right);
Ok(()) Ok(())
} }
"doc" => {
self.doc = Some(*assign.right);
Ok(())
}
"default_code" => { "default_code" => {
if let syn::Expr::Lit(syn::ExprLit { if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(code), lit: syn::Lit::Str(code),
@ -355,7 +354,7 @@ mod field_info {
} }
} }
// uh not sure // #[props(default)]
syn::Expr::Path(path) => { syn::Expr::Path(path) => {
let name = path_to_single_string(&path.path) let name = path_to_single_string(&path.path)
.ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
@ -365,39 +364,33 @@ mod field_info {
Some(syn::parse(quote!(Default::default()).into()).unwrap()); Some(syn::parse(quote!(Default::default()).into()).unwrap());
Ok(()) Ok(())
} }
_ => Err(Error::new_spanned( _ => {
&path, macro_rules! handle_fields {
format!("Unknown parameter {:?}", name), ( $( $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)))
syn::Expr::Call(call) => { } else {
let subsetting_name = if let syn::Expr::Path(path) = &*call.func { self.$field = true;
path_to_single_string(&path.path) Ok(())
} else { }
None }
} )*
.ok_or_else(|| { _ => Err(Error::new_spanned(
let call_func = &call.func; &path,
let call_func = quote!(#call_func); format!("Unknown setter parameter {:?}", name),
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(()) handle_fields!(
"skip", skip, "skipped";
"into", auto_into, "calling into() on the argument";
"strip_option", strip_option, "putting the argument in Some(...)";
)
} }
_ => Err(Error::new_spanned(
&call.func,
format!("Illegal builder setting group name {}", subsetting_name),
)),
} }
} }
@ -414,75 +407,6 @@ mod field_info {
self.default = None; self.default = None;
Ok(()) 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" => { "doc" => {
self.doc = None; self.doc = None;
Ok(()) Ok(())
@ -540,7 +464,7 @@ mod struct_info {
impl<'a> StructInfo<'a> { impl<'a> StructInfo<'a> {
pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> { pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
self.fields.iter().filter(|f| !f.builder_attr.setter.skip) self.fields.iter().filter(|f| !f.builder_attr.skip)
} }
pub fn new( pub fn new(
@ -826,14 +750,14 @@ Finally, call `.build()` to create the instance of `{name}`.
syn::GenericArgument::Type(ty_generics_tuple.into()), syn::GenericArgument::Type(ty_generics_tuple.into()),
); );
let (impl_generics, _, where_clause) = generics.split_for_impl(); let (impl_generics, _, where_clause) = generics.split_for_impl();
let doc = match field.builder_attr.setter.doc { let doc = match field.builder_attr.doc {
Some(ref doc) => quote!(#[doc = #doc]), Some(ref doc) => quote!(#[doc = #doc]),
None => quote!(), None => quote!(),
}; };
// NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of // 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. // nesting is different so we have to do this little dance.
let arg_type = if field.builder_attr.setter.strip_option { let arg_type = if field.builder_attr.strip_option {
let internal_type = field.type_from_inside_option().ok_or_else(|| { let internal_type = field.type_from_inside_option().ok_or_else(|| {
Error::new_spanned( Error::new_spanned(
&field_type, &field_type,
@ -844,7 +768,7 @@ Finally, call `.build()` to create the instance of `{name}`.
} else { } else {
field_type field_type
}; };
let (arg_type, arg_expr) = if field.builder_attr.setter.auto_into { let (arg_type, arg_expr) = if field.builder_attr.auto_into {
( (
quote!(impl core::convert::Into<#arg_type>), quote!(impl core::convert::Into<#arg_type>),
quote!(#field_name.into()), quote!(#field_name.into()),
@ -852,7 +776,7 @@ Finally, call `.build()` to create the instance of `{name}`.
} else { } else {
(quote!(#arg_type), quote!(#field_name)) (quote!(#arg_type), quote!(#field_name))
}; };
let arg_expr = if field.builder_attr.setter.strip_option { let arg_expr = if field.builder_attr.strip_option {
quote!(Some(#arg_expr)) quote!(Some(#arg_expr))
} else { } else {
arg_expr arg_expr
@ -1076,7 +1000,7 @@ Finally, call `.build()` to create the instance of `{name}`.
let assignments = self.fields.iter().map(|field| { let assignments = self.fields.iter().map(|field| {
let name = &field.name; let name = &field.name;
if let Some(ref default) = field.builder_attr.default { if let Some(ref default) = field.builder_attr.default {
if field.builder_attr.setter.skip { if field.builder_attr.skip {
quote!(let #name = #default;) quote!(let #name = #default;)
} else { } else {
quote!(let #name = #helper_trait_name::into_value(#name, || #default);) quote!(let #name = #helper_trait_name::into_value(#name, || #default);)

View file

@ -22,13 +22,13 @@ pub struct LinkProps<'a> {
/// Link { to: Route::Home, href: Route::as_url } /// Link { to: Route::Home, href: Route::as_url }
/// ///
/// ``` /// ```
#[props(default, setter(strip_option))] #[props(default, strip_option)]
href: Option<&'a str>, href: Option<&'a str>,
#[props(default, setter(strip_option))] #[props(default, strip_option)]
class: Option<&'a str>, class: Option<&'a str>,
#[props(default, setter(strip_option))] #[props(default, strip_option)]
id: Option<&'a str>, id: Option<&'a str>,
children: Element<'a>, children: Element<'a>,

View file

@ -11,7 +11,7 @@ use crate::RouterService;
pub struct RouterProps<'a> { pub struct RouterProps<'a> {
children: Element<'a>, children: Element<'a>,
#[props(default, setter(strip_option))] #[props(default, strip_option)]
onchange: Option<&'a Fn(&'a str)>, onchange: Option<&'a Fn(&'a str)>,
} }