Merge pull request #111 from DioxusLabs/jk/props-attrs

Flatten optional prop attributes
This commit is contained in:
Jonathan Kelley 2022-01-10 17:11:08 -05:00 committed by GitHub
commit 0369fe72fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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, strip_option)]
d: Option<String>,
}
fn Button(cx: Scope<ButtonProps>) -> Element {
todo!()
}

View file

@ -267,11 +267,6 @@ mod field_info {
#[derive(Debug, Default, Clone)]
pub struct FieldBuilderAttr {
pub default: Option<syn::Expr>,
pub setter: SetterSettings,
}
#[derive(Debug, Clone, Default)]
pub struct SetterSettings {
pub doc: Option<syn::Expr>,
pub skip: 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)
if self.setter.skip && skip_tokens.is_none() {
if self.skip && skip_tokens.is_none() {
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(
skip_tokens.unwrap(),
"#[props(skip)] must be accompanied by default or default_code",
@ -331,6 +326,10 @@ mod field_info {
self.default = Some(*assign.right);
Ok(())
}
"doc" => {
self.doc = Some(*assign.right);
Ok(())
}
"default_code" => {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(code),
@ -355,7 +354,7 @@ mod field_info {
}
}
// uh not sure
// #[props(default)]
syn::Expr::Path(path) => {
let name = path_to_single_string(&path.path)
.ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
@ -365,39 +364,33 @@ mod field_info {
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)?;
_ => {
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),
))
}
}
}
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;
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(())
@ -540,7 +464,7 @@ mod struct_info {
impl<'a> StructInfo<'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(
@ -826,14 +750,14 @@ Finally, call `.build()` to create the instance of `{name}`.
syn::GenericArgument::Type(ty_generics_tuple.into()),
);
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]),
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 arg_type = if field.builder_attr.strip_option {
let internal_type = field.type_from_inside_option().ok_or_else(|| {
Error::new_spanned(
&field_type,
@ -844,7 +768,7 @@ Finally, call `.build()` to create the instance of `{name}`.
} else {
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!(#field_name.into()),
@ -852,7 +776,7 @@ Finally, call `.build()` to create the instance of `{name}`.
} else {
(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))
} else {
arg_expr
@ -1076,7 +1000,7 @@ Finally, call `.build()` to create the instance of `{name}`.
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 {
if field.builder_attr.skip {
quote!(let #name = #default;)
} else {
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 }
///
/// ```
#[props(default, setter(strip_option))]
#[props(default, strip_option)]
href: Option<&'a str>,
#[props(default, setter(strip_option))]
#[props(default, strip_option)]
class: Option<&'a str>,
#[props(default, setter(strip_option))]
#[props(default, strip_option)]
id: Option<&'a str>,
children: Element<'a>,

View file

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