Fix parsing of braced expressions followed by a method (#4035)

* Fix parsing of braced expressions followed by a method

* Improves the logic for parsing braced expressions

* Fix hot-reload issue

* Added cases for braced expressions followed by identifiers or string literals
This commit is contained in:
Juan Vásquez authored and GitHub committed 2025-05-02 08:31:27 -05:00
1 parent f43a8c69fa
commit 71d85e6dd5
2 files changed
+53 -3

No files matched your search

+28 -3
View File
@@ -1,4 +1,4 @@
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::{Delimiter, TokenStream as TokenStream2, TokenTree};
use quote::ToTokens; use quote::ToTokens;
use std::hash; use std::hash;
use syn::{parse::Parse, spanned::Spanned, token::Brace, Expr}; use syn::{parse::Parse, spanned::Spanned, token::Brace, Expr};
@@ -15,8 +15,33 @@ pub struct PartialExpr {
impl Parse for PartialExpr { impl Parse for PartialExpr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
// Parse as an expression if there is no brace // Input is a braced expression if it's a braced group
if !input.peek(syn::token::Brace) { // followed by either of the following:
// - the end of the stream
// - a comma
// - another braced group
// - an identifier
// - a string literal
let mut is_braced = false;
if let Some((TokenTree::Group(group), next)) = input.fork().cursor().token_tree() {
let next_char_is_a_comma = next.punct().is_some_and(|(tt, _)| tt.as_char() == ',');
let next_is_a_braced_exp = next.group(Delimiter::Brace).is_some();
let next_is_an_ident = next.ident().is_some();
let next_is_a_string_literal = next.literal().is_some();
if group.delimiter() == Delimiter::Brace
&& (next.eof()
|| next_char_is_a_comma
|| next_is_a_braced_exp
|| next_is_an_ident
|| next_is_a_string_literal)
{
is_braced = true
}
};
// Parse as an expression if it's not braced
if !is_braced {
let expr = input.parse::<syn::Expr>()?; let expr = input.parse::<syn::Expr>()?;
return Ok(Self { return Ok(Self {
brace: None, brace: None,
+25
View File
@@ -71,6 +71,7 @@ fn complex_kitchen_sink() {
// complex_carry // complex_carry
button { button {
class: "flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded", class: "flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded",
width: {"100%"}.to_string(),
onclick: move |evt| { onclick: move |evt| {
show_user_menu.set(!show_user_menu.get()); show_user_menu.set(!show_user_menu.get());
evt.cancel_bubble(); evt.cancel_bubble();
@@ -167,3 +168,27 @@ fn key_cannot_be_static() {
println!("{:?}", parsed.body.diagnostics); println!("{:?}", parsed.body.diagnostics);
assert!(!parsed.body.diagnostics.is_empty()); assert!(!parsed.body.diagnostics.is_empty());
} }
#[test]
fn braced_expressions() {
let item = quote::quote! {
div {
width: {100} - 50,
width: {"100%"}.to_string(),
width: {|| "100%"}(),
}
// Partial expressions in braces rsx should be allowed and output as-is
// for autocomplete
{partial.}
div {}
{partial.}
// Comments should be ignored
div {}
{partial.}
"hello world"
{partial.}
if true {}
};
let _cb: CallBody = syn::parse2(item).unwrap();
}