Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properties 2.0 #975

Merged
merged 10 commits into from
Mar 1, 2020
117 changes: 42 additions & 75 deletions crates/macro/src/derive_props/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::convert::TryFrom;
use syn::parse::Result;
use syn::spanned::Spanned;
use syn::{
Error, ExprPath, Field, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Type, Visibility,
};
use syn::{Error, Expr, Field, Type, Visibility};

#[allow(clippy::large_enum_variant)]
#[derive(PartialEq, Eq)]
enum PropAttr {
Required { wrapped_name: Ident },
Default { default: ExprPath },
None,
PropOr(Expr),
PropOrElse(Expr),
PropOrDefault,
}

#[derive(Eq)]
Expand Down Expand Up @@ -83,25 +83,31 @@ impl PropField {
#wrapped_name: ::std::option::Option::None,
}
}
PropAttr::Default { default } => {
PropAttr::PropOr(value) => {
let name = &self.name;
let ty = &self.ty;
let span = default.span();
// Hacks to avoid misleading error message.
let span = value.span();
quote_spanned! {span=>
#name: {
match true {
#[allow(unreachable_code)]
false => {
let __unreachable: #ty = ::std::unreachable!();
__unreachable
},
true => #default()
false => ::std::unreachable!(),
true => #value,
jstarry marked this conversation as resolved.
Show resolved Hide resolved
}
},
}
}
PropAttr::None => {
PropAttr::PropOrElse(func) => {
let name = &self.name;
let span = func.span();
quote_spanned! {span=>
#name: {
match true {
false => ::std::unreachable!(),
true => (#func)(),
jstarry marked this conversation as resolved.
Show resolved Hide resolved
}
},
}
}
PropAttr::PropOrDefault => {
let name = &self.name;
quote! {
#name: ::std::default::Default::default(),
Expand Down Expand Up @@ -143,67 +149,28 @@ impl PropField {
}
}

// Detect `#[props(required)]` or `#[props(default="...")]` attribute
fn attribute(named_field: &syn::Field) -> Result<PropAttr> {
let meta_list = if let Some(meta_list) = Self::find_props_meta_list(named_field) {
meta_list
} else {
return Ok(PropAttr::None);
};

let expected_attr = syn::Error::new(
meta_list.span(),
"expected `props(required)` or `#[props(default=\"...\")]`",
);
let first_nested = if let Some(first_nested) = meta_list.nested.first() {
first_nested
} else {
return Err(expected_attr);
};
match first_nested {
NestedMeta::Meta(Meta::Path(word_path)) => {
if !word_path.is_ident("required") {
return Err(expected_attr);
}

if let Some(ident) = &named_field.ident {
let wrapped_name = Ident::new(&format!("{}_wrapper", ident), Span::call_site());
Ok(PropAttr::Required { wrapped_name })
} else {
unreachable!()
}
// Detect Properties 2.0 attributes
fn attribute(named_field: &Field) -> Result<PropAttr> {
let attr = named_field.attrs.iter().find(|attr| {
attr.path.is_ident("prop_or")
|| attr.path.is_ident("prop_or_else")
|| attr.path.is_ident("prop_or_default")
});

if let Some(attr) = attr {
if attr.path.is_ident("prop_or") {
Ok(PropAttr::PropOr(attr.parse_args()?))
} else if attr.path.is_ident("prop_or_else") {
Ok(PropAttr::PropOrElse(attr.parse_args()?))
} else if attr.path.is_ident("prop_or_default") {
Ok(PropAttr::PropOrDefault)
} else {
unreachable!()
}
NestedMeta::Meta(Meta::NameValue(name_value)) => {
let MetaNameValue { path, lit, .. } = name_value;

if !path.is_ident("default") {
return Err(expected_attr);
}

if let Lit::Str(lit_str) = lit {
let default = lit_str.parse()?;
Ok(PropAttr::Default { default })
} else {
Err(expected_attr)
}
}
_ => Err(expected_attr),
}
}

fn find_props_meta_list(field: &syn::Field) -> Option<MetaList> {
let meta_list = field
.attrs
.iter()
.find_map(|attr| match attr.parse_meta().ok()? {
Meta::List(meta_list) => Some(meta_list),
_ => None,
})?;

if meta_list.path.is_ident("props") {
Some(meta_list)
} else {
None
let ident = named_field.ident.as_ref().unwrap();
let wrapped_name = Ident::new(&format!("{}_wrapper", ident), Span::call_site());
Ok(PropAttr::Required { wrapped_name })
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions crates/macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
//!
//! #[derive(Clone, Properties)]
//! struct Props {
//! #[props(required)]
//! prop: String,
//! prop: String,
//! };
//!
//! # enum Msg { Submit }
Expand Down Expand Up @@ -88,7 +87,7 @@ fn non_capitalized_ascii(string: &str) -> bool {
}
}

#[proc_macro_derive(Properties, attributes(props))]
#[proc_macro_derive(Properties, attributes(prop_or, prop_or_else, prop_or_default))]
pub fn derive_props(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DerivePropsInput);
TokenStream::from(input.into_token_stream())
Expand Down
29 changes: 14 additions & 15 deletions crates/macro/tests/derive_props/fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod t1 {
#[derive(Clone, Properties)]
pub struct Props {
// ERROR: optional params must implement default
#[prop_or_default]
value: Value,
}
}
Expand All @@ -17,8 +18,8 @@ mod t2 {
use super::*;
#[derive(Clone, Properties)]
pub struct Props {
// ERROR: optional is not a tag
#[props(optional)]
// ERROR: old syntax no longer supported
#[props(default)]
value: String,
}
}
Expand All @@ -27,7 +28,6 @@ mod t3 {
use super::*;
#[derive(Clone, Properties)]
pub struct Props {
#[props(required)]
value: String,
}

Expand All @@ -41,7 +41,6 @@ mod t4 {
#[derive(Clone, Properties)]
pub struct Props {
b: i32,
#[props(required)]
a: i32,
}

Expand All @@ -54,8 +53,8 @@ mod t5 {
use super::*;
#[derive(Clone, Properties)]
pub struct Props {
// ERROR: default must be given a value
#[props(default)]
// ERROR: prop_or must be given a value
#[prop_or()]
value: String,
}
}
Expand All @@ -64,19 +63,19 @@ mod t6 {
use super::*;
#[derive(Clone, Properties)]
pub struct Props {
// ERROR: 123 is not a path or an identifier
#[props(default = 123)]
value: i32,
// ERROR: 123 is not a String
#[prop_or(123)]
value: String,
}
}

mod t7 {
use super::*;
#[derive(Clone, Properties)]
pub struct Props {
// ERROR: the value must be parsed into a path to a function
#[props(default = "123")]
value: String,
// ERROR: 123 is not a function
#[prop_or_else(123)]
value: i32,
}
}

Expand All @@ -85,7 +84,7 @@ mod t8 {
#[derive(Clone, Properties)]
pub struct Props {
// ERROR: cannot find function foo in this scope
#[props(default = "foo")]
#[prop_or_else(foo)]
value: String,
}
}
Expand All @@ -95,7 +94,7 @@ mod t9 {
#[derive(Clone, Properties)]
pub struct Props {
// ERROR: the function must take no arguments
#[props(default = "foo")]
#[prop_or_else(foo)]
value: String,
}

Expand All @@ -109,7 +108,7 @@ mod t10 {
#[derive(Clone, Properties)]
pub struct Props {
// ERROR: the function returns incompatible types
#[props(default = "foo")]
#[prop_or_else(foo)]
value: String,
}

Expand Down
89 changes: 49 additions & 40 deletions crates/macro/tests/derive_props/fail.stderr
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
error: expected `props(required)` or `#[props(default="...")]`
--> $DIR/fail.rs:21:11
error: unexpected end of input, expected expression
--> $DIR/fail.rs:57:18
|
21 | #[props(optional)]
| ^^^^^
57 | #[prop_or()]
| ^^

error: expected `props(required)` or `#[props(default="...")]`
--> $DIR/fail.rs:58:11
error: cannot find attribute `props` in this scope
--> $DIR/fail.rs:22:11
|
58 | #[props(default)]
22 | #[props(default)]
| ^^^^^

error: expected `props(required)` or `#[props(default="...")]`
--> $DIR/fail.rs:68:11
error[E0425]: cannot find value `foo` in this scope
--> $DIR/fail.rs:87:24
|
68 | #[props(default = 123)]
| ^^^^^

error: expected identifier
--> $DIR/fail.rs:78:27
|
78 | #[props(default = "123")]
| ^^^^^

error[E0425]: cannot find function `foo` in this scope
--> $DIR/fail.rs:88:27
|
88 | #[props(default = "foo")]
| ^^^^^ not found in this scope
87 | #[prop_or_else(foo)]
| ^^^ not found in this scope
|
help: possible candidates are found in other modules, you can import them into scope
|
84 | use crate::t10::foo;
83 | use crate::t10::foo;
|
84 | use crate::t9::foo;
83 | use crate::t9::foo;
|

error[E0277]: the trait bound `t1::Value: std::default::Default` is not satisfied
Expand All @@ -46,7 +34,7 @@ error[E0277]: the trait bound `t1::Value: std::default::Default` is not satisfie
error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>` in the current scope
--> $DIR/fail.rs:35:26
|
28 | #[derive(Clone, Properties)]
29 | #[derive(Clone, Properties)]
| ---------- method `build` not found for this
...
35 | Props::builder().build();
Expand All @@ -57,30 +45,51 @@ error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::Props
candidate #1: `proc_macro::bridge::server::TokenStreamBuilder`

error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::PropsBuilderStep_missing_required_prop_a>` in the current scope
--> $DIR/fail.rs:49:26
--> $DIR/fail.rs:48:26
|
41 | #[derive(Clone, Properties)]
| ---------- method `b` not found for this
...
49 | Props::builder().b(1).a(2).build();
48 | Props::builder().b(1).a(2).build();
| ^ help: there is a method with a similar name: `a`

error[E0308]: match arms have incompatible types
--> $DIR/fail.rs:67:19
|
67 | #[prop_or(123)]
| ^^^
| |
| expected struct `std::string::String`, found integer
| `match` arms have incompatible types
| this is found to be of type `std::string::String`
| help: try using a conversion method: `123.to_string()`
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error[E0618]: expected function, found `{integer}`
--> $DIR/fail.rs:77:24
|
77 | #[prop_or_else(123)]
| ^^^ call expression requires function

error[E0061]: this function takes 1 parameter but 0 parameters were supplied
--> $DIR/fail.rs:98:27
--> $DIR/fail.rs:97:24
|
98 | #[props(default = "foo")]
| ^^^^^ expected 1 parameter
97 | #[prop_or_else(foo)]
| ^^^ expected 1 parameter
...
102 | fn foo(bar: i32) -> String {
101 | fn foo(bar: i32) -> String {
| -------------------------- defined here

error[E0308]: match arms have incompatible types
--> $DIR/fail.rs:112:27
--> $DIR/fail.rs:111:24
|
111 | #[prop_or_else(foo)]
| ^^^
| |
| expected struct `std::string::String`, found `i32`
| `match` arms have incompatible types
| this is found to be of type `std::string::String`
| help: try using a conversion method: `foo.to_string()`
|
112 | #[props(default = "foo")]
| ^^^^^
| |
| expected struct `std::string::String`, found `i32`
| `match` arms have incompatible types
| this is found to be of type `std::string::String`
| help: try using a conversion method: `"foo".to_string()`
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
Loading