From ac3905b23711cc9729d8138fa72073692dfac7fb Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sat, 12 Dec 2020 16:08:08 -0500 Subject: [PATCH 1/5] Fix parsing of path type wrapped in a `None`-delimited group Previously, `peek2` was used to check for `::` or `<` following a `None`-delimited group. However, `peek2` will look *inside*` a `None`-delimited group, so this code would treat input like `{{ Vec }}` as having an angle bracket *after* the `None`-delimited group. We now consume the `None`-delimited group by parsing it, and then use `peek` to check for tokens that occur *after* the `None`-delimited group. --- src/ty.rs | 13 ++++++++++-- tests/test_ty.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/ty.rs b/src/ty.rs index 6e493e05b7..bbacd8487b 100644 --- a/src/ty.rs +++ b/src/ty.rs @@ -342,8 +342,17 @@ pub mod parsing { } fn ambig_ty(input: ParseStream, allow_plus: bool) -> Result { - if input.peek(token::Group) && !input.peek2(Token![::]) && !input.peek2(Token![<]) { - return input.parse().map(Type::Group); + if input.peek(token::Group) { + let forked = input.fork(); + // Consume the group, and check for a `::` or `<` *after* it + // This ensure that we match `$ty`, and not `$ty` + // where `$ty` is `SomeType. + // We cannot use `peek2`, since it looks *inside* a `None`-delimited + // group + let _ = forked.parse().map(Type::Group); + if !forked.peek(Token![::]) && !forked.peek(Token![<]) { + return input.parse().map(Type::Group); + } } let begin = input.fork(); diff --git a/tests/test_ty.rs b/tests/test_ty.rs index 9cbdcd6b99..1523391222 100644 --- a/tests/test_ty.rs +++ b/tests/test_ty.rs @@ -51,3 +51,55 @@ fn test_macro_variable_type() { } "###); } + +#[test] +fn test_group_angle_brackets() { + // mimics the token stream corresponding to `$ty` + let tokens = TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("Option", Span::call_site())), + TokenTree::Punct(Punct::new('<', Spacing::Alone)), + TokenTree::Group(Group::new(Delimiter::None, quote! { Vec })), + TokenTree::Punct(Punct::new('>', Spacing::Alone)), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Option", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Group { + elem: Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Vec", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "u8", + arguments: None, + }, + ], + }, + }), + ], + }, + }, + ], + }, + }, + }), + ], + }, + }, + ], + }, + } + "###); +} From 5822aeabaad20a71262bef3405cd945398d44dcc Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 18 Dec 2020 18:35:02 -0800 Subject: [PATCH 2/5] Add test of path type wrapped in None-delimited group --- tests/test_ty.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_ty.rs b/tests/test_ty.rs index 9cbdcd6b99..47c20041f3 100644 --- a/tests/test_ty.rs +++ b/tests/test_ty.rs @@ -51,3 +51,53 @@ fn test_macro_variable_type() { } "###); } + +#[test] +fn test_group_angle_brackets() { + // mimics the token stream corresponding to `Option<$ty>` + let tokens = TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("Option", Span::call_site())), + TokenTree::Punct(Punct::new('<', Spacing::Alone)), + TokenTree::Group(Group::new(Delimiter::None, quote! { Vec })), + TokenTree::Punct(Punct::new('>', Spacing::Alone)), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Option", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Vec", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "u8", + arguments: None, + }, + ], + }, + }), + ], + }, + }, + ], + }, + }), + ], + }, + }, + ], + }, + } + "###); +} From f492305aa019aaa0b1f4d89eb37e5c469cd20e11 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 18 Dec 2020 18:38:02 -0800 Subject: [PATCH 3/5] Preserve None-delimited group around path type --- src/path.rs | 28 +++++++++++++++++++--------- src/ty.rs | 36 +++++++++++++++++++++++++++++++++--- tests/test_ty.rs | 42 ++++++++++++++++++++++-------------------- 3 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/path.rs b/src/path.rs index a2b42241e1..31a80e59f6 100644 --- a/src/path.rs +++ b/src/path.rs @@ -499,22 +499,32 @@ pub mod parsing { } } - fn parse_helper(input: ParseStream, expr_style: bool) -> Result { - Ok(Path { + pub(crate) fn parse_helper(input: ParseStream, expr_style: bool) -> Result { + let mut path = Path { leading_colon: input.parse()?, segments: { let mut segments = Punctuated::new(); let value = PathSegment::parse_helper(input, expr_style)?; segments.push_value(value); - while input.peek(Token![::]) { - let punct: Token![::] = input.parse()?; - segments.push_punct(punct); - let value = PathSegment::parse_helper(input, expr_style)?; - segments.push_value(value); - } segments }, - }) + }; + Path::parse_rest(input, &mut path, expr_style)?; + Ok(path) + } + + pub(crate) fn parse_rest( + input: ParseStream, + path: &mut Self, + expr_style: bool, + ) -> Result<()> { + while input.peek(Token![::]) { + let punct: Token![::] = input.parse()?; + path.segments.push_punct(punct); + let value = PathSegment::parse_helper(input, expr_style)?; + path.segments.push_value(value); + } + Ok(()) } } diff --git a/src/ty.rs b/src/ty.rs index 6e493e05b7..d9a75b15d2 100644 --- a/src/ty.rs +++ b/src/ty.rs @@ -342,11 +342,41 @@ pub mod parsing { } fn ambig_ty(input: ParseStream, allow_plus: bool) -> Result { - if input.peek(token::Group) && !input.peek2(Token![::]) && !input.peek2(Token![<]) { - return input.parse().map(Type::Group); + let begin = input.fork(); + + if input.peek(token::Group) { + let mut group: TypeGroup = input.parse()?; + if input.peek(Token![::]) && input.peek3(Ident::peek_any) { + if let Type::Path(mut ty) = *group.elem { + Path::parse_rest(input, &mut ty.path, false)?; + return Ok(Type::Path(ty)); + } else { + return Ok(Type::Path(TypePath { + qself: Some(QSelf { + lt_token: Token![<](group.group_token.span), + position: 0, + as_token: None, + gt_token: Token![>](group.group_token.span), + ty: group.elem, + }), + path: Path::parse_helper(input, false)?, + })); + } + } else if input.peek(Token![<]) { + if let Type::Path(mut ty) = *group.elem { + let arguments = &mut ty.path.segments.last_mut().unwrap().arguments; + if let PathArguments::None = arguments { + *arguments = PathArguments::AngleBracketed(input.parse()?); + Path::parse_rest(input, &mut ty.path, false)?; + return Ok(Type::Path(ty)); + } else { + group.elem = Box::new(Type::Path(ty)); + } + } + } + return Ok(Type::Group(group)); } - let begin = input.fork(); let mut lifetimes = None::; let mut lookahead = input.lookahead1(); if lookahead.peek(Token![for]) { diff --git a/tests/test_ty.rs b/tests/test_ty.rs index 47c20041f3..546ff88290 100644 --- a/tests/test_ty.rs +++ b/tests/test_ty.rs @@ -70,27 +70,29 @@ fn test_group_angle_brackets() { ident: "Option", arguments: PathArguments::AngleBracketed { args: [ - Type(Type::Path { - path: Path { - segments: [ - PathSegment { - ident: "Vec", - arguments: PathArguments::AngleBracketed { - args: [ - Type(Type::Path { - path: Path { - segments: [ - PathSegment { - ident: "u8", - arguments: None, - }, - ], - }, - }), - ], + Type(Type::Group { + elem: Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Vec", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "u8", + arguments: None, + }, + ], + }, + }), + ], + }, }, - }, - ], + ], + }, }, }), ], From 0a2e4e38b3f598e96125bd2c29df158894ab4990 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 20 Dec 2020 12:22:20 -0800 Subject: [PATCH 4/5] Add test of $ty::Item --- tests/test_ty.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/test_ty.rs b/tests/test_ty.rs index 546ff88290..12eb81b35d 100644 --- a/tests/test_ty.rs +++ b/tests/test_ty.rs @@ -103,3 +103,80 @@ fn test_group_angle_brackets() { } "###); } + +#[test] +fn test_group_colons() { + // mimics the token stream corresponding to `$ty::Item` + let tokens = TokenStream::from_iter(vec![ + TokenTree::Group(Group::new(Delimiter::None, quote! { Vec })), + TokenTree::Punct(Punct::new(':', Spacing::Joint)), + TokenTree::Punct(Punct::new(':', Spacing::Alone)), + TokenTree::Ident(Ident::new("Item", Span::call_site())), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Vec", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "u8", + arguments: None, + }, + ], + }, + }), + ], + }, + }, + PathSegment { + ident: "Item", + arguments: None, + }, + ], + }, + } + "###); + + let tokens = TokenStream::from_iter(vec![ + TokenTree::Group(Group::new(Delimiter::None, quote! { [T] })), + TokenTree::Punct(Punct::new(':', Spacing::Joint)), + TokenTree::Punct(Punct::new(':', Spacing::Alone)), + TokenTree::Ident(Ident::new("Element", Span::call_site())), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + qself: Some(QSelf { + ty: Type::Slice { + elem: Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "T", + arguments: None, + }, + ], + }, + }, + }, + position: 0, + }), + path: Path { + leading_colon: Some, + segments: [ + PathSegment { + ident: "Element", + arguments: None, + }, + ], + }, + } + "###); +} From d5d299017269092c4f4186c3d644deb6df1f0d0f Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 20 Dec 2020 12:52:13 -0800 Subject: [PATCH 5/5] Add test of $ty:: --- src/ty.rs | 2 +- tests/test_ty.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/ty.rs b/src/ty.rs index d9a75b15d2..eacae3b5db 100644 --- a/src/ty.rs +++ b/src/ty.rs @@ -362,7 +362,7 @@ pub mod parsing { path: Path::parse_helper(input, false)?, })); } - } else if input.peek(Token![<]) { + } else if input.peek(Token![<]) || input.peek(Token![::]) && input.peek3(Token![<]) { if let Type::Path(mut ty) = *group.elem { let arguments = &mut ty.path.segments.last_mut().unwrap().arguments; if let PathArguments::None = arguments { diff --git a/tests/test_ty.rs b/tests/test_ty.rs index 12eb81b35d..7e20dd9936 100644 --- a/tests/test_ty.rs +++ b/tests/test_ty.rs @@ -50,6 +50,43 @@ fn test_macro_variable_type() { }, } "###); + + // mimics the token stream corresponding to `$ty::` + let tokens = TokenStream::from_iter(vec![ + TokenTree::Group(Group::new(Delimiter::None, quote! { ty })), + TokenTree::Punct(Punct::new(':', Spacing::Joint)), + TokenTree::Punct(Punct::new(':', Spacing::Alone)), + TokenTree::Punct(Punct::new('<', Spacing::Alone)), + TokenTree::Ident(Ident::new("T", Span::call_site())), + TokenTree::Punct(Punct::new('>', Spacing::Alone)), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "ty", + arguments: PathArguments::AngleBracketed { + colon2_token: Some, + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "T", + arguments: None, + }, + ], + }, + }), + ], + }, + }, + ], + }, + } + "###); } #[test]