Evaluate props in the order they're defined (#2887)

* don't change order of props

* rename `SortedPropList` to `PropList`

* docs + test

* remove dead code

* fmt
This commit is contained in:
Muhammad Hamza 2022-10-08 13:40:55 +05:00 committed by GitHub
parent 32b3150cb3
commit 426a1fd81d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 38 deletions

View File

@ -1,4 +1,3 @@
use std::cmp::Ordering;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -9,7 +8,6 @@ use syn::spanned::Spanned;
use syn::token::Brace; use syn::token::Brace;
use syn::{braced, Block, Expr, ExprBlock, ExprPath, ExprRange, Stmt, Token}; use syn::{braced, Block, Expr, ExprBlock, ExprPath, ExprRange, Stmt, Token};
use super::CHILDREN_LABEL;
use crate::html_tree::HtmlDashedName; use crate::html_tree::HtmlDashedName;
use crate::stringify::Stringify; use crate::stringify::Stringify;
@ -24,6 +22,7 @@ pub struct Prop {
/// Punctuation between `label` and `value`. /// Punctuation between `label` and `value`.
pub value: Expr, pub value: Expr,
} }
impl Parse for Prop { impl Parse for Prop {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let directive = input let directive = input
@ -216,36 +215,21 @@ fn advance_until_next_dot2(input: &ParseBuffer) -> syn::Result<()> {
/// ///
/// The list may contain multiple props with the same label. /// The list may contain multiple props with the same label.
/// Use `check_no_duplicates` to ensure that there are no duplicates. /// Use `check_no_duplicates` to ensure that there are no duplicates.
pub struct SortedPropList(Vec<Prop>); pub struct PropList(Vec<Prop>);
impl SortedPropList { impl PropList {
/// Create a new `SortedPropList` from a vector of props. /// Create a new `SortedPropList` from a vector of props.
/// The given `props` doesn't need to be sorted. /// The given `props` doesn't need to be sorted.
pub fn new(mut props: Vec<Prop>) -> Self { pub fn new(props: Vec<Prop>) -> Self {
props.sort_by(|a, b| Self::cmp_label(&a.label.to_string(), &b.label.to_string()));
Self(props) Self(props)
} }
fn cmp_label(a: &str, b: &str) -> Ordering {
if a == b {
Ordering::Equal
} else if a == CHILDREN_LABEL {
Ordering::Greater
} else if b == CHILDREN_LABEL {
Ordering::Less
} else {
a.cmp(b)
}
}
fn position(&self, key: &str) -> Option<usize> { fn position(&self, key: &str) -> Option<usize> {
self.0 self.0.iter().position(|it| it.label.to_string() == key)
.binary_search_by(|prop| Self::cmp_label(prop.label.to_string().as_str(), key))
.ok()
} }
/// Get the first prop with the given key. /// Get the first prop with the given key.
pub fn get_by_label(&self, key: &str) -> Option<&Prop> { pub fn get_by_label(&self, key: &str) -> Option<&Prop> {
self.position(key).and_then(|i| self.0.get(i)) self.0.iter().find(|it| it.label.to_string() == key)
} }
/// Pop the first prop with the given key. /// Pop the first prop with the given key.
@ -312,7 +296,7 @@ impl SortedPropList {
})) }))
} }
} }
impl Parse for SortedPropList { impl Parse for PropList {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let mut props: Vec<Prop> = Vec::new(); let mut props: Vec<Prop> = Vec::new();
// Stop parsing props if a base expression preceded by `..` is reached // Stop parsing props if a base expression preceded by `..` is reached
@ -323,7 +307,7 @@ impl Parse for SortedPropList {
Ok(Self::new(props)) Ok(Self::new(props))
} }
} }
impl Deref for SortedPropList { impl Deref for PropList {
type Target = [Prop]; type Target = [Prop];
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -340,7 +324,7 @@ impl SpecialProps {
const KEY_LABEL: &'static str = "key"; const KEY_LABEL: &'static str = "key";
const REF_LABEL: &'static str = "ref"; const REF_LABEL: &'static str = "ref";
fn pop_from(props: &mut SortedPropList) -> syn::Result<Self> { fn pop_from(props: &mut PropList) -> syn::Result<Self> {
let node_ref = props.pop_unique(Self::REF_LABEL)?; let node_ref = props.pop_unique(Self::REF_LABEL)?;
let key = props.pop_unique(Self::KEY_LABEL)?; let key = props.pop_unique(Self::KEY_LABEL)?;
Ok(Self { node_ref, key }) Ok(Self { node_ref, key })
@ -386,15 +370,15 @@ impl SpecialProps {
pub struct Props { pub struct Props {
pub special: SpecialProps, pub special: SpecialProps,
pub prop_list: SortedPropList, pub prop_list: PropList,
} }
impl Parse for Props { impl Parse for Props {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
Self::try_from(input.parse::<SortedPropList>()?) Self::try_from(input.parse::<PropList>()?)
} }
} }
impl Deref for Props { impl Deref for Props {
type Target = SortedPropList; type Target = PropList;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.prop_list &self.prop_list
@ -406,10 +390,10 @@ impl DerefMut for Props {
} }
} }
impl TryFrom<SortedPropList> for Props { impl TryFrom<PropList> for Props {
type Error = syn::Error; type Error = syn::Error;
fn try_from(mut prop_list: SortedPropList) -> Result<Self, Self::Error> { fn try_from(mut prop_list: PropList) -> Result<Self, Self::Error> {
let special = SpecialProps::pop_from(&mut prop_list)?; let special = SpecialProps::pop_from(&mut prop_list)?;
Ok(Self { special, prop_list }) Ok(Self { special, prop_list })
} }

View File

@ -8,7 +8,7 @@ use syn::spanned::Spanned;
use syn::token::Brace; use syn::token::Brace;
use syn::{Expr, Token, TypePath}; use syn::{Expr, Token, TypePath};
use super::{ComponentProps, Prop, Props, SortedPropList}; use super::{ComponentProps, Prop, PropList, Props};
use crate::html_tree::HtmlDashedName; use crate::html_tree::HtmlDashedName;
/// Pop from `Punctuated` without leaving it in a state where it has trailing punctuation. /// Pop from `Punctuated` without leaving it in a state where it has trailing punctuation.
@ -106,7 +106,7 @@ pub struct PropsMacroInput {
impl Parse for PropsMacroInput { impl Parse for PropsMacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let PropsExpr { ty, fields, .. } = input.parse()?; let PropsExpr { ty, fields, .. } = input.parse()?;
let prop_list = SortedPropList::new(fields.into_iter().map(Into::into).collect()); let prop_list = PropList::new(fields.into_iter().map(Into::into).collect());
let props: Props = prop_list.try_into()?; let props: Props = prop_list.try_into()?;
props.special.check_all(|prop| { props.special.check_all(|prop| {
let label = &prop.label; let label = &prop.label;

View File

@ -143,10 +143,10 @@ error: `with` doesn't have a value. (hint: set the value to `true` or `false` fo
| ^^^^ | ^^^^
error: `ref` can only be specified once error: `ref` can only be specified once
--> tests/html_macro/component-fail.rs:67:20 --> tests/html_macro/component-fail.rs:67:29
| |
67 | html! { <Child ref={()} ref={()} value=1 ..props /> }; 67 | html! { <Child ref={()} ref={()} value=1 ..props /> };
| ^^^ | ^^^
error: `with` doesn't have a value. (hint: set the value to `true` or `false` for boolean attributes) error: `with` doesn't have a value. (hint: set the value to `true` or `false` for boolean attributes)
--> tests/html_macro/component-fail.rs:68:20 --> tests/html_macro/component-fail.rs:68:20

View File

@ -101,16 +101,16 @@ error: `class` can only be specified once but is given here again
| ^^^^^ | ^^^^^
error: `ref` can only be specified once error: `ref` can only be specified once
--> tests/html_macro/element-fail.rs:33:20 --> tests/html_macro/element-fail.rs:33:29
| |
33 | html! { <input ref={()} ref={()} /> }; 33 | html! { <input ref={()} ref={()} /> };
| ^^^ | ^^^
error: `ref` can only be specified once error: `ref` can only be specified once
--> tests/html_macro/element-fail.rs:63:20 --> tests/html_macro/element-fail.rs:63:29
| |
63 | html! { <input ref={()} ref={()} /> }; 63 | html! { <input ref={()} ref={()} /> };
| ^^^ | ^^^
error: the tag `<input>` is a void element and cannot have children (hint: rewrite this as `<input/>`) error: the tag `<input>` is a void element and cannot have children (hint: rewrite this as `<input/>`)
--> tests/html_macro/element-fail.rs:66:13 --> tests/html_macro/element-fail.rs:66:13

View File

@ -5,3 +5,24 @@ fn props_macro() {
t.pass("tests/props_macro/*-pass.rs"); t.pass("tests/props_macro/*-pass.rs");
t.compile_fail("tests/props_macro/*-fail.rs"); t.compile_fail("tests/props_macro/*-fail.rs");
} }
#[test]
fn props_order() {
#[derive(yew::Properties, PartialEq)]
struct Props {
first: usize,
second: usize,
last: usize,
}
let mut g = 1..=3;
let props = yew::props!(Props {
first: g.next().unwrap(),
second: g.next().unwrap(),
last: g.next().unwrap()
});
assert_eq!(props.first, 1);
assert_eq!(props.second, 2);
assert_eq!(props.last, 3);
}

View File

@ -263,6 +263,24 @@ fn App() -> Html {
} }
``` ```
## Evaluation Order
Props are evaluated in the order they're specified, as shown by the following example:
```rust
#[derive(yew::Properties, PartialEq)]
struct Props { first: usize, second: usize, last: usize }
fn main() {
let mut g = 1..=3;
let props = yew::props!(Props { first: g.next().unwrap(), second: g.next().unwrap(), last: g.next().unwrap() });
assert_eq!(props.first, 1);
assert_eq!(props.second, 2);
assert_eq!(props.last, 3);
}
```
## Anti Patterns ## Anti Patterns
While almost any Rust type can be passed as properties, there are some anti-patterns that should be avoided. While almost any Rust type can be passed as properties, there are some anti-patterns that should be avoided.