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::ops::{Deref, DerefMut};
@ -9,7 +8,6 @@ use syn::spanned::Spanned;
use syn::token::Brace;
use syn::{braced, Block, Expr, ExprBlock, ExprPath, ExprRange, Stmt, Token};
use super::CHILDREN_LABEL;
use crate::html_tree::HtmlDashedName;
use crate::stringify::Stringify;
@ -24,6 +22,7 @@ pub struct Prop {
/// Punctuation between `label` and `value`.
pub value: Expr,
}
impl Parse for Prop {
fn parse(input: ParseStream) -> syn::Result<Self> {
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.
/// Use `check_no_duplicates` to ensure that there are no duplicates.
pub struct SortedPropList(Vec<Prop>);
impl SortedPropList {
pub struct PropList(Vec<Prop>);
impl PropList {
/// Create a new `SortedPropList` from a vector of props.
/// The given `props` doesn't need to be sorted.
pub fn new(mut props: Vec<Prop>) -> Self {
props.sort_by(|a, b| Self::cmp_label(&a.label.to_string(), &b.label.to_string()));
pub fn new(props: Vec<Prop>) -> Self {
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> {
self.0
.binary_search_by(|prop| Self::cmp_label(prop.label.to_string().as_str(), key))
.ok()
self.0.iter().position(|it| it.label.to_string() == key)
}
/// Get the first prop with the given key.
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.
@ -312,7 +296,7 @@ impl SortedPropList {
}))
}
}
impl Parse for SortedPropList {
impl Parse for PropList {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut props: Vec<Prop> = Vec::new();
// Stop parsing props if a base expression preceded by `..` is reached
@ -323,7 +307,7 @@ impl Parse for SortedPropList {
Ok(Self::new(props))
}
}
impl Deref for SortedPropList {
impl Deref for PropList {
type Target = [Prop];
fn deref(&self) -> &Self::Target {
@ -340,7 +324,7 @@ impl SpecialProps {
const KEY_LABEL: &'static str = "key";
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 key = props.pop_unique(Self::KEY_LABEL)?;
Ok(Self { node_ref, key })
@ -386,15 +370,15 @@ impl SpecialProps {
pub struct Props {
pub special: SpecialProps,
pub prop_list: SortedPropList,
pub prop_list: PropList,
}
impl Parse for Props {
fn parse(input: ParseStream) -> syn::Result<Self> {
Self::try_from(input.parse::<SortedPropList>()?)
Self::try_from(input.parse::<PropList>()?)
}
}
impl Deref for Props {
type Target = SortedPropList;
type Target = PropList;
fn deref(&self) -> &Self::Target {
&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;
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)?;
Ok(Self { special, prop_list })
}

View File

@ -8,7 +8,7 @@ use syn::spanned::Spanned;
use syn::token::Brace;
use syn::{Expr, Token, TypePath};
use super::{ComponentProps, Prop, Props, SortedPropList};
use super::{ComponentProps, Prop, PropList, Props};
use crate::html_tree::HtmlDashedName;
/// 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 {
fn parse(input: ParseStream) -> syn::Result<Self> {
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()?;
props.special.check_all(|prop| {
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
--> tests/html_macro/component-fail.rs:67:20
--> tests/html_macro/component-fail.rs:67:29
|
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)
--> 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
--> tests/html_macro/element-fail.rs:33:20
--> tests/html_macro/element-fail.rs:33:29
|
33 | html! { <input ref={()} ref={()} /> };
| ^^^
| ^^^
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={()} /> };
| ^^^
| ^^^
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

View File

@ -5,3 +5,24 @@ fn props_macro() {
t.pass("tests/props_macro/*-pass.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
While almost any Rust type can be passed as properties, there are some anti-patterns that should be avoided.