Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues with tuples in closing tag #2886

Merged
merged 5 commits into from Oct 8, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
161 changes: 12 additions & 149 deletions 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,
Expand All @@ -21,17 +14,16 @@ pub struct HtmlComponent {
close: Option<HtmlComponentClose>,
}

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<Self> {
if HtmlComponentClose::peek(input.cursor()).is_some() {
// check if the next tokens are </
let trying_to_close = || {
let lt = input.peek(Token![<]);
let div = input.peek2(Token![/]);
lt && div
};

if trying_to_close() {
return match input.parse::<HtmlComponentClose>() {
Ok(close) => Err(syn::Error::new_spanned(
close.to_spanned(),
Expand Down Expand Up @@ -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;
}
hamza1311 marked this conversation as resolved.
Show resolved Hide resolved
}

if trying_to_close() {
break;
}
children.parse_child(input)?;
}

Expand Down Expand Up @@ -127,108 +117,6 @@ impl ToTokens for HtmlComponent {
}
}

impl HtmlComponent {
fn double_colon(mut cursor: Cursor) -> Option<Cursor> {
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,
Expand All @@ -244,15 +132,6 @@ impl HtmlComponentOpen {
}
}

impl PeekValue<Type> for HtmlComponentOpen {
fn peek(cursor: Cursor) -> Option<Type> {
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<Self> {
TagTokens::parse_start_content(input, |input, tag| {
Expand Down Expand Up @@ -282,22 +161,6 @@ impl HtmlComponentClose {
}
}

impl PeekValue<Type> for HtmlComponentClose {
fn peek(cursor: Cursor) -> Option<Type> {
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<Self> {
TagTokens::parse_end_content(input, |input, tag| {
Expand Down
22 changes: 13 additions & 9 deletions 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! { <Generic<String>> };
| ^^^^^^^^^^^^^^^^^

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! { <Generic<String>></Generic> };
| ^^^^^^^^^^

error: this closing tag has no corresponding opening tag
--> $DIR/generic-component-fail.rs:45:30
| ^^^^^^^ expected 1 generic argument
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Removing hand-written parsing and getting better errors!

|
note: struct defined here, with 1 generic parameter: `T`
--> tests/html_macro/generic-component-fail.rs:4:12
|
4 | pub struct Generic<T> {
| ^^^^^^^ -
help: add missing generic argument
|
45 | html! { <Generic<String>></Generic<Vec<String>>> };
| ^^^^^^^^^^^^^^^^^^^^^^^
44 | html! { <Generic<String>></Generic<T>> };
| ~~~~~~~~~~
2 changes: 2 additions & 0 deletions packages/yew-macro/tests/html_macro/generic-component-pass.rs
Expand Up @@ -77,6 +77,8 @@ where

fn compile_pass() {
::yew::html! { <Generic<::std::string::String> /> };
::yew::html! { <Generic<(u8, bool)> /> };
::yew::html! { <Generic<(u8, bool)> ></Generic<(u8, bool)>> };
::yew::html! { <Generic<::std::string::String> ></Generic<::std::string::String>> };

::yew::html! { <Generic<::std::vec::Vec<::std::string::String>> /> };
Expand Down