mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
feat: flatten props attrs
This commit is contained in:
parent
f4132d1874
commit
d2372717bd
4 changed files with 79 additions and 118 deletions
37
examples/optional_props.rs
Normal file
37
examples/optional_props.rs
Normal 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!()
|
||||||
|
}
|
|
@ -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 don’t yet know if it’ll be an error)
|
// Stash its span for later (we don’t yet know if it’ll 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);)
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue