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
56 changes: 54 additions & 2 deletions packages/yew-macro/src/derive_props/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::convert::TryFrom;
use syn::parse::Result;
use syn::spanned::Spanned;
use syn::{Error, Expr, Field, Type, TypePath, Visibility};
use syn::{Error, Expr, Field, Path, Type, TypePath, Visibility};

#[allow(clippy::large_enum_variant)]
#[derive(PartialEq, Eq)]
Expand Down Expand Up @@ -167,7 +167,7 @@ impl PropField {
} else if matches!(
&named_field.ty,
Type::Path(TypePath { path, .. })
if path.segments.len() == 1 && path.segments[0].ident == "Option"
if is_path_an_option(path)
) {
Ok(PropAttr::Option)
} else {
Expand All @@ -178,6 +178,36 @@ impl PropField {
}
}

fn is_path_segments_an_option(path_segments: impl Iterator<Item = String>) -> bool {
fn is_option_path_seg(seg_index: usize, path: &str) -> u8 {
match (seg_index, path) {
(0, "core") => 0b001,
(0, "std") => 0b001,
(0, "Option") => 0b111,
(1, "option") => 0b010,
(2, "Option") => 0b100,
_ => 0,
}
}

path_segments
.enumerate()
.fold(0, |flags, (i, ps)| flags | is_option_path_seg(i, &ps))
== 0b111
}

/// Returns true when the [`Path`] seems like an [`Option`] type.
///
/// This function considers the following paths as Options:
/// - core::option::Option
/// - std::option::Option
/// - Option::*
///
/// Users can define their own [`Option`] type and this will return true - this is unavoidable.
fn is_path_an_option(path: &Path) -> bool {
is_path_segments_an_option(path.segments.iter().take(3).map(|ps| ps.ident.to_string()))
}

impl TryFrom<Field> for PropField {
type Error = Error;

Expand Down Expand Up @@ -223,3 +253,25 @@ impl PartialEq for PropField {
self.name == other.name
}
}

#[cfg(test)]
mod tests {
use crate::derive_props::field::is_path_segments_an_option;

#[test]
fn all_std_and_core_option_path_seg_return_true() {
assert!(is_path_segments_an_option(
vec!["core".to_owned(), "option".to_owned(), "Option".to_owned()].into_iter()
));
assert!(is_path_segments_an_option(
vec!["std".to_owned(), "option".to_owned(), "Option".to_owned()].into_iter()
));
assert!(is_path_segments_an_option(
vec!["Option".to_owned()].into_iter()
));
// why OR instead of XOR
assert!(is_path_segments_an_option(
vec!["Option".to_owned(), "Vec".to_owned(), "Option".to_owned()].into_iter()
));
}
}
4 changes: 2 additions & 2 deletions packages/yew-macro/src/html_tree/html_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl ToTokens for HtmlComponent {
let value = &node_ref.value;
quote_spanned! {value.span()=> #value }
} else {
quote! { ::yew::html::NodeRef::default() }
quote! { <::yew::html::NodeRef as ::std::default::Default>::default() }
};

let key = if let Some(key) = &special_props.key {
Expand All @@ -118,7 +118,7 @@ impl ToTokens for HtmlComponent {
Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value))
}
} else {
quote! { None }
quote! { ::std::option::Option::None }
};

tokens.extend(quote_spanned! {ty.span()=>
Expand Down
2 changes: 1 addition & 1 deletion packages/yew-macro/src/html_tree/html_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ impl ToTokens for HtmlElement {
::std::borrow::Cow::<'static, str>::Borrowed(#key)
)
} else {
None
::std::option::Option::None
}
}),
},
Expand Down
120 changes: 61 additions & 59 deletions packages/yew-macro/tests/derive_props/pass.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,85 @@
#![no_implicit_prelude]
#![recursion_limit = "128"]

use yew::prelude::*;

mod t1 {
use super::*;

#[derive(Clone, Properties, PartialEq)]
pub struct Props<T: Clone + Default + PartialEq> {
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props<T: ::std::clone::Clone + ::std::default::Default + ::std::cmp::PartialEq> {
#[prop_or_default]
value: T,
}

fn optional_prop_generics_should_work() {
use ::yew::Properties;

Props::<bool>::builder().build();
Props::<bool>::builder().value(true).build();
}
}

mod t2 {
use super::*;

#[derive(Clone, PartialEq)]
#[derive(::std::clone::Clone, ::std::cmp::PartialEq)]
struct Value;
#[derive(Clone, Properties, PartialEq)]
pub struct Props<T: Clone + PartialEq> {
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props<T: ::std::clone::Clone + ::std::cmp::PartialEq> {
value: T,
}

fn required_prop_generics_should_work() {
use ::yew::Properties;

Props::<Value>::builder().value(Value).build();
}
}

mod t3 {
use super::*;

#[derive(Clone, Properties, PartialEq)]
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props {
b: i32,
#[prop_or_default]
a: i32,
}

fn order_is_alphabetized() {
use ::yew::Properties;

Props::builder().b(1).build();
Props::builder().a(1).b(2).build();
}
}

mod t4 {
use super::*;

#[derive(Clone, Properties, PartialEq)]
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props<T>
where
T: Clone + Default + PartialEq,
T: ::std::clone::Clone + ::std::default::Default + ::std::cmp::PartialEq,
{
#[prop_or_default]
value: T,
}

fn optional_prop_generics_should_work() {
use ::yew::Properties;

Props::<bool>::builder().build();
Props::<bool>::builder().value(true).build();
}
}

mod t5 {
use super::*;

#[derive(Clone, Properties, PartialEq)]
pub struct Props<'a, T: Clone + Default + PartialEq + 'a> {
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props<
'a,
T: ::std::clone::Clone + ::std::default::Default + ::std::cmp::PartialEq + 'a,
> {
#[prop_or_default]
static_value: &'static str,
value: &'a T,
}

fn optional_prop_generics_with_lifetime_should_work() {
use ::std::{convert::From, string::String};
use ::yew::Properties;

Props::<String>::builder().value(&String::from("")).build();
Props::<String>::builder()
.static_value("")
Expand All @@ -86,128 +89,127 @@ mod t5 {
}

mod t6 {
use super::*;
use std::str::FromStr;

#[derive(Properties, Clone, PartialEq)]
pub struct Props<T: FromStr + Clone + PartialEq>
#[derive(::yew::Properties, ::std::clone::Clone, ::std::cmp::PartialEq)]
pub struct Props<T: ::std::str::FromStr + ::std::clone::Clone + ::std::cmp::PartialEq>
where
<T as FromStr>::Err: Clone + PartialEq,
<T as ::std::str::FromStr>::Err: ::std::clone::Clone + ::std::cmp::PartialEq,
{
value: Result<T, <T as FromStr>::Err>,
value: ::std::result::Result<T, <T as ::std::str::FromStr>::Err>,
}

fn required_prop_generics_with_where_clause_should_work() {
use ::std::{convert::From, result::Result::Ok, string::String};
use ::yew::Properties;

Props::<String>::builder()
.value(Ok(String::from("")))
.build();
}
}

mod t7 {
use super::*;

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(::std::clone::Clone, Debug, Eq, ::std::cmp::PartialEq)]
pub enum Foo {
One,
Two,
}

#[derive(Clone, Properties, PartialEq)]
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props {
#[prop_or(Foo::One)]
value: Foo,
}

fn prop_or_value_should_work() {
use ::std::assert_eq;
use ::yew::Properties;

let props = Props::builder().build();
assert_eq!(props.value, Foo::One);
Props::builder().value(Foo::Two).build();
}
}

mod t8 {
use super::*;

#[derive(Clone, Properties, PartialEq)]
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props {
#[prop_or_else(|| 123)]
value: i32,
}

fn prop_or_else_closure_should_work() {
use ::std::assert_eq;
use ::yew::Properties;

let props = Props::builder().build();
assert_eq!(props.value, 123);
Props::builder().value(123).build();
}
}

mod t9 {
use super::*;
use std::str::FromStr;

#[derive(Clone, Properties, PartialEq)]
pub struct Props<T: FromStr + Clone + PartialEq>
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props<T: ::std::str::FromStr + ::std::clone::Clone + ::std::cmp::PartialEq>
where
<T as FromStr>::Err: Clone + PartialEq,
<T as ::std::str::FromStr>::Err: ::std::clone::Clone + ::std::cmp::PartialEq,
{
#[prop_or_else(default_value)]
value: Result<T, <T as FromStr>::Err>,
value: ::std::result::Result<T, <T as ::std::str::FromStr>::Err>,
}

fn default_value<T: FromStr + Clone>() -> Result<T, <T as FromStr>::Err>
fn default_value<T: ::std::str::FromStr + ::std::clone::Clone>(
) -> ::std::result::Result<T, <T as ::std::str::FromStr>::Err>
where
<T as FromStr>::Err: Clone,
<T as ::std::str::FromStr>::Err: ::std::clone::Clone,
{
"123".parse()
}

fn prop_or_else_function_with_generics_should_work() {
use ::std::{assert_eq, result::Result::Ok};
use ::yew::Properties;

let props = Props::<i32>::builder().build();
assert_eq!(props.value, Ok(123));
Props::<i32>::builder().value(Ok(456)).build();
}
}

mod t10 {
use super::*;

// this test makes sure that Yew handles generic params with default values properly.

#[derive(Clone, Properties, PartialEq)]
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Foo<S, M = S>
where
S: Clone + PartialEq,
M: Clone + PartialEq,
S: ::std::clone::Clone + ::std::cmp::PartialEq,
M: ::std::clone::Clone + ::std::cmp::PartialEq,
{
bar: S,
baz: M,
}
}

mod t11 {
use super::*;

// this test makes sure that Yew handles generic params with const generics properly.

#[derive(Clone, Properties, PartialEq)]
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Foo<T, const N: usize>
where
T: Clone + PartialEq,
T: ::std::clone::Clone + ::std::cmp::PartialEq,
{
bar: [T; N],
}
}

mod t12 {
use super::*;

#[derive(Clone, Properties, PartialEq)]
pub struct Props<T: Clone + PartialEq> {
value: Option<T>,
#[derive(::std::clone::Clone, ::yew::Properties, ::std::cmp::PartialEq)]
pub struct Props<T: ::std::clone::Clone + ::std::cmp::PartialEq> {
value: ::std::option::Option<T>,
}

fn optional_prop_generics_should_work() {
use ::yew::Properties;

Props::<bool>::builder().build();
Props::<bool>::builder().value(true).build();
}
Expand Down
4 changes: 2 additions & 2 deletions packages/yew-macro/tests/function_attr_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
#[rustversion::attr(stable(1.51), test)]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/function_attr/*-pass.rs");
t.compile_fail("tests/function_attr/*-fail.rs");
t.pass("tests/function_component_attr/*-pass.rs");
t.compile_fail("tests/function_component_attr/*-fail.rs");
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use yew::prelude::*;
use yew_functional::function_component;

#[derive(Clone, Properties, PartialEq)]
struct Props {
Expand Down
Loading