From e6e846c946ef2dbda83417c69f5dbba2b55f2367 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 25 Sep 2022 19:04:06 +0500 Subject: [PATCH 1/5] Add test case for #2225 --- packages/yew-macro/tests/html_macro/generic-component-pass.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yew-macro/tests/html_macro/generic-component-pass.rs b/packages/yew-macro/tests/html_macro/generic-component-pass.rs index 49d88cb56b8..e520923e473 100644 --- a/packages/yew-macro/tests/html_macro/generic-component-pass.rs +++ b/packages/yew-macro/tests/html_macro/generic-component-pass.rs @@ -77,6 +77,7 @@ where fn compile_pass() { ::yew::html! { /> }; + ::yew::html! { /> }; ::yew::html! { >> }; ::yew::html! { > /> }; From a9038daeeab72406e13e16e9760c9c027fa935fa Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 25 Sep 2022 22:00:38 +0500 Subject: [PATCH 2/5] Fix issues with tuples in closing tag --- .../yew-macro/src/html_tree/html_component.rs | 161 ++---------------- .../html_macro/generic-component-fail.stderr | 22 ++- .../html_macro/generic-component-pass.rs | 1 + 3 files changed, 26 insertions(+), 158 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_component.rs b/packages/yew-macro/src/html_tree/html_component.rs index 1a13b16351d..9f8dd962cb8 100644 --- a/packages/yew-macro/src/html_tree/html_component.rs +++ b/packages/yew-macro/src/html_tree/html_component.rs @@ -1,18 +1,11 @@ -use boolinator::Boolinator; use proc_macro2::Span; use quote::{quote, quote_spanned, ToTokens}; -use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::{ - AngleBracketedGenericArguments, GenericArgument, Path, PathArguments, PathSegment, Token, Type, - TypePath, -}; +use syn::{Token, Type}; use super::{HtmlChildrenTree, TagTokens}; use crate::props::ComponentProps; -use crate::PeekValue; pub struct HtmlComponent { ty: Type, @@ -21,17 +14,16 @@ pub struct HtmlComponent { close: Option, } -impl PeekValue<()> for HtmlComponent { - fn peek(cursor: Cursor) -> Option<()> { - HtmlComponentOpen::peek(cursor) - .or_else(|| HtmlComponentClose::peek(cursor)) - .map(|_| ()) - } -} - impl Parse for HtmlComponent { fn parse(input: ParseStream) -> syn::Result { - if HtmlComponentClose::peek(input.cursor()).is_some() { + // check if the next tokens are () { Ok(close) => Err(syn::Error::new_spanned( close.to_spanned(), @@ -60,12 +52,10 @@ impl Parse for HtmlComponent { "this opening tag has no corresponding closing tag", )); } - if let Some(ty) = HtmlComponentClose::peek(input.cursor()) { - if open.ty == ty { - break; - } - } + if trying_to_close() { + break; + } children.parse_child(input)?; } @@ -127,108 +117,6 @@ impl ToTokens for HtmlComponent { } } -impl HtmlComponent { - fn double_colon(mut cursor: Cursor) -> Option { - for _ in 0..2 { - let (punct, c) = cursor.punct()?; - (punct.as_char() == ':').as_option()?; - cursor = c; - } - - Some(cursor) - } - - /// Refer to the [`syn::parse::Parse`] implementation for [`AngleBracketedGenericArguments`]. - fn path_arguments(mut cursor: Cursor) -> Option<(PathArguments, Cursor)> { - let (punct, c) = cursor.punct()?; - cursor = c; - (punct.as_char() == '<').as_option()?; - - let mut args = Punctuated::new(); - - loop { - let punct = cursor.punct(); - if let Some((punct, c)) = punct { - if punct.as_char() == '>' { - cursor = c; - break; - } - } - - let (ty, c) = Self::peek_type(cursor); - cursor = c; - - args.push_value(GenericArgument::Type(ty)); - - let punct = cursor.punct(); - if let Some((punct, c)) = punct { - cursor = c; - if punct.as_char() == '>' { - break; - } else if punct.as_char() == ',' { - args.push_punct(Token![,](Span::mixed_site())) - } - } - } - - Some(( - PathArguments::AngleBracketed(AngleBracketedGenericArguments { - colon2_token: None, - lt_token: Token![<](Span::mixed_site()), - args, - gt_token: Token![>](Span::mixed_site()), - }), - cursor, - )) - } - - fn peek_type(mut cursor: Cursor) -> (Type, Cursor) { - let mut colons_optional = true; - let mut leading_colon = None; - let mut segments = Punctuated::new(); - - loop { - let mut post_colons_cursor = cursor; - if let Some(c) = Self::double_colon(post_colons_cursor) { - if colons_optional { - leading_colon = Some(Token![::](Span::mixed_site())); - } - post_colons_cursor = c; - } else if !colons_optional { - break; - } - - if let Some((ident, c)) = post_colons_cursor.ident() { - cursor = c; - let arguments = if let Some((args, c)) = Self::path_arguments(cursor) { - cursor = c; - args - } else { - PathArguments::None - }; - - segments.push(PathSegment { ident, arguments }); - } else { - break; - } - - // only first `::` is optional - colons_optional = false; - } - - ( - Type::Path(TypePath { - qself: None, - path: Path { - leading_colon, - segments, - }, - }), - cursor, - ) - } -} - struct HtmlComponentOpen { tag: TagTokens, ty: Type, @@ -244,15 +132,6 @@ impl HtmlComponentOpen { } } -impl PeekValue for HtmlComponentOpen { - fn peek(cursor: Cursor) -> Option { - let (punct, cursor) = cursor.punct()?; - (punct.as_char() == '<').as_option()?; - let (typ, _) = HtmlComponent::peek_type(cursor); - Some(typ) - } -} - impl Parse for HtmlComponentOpen { fn parse(input: ParseStream) -> syn::Result { TagTokens::parse_start_content(input, |input, tag| { @@ -282,22 +161,6 @@ impl HtmlComponentClose { } } -impl PeekValue for HtmlComponentClose { - fn peek(cursor: Cursor) -> Option { - let (punct, cursor) = cursor.punct()?; - (punct.as_char() == '<').as_option()?; - - let (punct, cursor) = cursor.punct()?; - (punct.as_char() == '/').as_option()?; - - let (typ, cursor) = HtmlComponent::peek_type(cursor); - - let (punct, _) = cursor.punct()?; - (punct.as_char() == '>').as_option()?; - - Some(typ) - } -} impl Parse for HtmlComponentClose { fn parse(input: ParseStream) -> syn::Result { TagTokens::parse_end_content(input, |input, tag| { diff --git a/packages/yew-macro/tests/html_macro/generic-component-fail.stderr b/packages/yew-macro/tests/html_macro/generic-component-fail.stderr index 33312601115..7ce8f5a802d 100644 --- a/packages/yew-macro/tests/html_macro/generic-component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/generic-component-fail.stderr @@ -1,17 +1,21 @@ error: this opening tag has no corresponding closing tag - --> $DIR/generic-component-fail.rs:43:13 + --> tests/html_macro/generic-component-fail.rs:43:13 | 43 | html! { > }; | ^^^^^^^^^^^^^^^^^ -error: this closing tag has no corresponding opening tag - --> $DIR/generic-component-fail.rs:44:30 +error[E0107]: missing generics for struct `Generic` + --> tests/html_macro/generic-component-fail.rs:44:32 | 44 | html! { > }; - | ^^^^^^^^^^ - -error: this closing tag has no corresponding opening tag - --> $DIR/generic-component-fail.rs:45:30 + | ^^^^^^^ expected 1 generic argument + | +note: struct defined here, with 1 generic parameter: `T` + --> tests/html_macro/generic-component-fail.rs:4:12 + | +4 | pub struct Generic { + | ^^^^^^^ - +help: add missing generic argument | -45 | html! { >>> }; - | ^^^^^^^^^^^^^^^^^^^^^^^ +44 | html! { >> }; + | ~~~~~~~~~~ diff --git a/packages/yew-macro/tests/html_macro/generic-component-pass.rs b/packages/yew-macro/tests/html_macro/generic-component-pass.rs index e520923e473..4aca8a05457 100644 --- a/packages/yew-macro/tests/html_macro/generic-component-pass.rs +++ b/packages/yew-macro/tests/html_macro/generic-component-pass.rs @@ -78,6 +78,7 @@ where fn compile_pass() { ::yew::html! { /> }; ::yew::html! { /> }; + ::yew::html! { >> }; ::yew::html! { >> }; ::yew::html! { > /> }; From ca6c03f807ab85df25a8c9d7c53c891f88ce2f99 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Tue, 4 Oct 2022 01:46:41 +0500 Subject: [PATCH 3/5] Improve diagnostics --- .../yew-macro/src/html_tree/html_component.rs | 45 ++++++++++++++++--- .../tests/html_macro/component-fail.rs | 30 +++++++++++++ .../tests/html_macro/component-fail.stderr | 14 ++++++ .../html_macro/generic-component-fail.rs | 6 +++ .../html_macro/generic-component-fail.stderr | 36 +++++++++------ 5 files changed, 113 insertions(+), 18 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_component.rs b/packages/yew-macro/src/html_tree/html_component.rs index 9f8dd962cb8..751f255f751 100644 --- a/packages/yew-macro/src/html_tree/html_component.rs +++ b/packages/yew-macro/src/html_tree/html_component.rs @@ -1,5 +1,6 @@ use proc_macro2::Span; use quote::{quote, quote_spanned, ToTokens}; +use syn::parse::discouraged::Speculative; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{Token, Type}; @@ -45,7 +46,7 @@ impl Parse for HtmlComponent { } let mut children = HtmlChildrenTree::new(); - loop { + let close = loop { if input.is_empty() { return Err(syn::Error::new_spanned( open.to_spanned(), @@ -54,12 +55,46 @@ impl Parse for HtmlComponent { } if trying_to_close() { - break; + let cursor = input.cursor(); + let _ = cursor + .punct() + .and_then(|(_, cursor)| cursor.punct()) + .and_then(|(_, cursor)| cursor.ident()) + .ok_or_else(|| { + syn::Error::new(Span::call_site(), "expected a valid closing tag (e.g.: )") + })?; + + let fork = input.fork(); + let lt = fork.parse::()?; + let div = Some(fork.parse::()?); + let ty = fork.parse::()?; + if ty != open.ty { + fn format_token_stream(ts: impl ToTokens) -> String { + let string = ts.to_token_stream().to_string(); + // remove unnecessary spaces + string.replace(' ', "") + } + let open_ty = open.ty; + return Err(syn::Error::new_spanned( + quote!(#open_ty #ty), + format!( + "mismatched closing tags: expected `{}`, found `{}`", + format_token_stream(open_ty), + format_token_stream(ty) + ), + )); + } else { + let gt = fork.parse::]>()?; + let close = HtmlComponentClose { + tag: TagTokens { lt, div, gt }, + ty, + }; + input.advance_to(&fork); + break close; + } } children.parse_child(input)?; - } - - let close = input.parse::()?; + }; if !children.is_empty() { if let Some(children_prop) = open.props.children() { diff --git a/packages/yew-macro/tests/html_macro/component-fail.rs b/packages/yew-macro/tests/html_macro/component-fail.rs index 5da187d2f5e..cdf9ccb34d2 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.rs +++ b/packages/yew-macro/tests/html_macro/component-fail.rs @@ -148,4 +148,34 @@ fn not_expressions() { html! { }; } +fn mismatch_closing_tags() { + pub struct A; + impl Component for A { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + unimplemented!() + } + fn view(&self, _ctx: &Context) -> Html { + unimplemented!() + } + } + + pub struct B; + impl Component for B { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + unimplemented!() + } + fn view(&self, _ctx: &Context) -> Html { + unimplemented!() + } + } + let _ = html! { }; + let _ = html! { }; +} + fn main() {} diff --git a/packages/yew-macro/tests/html_macro/component-fail.stderr b/packages/yew-macro/tests/html_macro/component-fail.stderr index 33f60e1a6a3..88716431599 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-fail.stderr @@ -386,6 +386,20 @@ error: only an expression may be assigned as a property. Consider removing this 148 | html! { }; | ^ +error: mismatched closing tags: expected `A`, found `B` + --> tests/html_macro/component-fail.rs:177:22 + | +177 | let _ = html! { }; + | ^^^^^ + +error: expected a valid closing tag (e.g.: ) + --> tests/html_macro/component-fail.rs:178:13 + | +178 | let _ = html! { }; + | ^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0425]: cannot find value `blah` in this scope --> tests/html_macro/component-fail.rs:82:22 | diff --git a/packages/yew-macro/tests/html_macro/generic-component-fail.rs b/packages/yew-macro/tests/html_macro/generic-component-fail.rs index ed2163bcfc5..7d06180caab 100644 --- a/packages/yew-macro/tests/html_macro/generic-component-fail.rs +++ b/packages/yew-macro/tests/html_macro/generic-component-fail.rs @@ -40,9 +40,15 @@ where }} fn compile_fail() { + #[allow(unused_imports)] + use std::path::Path; + html! { > }; html! { > }; html! { >>> }; + + html! { >> }; + html! { > }; } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/generic-component-fail.stderr b/packages/yew-macro/tests/html_macro/generic-component-fail.stderr index 7ce8f5a802d..aa7dbd2b589 100644 --- a/packages/yew-macro/tests/html_macro/generic-component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/generic-component-fail.stderr @@ -1,21 +1,31 @@ error: this opening tag has no corresponding closing tag - --> tests/html_macro/generic-component-fail.rs:43:13 + --> tests/html_macro/generic-component-fail.rs:46:13 | -43 | html! { > }; +46 | html! { > }; | ^^^^^^^^^^^^^^^^^ -error[E0107]: missing generics for struct `Generic` - --> tests/html_macro/generic-component-fail.rs:44:32 +error: mismatched closing tags: expected `Generic`, found `Generic` + --> tests/html_macro/generic-component-fail.rs:47:14 | -44 | html! { > }; - | ^^^^^^^ expected 1 generic argument +47 | html! { > }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: mismatched closing tags: expected `Generic`, found `Generic>` + --> tests/html_macro/generic-component-fail.rs:48:14 + | +48 | html! { >>> }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: mismatched closing tags: expected `Generic`, found `Generic` + --> tests/html_macro/generic-component-fail.rs:50:14 | -note: struct defined here, with 1 generic parameter: `T` - --> tests/html_macro/generic-component-fail.rs:4:12 +50 | html! { >> }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected a valid closing tag (e.g.: ) + --> tests/html_macro/generic-component-fail.rs:51:5 | -4 | pub struct Generic { - | ^^^^^^^ - -help: add missing generic argument +51 | html! { > }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -44 | html! { >> }; - | ~~~~~~~~~~ + = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) From ff923bd2f2c5e830a681ed9cf87f7611b2f2e41d Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Tue, 4 Oct 2022 01:55:23 +0500 Subject: [PATCH 4/5] fmt --- packages/yew-macro/src/html_tree/html_component.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/yew-macro/src/html_tree/html_component.rs b/packages/yew-macro/src/html_tree/html_component.rs index 751f255f751..e3ef15954e9 100644 --- a/packages/yew-macro/src/html_tree/html_component.rs +++ b/packages/yew-macro/src/html_tree/html_component.rs @@ -61,7 +61,10 @@ impl Parse for HtmlComponent { .and_then(|(_, cursor)| cursor.punct()) .and_then(|(_, cursor)| cursor.ident()) .ok_or_else(|| { - syn::Error::new(Span::call_site(), "expected a valid closing tag (e.g.: )") + syn::Error::new( + Span::call_site(), + "expected a valid closing tag (e.g.: )", + ) })?; let fork = input.fork(); From 3dae63ae17af356b38bacecd8c4933c5bac60a03 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 8 Oct 2022 20:43:51 +0500 Subject: [PATCH 5/5] better diagnostics --- .../yew-macro/src/html_tree/html_component.rs | 67 +++++++++---------- .../tests/html_macro/component-fail.stderr | 10 +-- .../html_macro/generic-component-fail.stderr | 10 +-- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_component.rs b/packages/yew-macro/src/html_tree/html_component.rs index e3ef15954e9..4f557134c36 100644 --- a/packages/yew-macro/src/html_tree/html_component.rs +++ b/packages/yew-macro/src/html_tree/html_component.rs @@ -55,46 +55,43 @@ impl Parse for HtmlComponent { } if trying_to_close() { - let cursor = input.cursor(); - let _ = cursor - .punct() - .and_then(|(_, cursor)| cursor.punct()) - .and_then(|(_, cursor)| cursor.ident()) - .ok_or_else(|| { + fn format_token_stream(ts: impl ToTokens) -> String { + let string = ts.to_token_stream().to_string(); + // remove unnecessary spaces + string.replace(' ', "") + } + + let fork = input.fork(); + break TagTokens::parse_end_content(&fork, |i_fork, tag| { + let ty = i_fork.parse().map_err(|e| { syn::Error::new( - Span::call_site(), - "expected a valid closing tag (e.g.: )", + e.span(), + format!( + "expected a valid closing tag for component\nnote: found opening \ + tag `{lt}{0}{gt}`\nhelp: try `{lt}/{0}{gt}`", + format_token_stream(&open.ty), + lt = open.tag.lt.to_token_stream(), + gt = open.tag.gt.to_token_stream(), + ), ) })?; - let fork = input.fork(); - let lt = fork.parse::()?; - let div = Some(fork.parse::()?); - let ty = fork.parse::()?; - if ty != open.ty { - fn format_token_stream(ts: impl ToTokens) -> String { - let string = ts.to_token_stream().to_string(); - // remove unnecessary spaces - string.replace(' ', "") + if ty != open.ty { + let open_ty = &open.ty; + Err(syn::Error::new_spanned( + quote!(#open_ty #ty), + format!( + "mismatched closing tags: expected `{}`, found `{}`", + format_token_stream(open_ty), + format_token_stream(ty) + ), + )) + } else { + let close = HtmlComponentClose { tag, ty }; + input.advance_to(&fork); + Ok(close) } - let open_ty = open.ty; - return Err(syn::Error::new_spanned( - quote!(#open_ty #ty), - format!( - "mismatched closing tags: expected `{}`, found `{}`", - format_token_stream(open_ty), - format_token_stream(ty) - ), - )); - } else { - let gt = fork.parse::]>()?; - let close = HtmlComponentClose { - tag: TagTokens { lt, div, gt }, - ty, - }; - input.advance_to(&fork); - break close; - } + })?; } children.parse_child(input)?; }; diff --git a/packages/yew-macro/tests/html_macro/component-fail.stderr b/packages/yew-macro/tests/html_macro/component-fail.stderr index 88716431599..15229044d0e 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-fail.stderr @@ -392,13 +392,13 @@ error: mismatched closing tags: expected `A`, found `B` 177 | let _ = html! { }; | ^^^^^ -error: expected a valid closing tag (e.g.: ) - --> tests/html_macro/component-fail.rs:178:13 +error: expected a valid closing tag for component + note: found opening tag `` + help: try `` + --> tests/html_macro/component-fail.rs:178:24 | 178 | let _ = html! { }; - | ^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^ error[E0425]: cannot find value `blah` in this scope --> tests/html_macro/component-fail.rs:82:22 diff --git a/packages/yew-macro/tests/html_macro/generic-component-fail.stderr b/packages/yew-macro/tests/html_macro/generic-component-fail.stderr index aa7dbd2b589..a0071a87b44 100644 --- a/packages/yew-macro/tests/html_macro/generic-component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/generic-component-fail.stderr @@ -22,10 +22,10 @@ error: mismatched closing tags: expected `Generic`, found `Generic 50 | html! { >> }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected a valid closing tag (e.g.: ) - --> tests/html_macro/generic-component-fail.rs:51:5 +error: expected a valid closing tag for component + note: found opening tag `>` + help: try `>` + --> tests/html_macro/generic-component-fail.rs:51:30 | 51 | html! { > }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^