call_with_explicit_closure support closure in block (#4031)
* call_with_explicit_closure support closure in block * fix test
This commit is contained in:
1 parent
7392d7882b
commit
28a85ab614
2 files changed
+92
-17
No files matched your search
@@ -25,7 +25,8 @@ use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_quote,
|
||||
spanned::Spanned,
|
||||
Block, Expr, ExprClosure, ExprIf, Ident, Lit, LitBool, LitFloat, LitInt, LitStr, Token,
|
||||
Block, Expr, ExprBlock, ExprClosure, ExprIf, Ident, Lit, LitBool, LitFloat, LitInt, LitStr,
|
||||
Stmt, Token,
|
||||
};
|
||||
|
||||
/// A property value in the from of a `name: value` pair with an optional comma.
|
||||
@@ -233,25 +234,55 @@ impl Attribute {
|
||||
)
|
||||
}
|
||||
}
|
||||
AttributeValue::EventTokens(tokens) => match &self.name {
|
||||
AttributeName::BuiltIn(name) => {
|
||||
let event_tokens_is_closure =
|
||||
syn::parse2::<ExprClosure>(tokens.to_token_stream()).is_ok();
|
||||
let function_name =
|
||||
quote_spanned! { tokens.span() => dioxus_elements::events::#name };
|
||||
let function = if event_tokens_is_closure {
|
||||
// If we see an explicit closure, we can call the `call_with_explicit_closure` version of the event for better type inference
|
||||
quote_spanned! { tokens.span() => #function_name::call_with_explicit_closure }
|
||||
} else {
|
||||
function_name
|
||||
AttributeValue::EventTokens(_) | AttributeValue::AttrExpr(_) => {
|
||||
let (tokens, span) = match &self.value {
|
||||
AttributeValue::EventTokens(tokens) => {
|
||||
(tokens.to_token_stream(), tokens.span())
|
||||
}
|
||||
AttributeValue::AttrExpr(tokens) => {
|
||||
(tokens.to_token_stream(), tokens.span())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
fn check_tokens_is_closure(tokens: &TokenStream2) -> bool {
|
||||
if syn::parse2::<ExprClosure>(tokens.to_token_stream()).is_ok() {
|
||||
return true;
|
||||
}
|
||||
let Ok(block) = syn::parse2::<ExprBlock>(tokens.to_token_stream()) else {
|
||||
return false;
|
||||
};
|
||||
quote_spanned! { tokens.span() =>
|
||||
#function(#tokens)
|
||||
let mut block = █
|
||||
loop {
|
||||
match block.block.stmts.last() {
|
||||
Some(Stmt::Expr(Expr::Closure(_), _)) => return true,
|
||||
Some(Stmt::Expr(Expr::Block(b), _)) => {
|
||||
block = b;
|
||||
continue;
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
AttributeName::Custom(_) => unreachable!("Handled elsewhere in the macro"),
|
||||
AttributeName::Spread(_) => unreachable!("Handled elsewhere in the macro"),
|
||||
},
|
||||
match &self.name {
|
||||
AttributeName::BuiltIn(name) => {
|
||||
let event_tokens_is_closure = check_tokens_is_closure(&tokens);
|
||||
let function_name =
|
||||
quote_spanned! { span => dioxus_elements::events::#name };
|
||||
let function = if event_tokens_is_closure {
|
||||
// If we see an explicit closure, we can call the `call_with_explicit_closure` version of the event for better type inference
|
||||
quote_spanned! { span => #function_name::call_with_explicit_closure }
|
||||
} else {
|
||||
function_name
|
||||
};
|
||||
quote_spanned! { span =>
|
||||
#function(#tokens)
|
||||
}
|
||||
}
|
||||
AttributeName::Custom(_) => unreachable!("Handled elsewhere in the macro"),
|
||||
AttributeName::Spread(_) => unreachable!("Handled elsewhere in the macro"),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
quote_spanned! { value.span() => dioxus_elements::events::#name(#value) }
|
||||
}
|
||||
@@ -834,8 +865,41 @@ mod tests {
|
||||
let a: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
|
||||
let b: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
|
||||
let c: Attribute = parse2(quote! { onclick: move |e| {} }).unwrap();
|
||||
let d: Attribute = parse2(quote! { onclick: { |e| {} } }).unwrap();
|
||||
assert_eq!(a, b);
|
||||
assert_ne!(a, c);
|
||||
assert_ne!(a, d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_with_explicit_closure() {
|
||||
let mut a: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
|
||||
a.el_name = Some(parse_quote!(button));
|
||||
assert!(a
|
||||
.rendered_as_dynamic_attr()
|
||||
.to_string()
|
||||
.contains("call_with_explicit_closure"));
|
||||
|
||||
let mut a: Attribute = parse2(quote! { onclick: { let a = 1; |e| {} } }).unwrap();
|
||||
a.el_name = Some(parse_quote!(button));
|
||||
assert!(a
|
||||
.rendered_as_dynamic_attr()
|
||||
.to_string()
|
||||
.contains("call_with_explicit_closure"));
|
||||
|
||||
let mut a: Attribute = parse2(quote! { onclick: { let b = 2; { |e| { b } } } }).unwrap();
|
||||
a.el_name = Some(parse_quote!(button));
|
||||
assert!(a
|
||||
.rendered_as_dynamic_attr()
|
||||
.to_string()
|
||||
.contains("call_with_explicit_closure"));
|
||||
|
||||
let mut a: Attribute = parse2(quote! { onclick: { let r = |e| { b }; r } }).unwrap();
|
||||
a.el_name = Some(parse_quote!(button));
|
||||
assert!(!a
|
||||
.rendered_as_dynamic_attr()
|
||||
.to_string()
|
||||
.contains("call_with_explicit_closure"));
|
||||
}
|
||||
|
||||
/// Make sure reserved keywords are parsed as attributes
|
||||
|
||||
@@ -110,6 +110,17 @@ fn complex_kitchen_sink() {
|
||||
}
|
||||
}
|
||||
|
||||
// No nesting
|
||||
Component {
|
||||
adsasd: "asd",
|
||||
onclick: {
|
||||
let a = 1;
|
||||
move |_| {
|
||||
let blah = 120;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Component path
|
||||
my::thing::Component {
|
||||
adsasd: "asd",
|
||||
|
||||
Reference in new issue
Block a user