Skip to content

Commit

Permalink
Properties 2.0 (#975)
Browse files Browse the repository at this point in the history
* Properties 2.0

* Fix problem when passing a closure to prop_or_else

* Update test names

* Add support for building with web-sys (#961)

* Enable travis

* `web-sys` general conversion (#826)

* Moved patches from different PRs.

* Add bits & pieces and some services.

* Rename `stdweb` feature to `std_web`.

* Move tests and examples to different PR.

* Revert some `cargo_web` handling removal.

* Missed something.

* Implement `console_error_panic_hook`.

* Update Cargo.toml

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>

* Move document creation to util convenience method (#855)

* `web-sys` listener conversion (#813)

* `web-sys` listener initial try.

* Improve macros?

* Remove generic from `EventListenerHandle`.

* Fix build.

* A cleaner solution?

* Even cleaner.

* Fix `build.rs`.

* Minor improvements.

* Following the yew toml style.

* Fixing visibility.

* Fix `rustfmt`.

* Add `web-sys` re-exports.

* Move general changes to different PR.

* Remove compat.

* Actually remove `compat.rs`.

* Rename `stdweb` feature to `std_web`.

* Move to gloo's `EventListener` and some polish.

* Remove outdated comment.

* Change `EventHandler` to be cancelled on drop.

* `web-sys` html conversion (#817)

* Converting all `html` parts.

* Format.

* Move general changes to different PR.

* Removed compat.

* Rename `stdweb` feature to `std_web`.

* Remove redudant function copy.

* Some polish.

* Move to gloo's `EventListener`.

* Replace `unwrap`s with `expect`s.

* `web-sys` agent conversion (#818)

* Converting `agent`.

* Remove wrong `cfg` in imports.

* Move general changes to different PR.

* Some optimisations.

* Rename `stdweb` feature to `std_web`.

* Fix `ArrayBuffer` to `Uint8Array` conversions.

* Add js module worker.

* Use `cfg-if`` and `cfg-match` to make things clearer.

* Fix `std_web` build.

* Add some polish.

* Add build guards for invalid build configs (#866)

* `web_sys` cfg conversion (#862)

* Use `cfg-if` and `cfg-match` and some polish.

* Mistakes were made.

* Missed line during rebasing.

* Mistakes were undone.

* Remove global.

* Remove part of `global!`.

* `web-sys` services conversion (#827)

* Convert `console`.

* Finish services.

* Some polish.

* Fix `ArrayBuffer` to `Uint8Array` conversions.

* Fix aborting fetch leading to error and some polish.

* Replaced some `unwrap`s with `expect`s.

* Use `cfg_if` and `cfg_match` and do some polish.

* Proper scoping.

* Some fixes.

* Move fetch and reader services to different PR.

* Revert split.

* Fix CI builds (#877)

* Fix derive_props_test

* Move tests (#897) (#898)

* `web-sys` fetch service conversion (#867)

* Split implementation.

* Import global.

* Import global.

* Revert split.

* Make fetch available again.

* Revert "Revert split."

This reverts commit 6e3f101.

* Re-revert split.

* Some polish.

* Move to `wasm_bindgen_futures`.

* Switch to `thiserror`.

* wip

* Update src/services/fetch/web_sys.rs

Co-Authored-By: daxpedda <daxpedda@gmail.com>

* Some more polish.

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>

* `web-sys` reader service conversion (#868)

* Split reader implementation.

* Revert split.

* Remove leftover files.

* Make reader available again.

* Revert "Revert split."

This reverts commit 8abdc9c.

* Revert "Remove leftover files."

This reverts commit 188c6eb.

* Re-revert split.

* Polish.

* Forgot some part.

* Some polish.

* Some polish.

* `web-sys` examples/tests conversion (#841)

* Fix examples/tests to work with `web_sys`.

* Update `StorageService` usage.

* Split `stdweb` and `web-sys` examples.

* Fixing the shell script.

* Trying to reset file permissions.

* Update to new reader API.

* Update to new fetch API.

* Update to new fetch API.

* Re-enable examples CI.

* Deleted duplicate example.

* Some fixes.

* Fix rand build.

* Fix spawning workers in combination with `wasm-bindgen`. (#901)

* Fix component rendering process (#913)

* wip

* Fix component rendering process

* Simplify yew-macro a bit (#902)

* yew-macro: Simplify Properties validation

* Fix most clippy warnings

* Fix clippy warnings (#912)

* Import Task trait in dashboard example

* Remove duplicate vtag tests

* Fix prevent_default() by non-passive (#958)

* Fix prevent_default() by non-passive

* Fix cargo fmt

* Remove `Option` from most services.

* Remove `Option` from resize service.

* Apply fetch changes.

* Apply reader service changes.

* Fix `node_refs` example.

* Remove web-sys travis branch

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
Co-authored-by: Jet Li <jing.i.qin@icloud.com>

* Fix vtag test warning (#978)

* Clean up exported apis and doc visibility (#977)

* Clean up exported apis and doc visibility

* Remove unused ScopeHolder

* Change ComponentLink to alias of Scope

* cargo fmt

* update new examples to Properties 2.0

* Fix a mistake when resolving conflicts

* Remove an outdated hack.

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
Co-authored-by: Jet Li <jing.i.qin@icloud.com>
  • Loading branch information
5 people committed Mar 1, 2020
1 parent f659052 commit afb5e9d
Show file tree
Hide file tree
Showing 21 changed files with 237 additions and 257 deletions.
115 changes: 36 additions & 79 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,21 @@ 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()
}
},
#name: #value,
}
}
PropAttr::PropOrElse(func) => {
let name = &self.name;
let span = func.span();
quote_spanned! {span=>
#name: (#func)(),
}
}
PropAttr::None => {
PropAttr::PropOrDefault => {
let name = &self.name;
quote! {
#name: ::std::default::Default::default(),
Expand Down Expand Up @@ -143,67 +139,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!()
}
}
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)
}
// 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!()
}
_ => 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
83 changes: 42 additions & 41 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
|
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
error[E0425]: cannot find value `foo` in this scope
--> $DIR/fail.rs:87:24
|
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,43 @@ 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]: mismatched types
--> $DIR/fail.rs:67:19
|
67 | #[prop_or(123)]
| ^^^
| |
| expected struct `std::string::String`, found integer
| help: try using a conversion method: `123.to_string()`

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
error[E0308]: mismatched types
--> $DIR/fail.rs:111:24
|
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()`
111 | #[prop_or_else(foo)]
| ^^^
| |
| expected struct `std::string::String`, found `i32`
| help: try using a conversion method: `foo.to_string()`
Loading

0 comments on commit afb5e9d

Please sign in to comment.