Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion packages/yew-macro/src/derive_props/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,28 @@ use proc_macro2::{Ident, Span};
use quote::{format_ident, quote, quote_spanned};
use syn::parse::Result;
use syn::spanned::Spanned;
use syn::{parse_quote, Attribute, Error, Expr, Field, GenericParam, Generics, Type, Visibility};
use syn::{
parse_quote, Attribute, Error, Expr, Field, GenericArgument, GenericParam, Generics,
PathArguments, Type, Visibility,
};

use super::should_preserve_attr;
use crate::derive_props::generics::push_type_param;

fn is_option_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
if segment.ident == "Option" {
if let PathArguments::AngleBracketed(args) = &segment.arguments {
return args.args.len() == 1
&& matches!(args.args.first(), Some(GenericArgument::Type(_)));
}
}
}
}
false
}

#[allow(clippy::large_enum_variant)]
#[derive(PartialEq, Eq)]
pub enum PropAttr {
Expand Down Expand Up @@ -130,9 +147,24 @@ impl PropField {
) -> proc_macro2::TokenStream {
let Self { name, ty, attr, .. } = self;
let token_ty = Ident::new("__YewTokenTy", Span::mixed_site());
let none_fn_name = format_ident!("{}_none", name, span = Span::mixed_site());
let build_fn = match attr {
PropAttr::Required { wrapped_name } => {
let check_struct = self.to_check_name(props_name);
let none_setter = if is_option_type(ty) {
quote! {
#[doc(hidden)]
#vis fn #none_fn_name<#token_ty>(
&mut self,
token: #token_ty,
) -> #check_struct< #token_ty > {
self.wrapped.#wrapped_name = ::std::option::Option::Some(::std::option::Option::None);
#check_struct ( ::std::marker::PhantomData )
}
}
} else {
quote! {}
};
quote! {
#[doc(hidden)]
#vis fn #name<#token_ty>(
Expand All @@ -143,9 +175,25 @@ impl PropField {
self.wrapped.#wrapped_name = ::std::option::Option::Some(value.into_prop_value());
#check_struct ( ::std::marker::PhantomData )
}

#none_setter
}
}
_ => {
let none_setter = if is_option_type(ty) {
quote! {
#[doc(hidden)]
#vis fn #none_fn_name<#token_ty>(
&mut self,
token: #token_ty,
) -> #token_ty {
self.wrapped.#name = ::std::option::Option::Some(::std::option::Option::None);
token
}
}
} else {
quote! {}
};
quote! {
#[doc(hidden)]
#vis fn #name<#token_ty>(
Expand All @@ -156,6 +204,8 @@ impl PropField {
self.wrapped.#name = ::std::option::Option::Some(value.into_prop_value());
token
}

#none_setter
}
}
};
Expand Down
35 changes: 31 additions & 4 deletions packages/yew-macro/src/props/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ use syn::Expr;

use super::{Prop, Props, SpecialProps, CHILDREN_LABEL};

fn is_none_expr(expr: &Expr) -> bool {
matches!(
expr,
Expr::Path(syn::ExprPath {
attrs,
qself: None,
path,
}) if attrs.is_empty() && path.is_ident("None")
)
}

struct BaseExpr {
pub dot_dot: DotDot,
pub expr: Expr,
Expand Down Expand Up @@ -100,8 +111,18 @@ impl ComponentProps {
let #token_ident = ::yew::html::AssertAllProps;
};
let set_props = self.props.iter().map(|Prop { label, value, .. }| {
quote_spanned! {value.span()=>
let #token_ident = #builder_ident.#label(#token_ident, #value);
if is_none_expr(value) {
let none_setter = Ident::new(
&format!("{}_none", label),
label.span().resolved_at(Span::mixed_site()),
);
quote_spanned! {value.span()=>
let #token_ident = #builder_ident.#none_setter(#token_ident);
}
} else {
quote_spanned! {value.span()=>
let #token_ident = #builder_ident.#label(#token_ident, #value);
}
}
});
let set_children = children_renderer.map(|children| {
Expand All @@ -125,8 +146,14 @@ impl ComponentProps {
Some(expr) => {
let ident = Ident::new("__yew_props", props_ty.span());
let set_props = self.props.iter().map(|Prop { label, value, .. }| {
quote_spanned! {value.span().resolved_at(Span::call_site())=>
#ident.#label = ::yew::html::IntoPropValue::into_prop_value(#value);
if is_none_expr(value) {
quote_spanned! {value.span().resolved_at(Span::call_site())=>
#ident.#label = ::std::option::Option::None;
}
} else {
quote_spanned! {value.span().resolved_at(Span::call_site())=>
#ident.#label = ::yew::html::IntoPropValue::into_prop_value(#value);
}
}
});
let set_children = children_renderer.map(|children| {
Expand Down
76 changes: 76 additions & 0 deletions packages/yew/src/html/conversion/into_prop_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,4 +570,80 @@ mod test {
let _ = html! { <Child>{&attr_value}</Child> };
}
}

#[test]
fn test_bare_none_option_string_prop() {
use crate::prelude::*;

#[derive(PartialEq, Properties)]
pub struct Props {
pub foo: Option<String>,
}

#[component]
fn Comp(_props: &Props) -> Html {
html! {}
}

let _ = html! { <Comp foo={None} /> };
let _ = html! { <Comp foo="hello" /> };
let _ = html! { <Comp foo={Some("hello")} /> };
}

#[test]
fn test_bare_none_option_attr_value_prop() {
use crate::prelude::*;

#[derive(PartialEq, Properties)]
pub struct Props {
pub foo: Option<AttrValue>,
}

#[component]
fn Comp(_props: &Props) -> Html {
html! {}
}

let _ = html! { <Comp foo={None} /> };
let _ = html! { <Comp foo="hello" /> };
let _ = html! { <Comp foo={AttrValue::from("hello")} /> };
}

#[test]
fn test_bare_none_option_html_prop() {
use crate::prelude::*;

#[derive(PartialEq, Properties)]
pub struct Props {
pub title: Option<Html>,
}

#[component]
fn Comp(_props: &Props) -> Html {
html! {}
}

let _ = html! { <Comp title={None} /> };
let _ = html! { <Comp title={Option::<Html>::None} /> };
}

#[test]
fn test_bare_none_optional_prop_with_default() {
use crate::prelude::*;

#[derive(PartialEq, Properties)]
pub struct Props {
#[prop_or_default]
pub foo: Option<String>,
}

#[component]
fn Comp(_props: &Props) -> Html {
html! {}
}

let _ = html! { <Comp foo={None} /> };
let _ = html! { <Comp foo="hello" /> };
let _ = html! { <Comp /> };
}
}
Loading