call_with_explicit_closure support closure in block (#4031)

* call_with_explicit_closure support closure in block

* fix test
This commit is contained in:
Clouds authored and GitHub committed 2025-05-07 13:30:46 -07:00
1 parent 7392d7882b
commit 28a85ab614
2 files changed
+92 -17

No files matched your search

+81 -17
View File
@@ -25,7 +25,8 @@ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
parse_quote, parse_quote,
spanned::Spanned, 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. /// 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 { AttributeValue::EventTokens(_) | AttributeValue::AttrExpr(_) => {
AttributeName::BuiltIn(name) => { let (tokens, span) = match &self.value {
let event_tokens_is_closure = AttributeValue::EventTokens(tokens) => {
syn::parse2::<ExprClosure>(tokens.to_token_stream()).is_ok(); (tokens.to_token_stream(), tokens.span())
let function_name = }
quote_spanned! { tokens.span() => dioxus_elements::events::#name }; AttributeValue::AttrExpr(tokens) => {
let function = if event_tokens_is_closure { (tokens.to_token_stream(), tokens.span())
// 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 } _ => unreachable!(),
} else { };
function_name
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() => let mut block = &block;
#function(#tokens) 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"), match &self.name {
AttributeName::Spread(_) => unreachable!("Handled elsewhere in the macro"), 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) } quote_spanned! { value.span() => dioxus_elements::events::#name(#value) }
} }
@@ -834,8 +865,41 @@ mod tests {
let a: Attribute = parse2(quote! { onclick: |e| {} }).unwrap(); let a: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
let b: Attribute = parse2(quote! { onclick: |e| {} }).unwrap(); let b: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
let c: Attribute = parse2(quote! { onclick: move |e| {} }).unwrap(); let c: Attribute = parse2(quote! { onclick: move |e| {} }).unwrap();
let d: Attribute = parse2(quote! { onclick: { |e| {} } }).unwrap();
assert_eq!(a, b); assert_eq!(a, b);
assert_ne!(a, c); 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 /// Make sure reserved keywords are parsed as attributes
+11
View File
@@ -110,6 +110,17 @@ fn complex_kitchen_sink() {
} }
} }
// No nesting
Component {
adsasd: "asd",
onclick: {
let a = 1;
move |_| {
let blah = 120;
}
}
}
// Component path // Component path
my::thing::Component { my::thing::Component {
adsasd: "asd", adsasd: "asd",