From c7ccf6d5564e6e56c6b5a80f317348421fc0d007 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 10 Dec 2023 17:43:19 -0800 Subject: [PATCH 1/3] Add test of interpolated paths --- tests/test_expr.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/test_expr.rs b/tests/test_expr.rs index 2574ea5487..a0a0014f61 100644 --- a/tests/test_expr.rs +++ b/tests/test_expr.rs @@ -339,3 +339,64 @@ fn test_ambiguous_label() { syn::parse2::(stmt).unwrap_err(); } } + +#[test] +fn test_extended_interpolated_path() { + let path = Group::new(Delimiter::None, quote!(a::b)); + + let tokens = quote!(if #path {}); + snapshot!(tokens as Expr, @r###" + Expr::If { + cond: Expr::Path { + path: Path { + segments: [ + PathSegment { + ident: "a", + }, + PathSegment { + ident: "b", + }, + ], + }, + }, + then_branch: Block { + stmts: [], + }, + } + "###); + + let tokens = quote!(#path {}); + snapshot!(tokens as Expr, @r###" + Expr::Struct { + path: Path { + segments: [ + PathSegment { + ident: "a", + }, + PathSegment { + ident: "b", + }, + ], + }, + } + "###); + + let tokens = quote!(#path :: c); + snapshot!(tokens as Expr, @r###" + Expr::Path { + path: Path { + segments: [ + PathSegment { + ident: "a", + }, + PathSegment { + ident: "b", + }, + PathSegment { + ident: "c", + }, + ], + }, + } + "###); +} From 93d52b0ebb75b33e8e0d6dd0b2217880f62c960c Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 10 Dec 2023 17:48:51 -0800 Subject: [PATCH 2/3] Add test where None-delimited group makes precedence go wrong --- tests/test_expr.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_expr.rs b/tests/test_expr.rs index a0a0014f61..efb7baca88 100644 --- a/tests/test_expr.rs +++ b/tests/test_expr.rs @@ -399,4 +399,43 @@ fn test_extended_interpolated_path() { }, } "###); + + // FIXME + let nested = Group::new(Delimiter::None, quote!(a::b || true)); + let tokens = quote!(if #nested && false {}); + snapshot!(tokens as Expr, @r###" + Expr::If { + cond: Expr::Binary { + left: Expr::Path { + path: Path { + segments: [ + PathSegment { + ident: "a", + }, + PathSegment { + ident: "b", + }, + ], + }, + }, + op: BinOp::Or, + right: Expr::Binary { + left: Expr::Lit { + lit: Lit::Bool { + value: true, + }, + }, + op: BinOp::And, + right: Expr::Lit { + lit: Lit::Bool { + value: false, + }, + }, + }, + }, + then_branch: Block { + stmts: [], + }, + } + "###); } From da9495b37fc5c883f9c0822f44c0695465703089 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 10 Dec 2023 17:40:11 -0800 Subject: [PATCH 3/3] Support interpolated $:path followed by macro call or struct literal --- src/expr.rs | 63 ++++++++++++++++++++++++++++++++------------ tests/test_expr.rs | 65 ++++++++++++++++++++++++---------------------- 2 files changed, 81 insertions(+), 47 deletions(-) diff --git a/src/expr.rs b/src/expr.rs index 59c0a086c7..f3e37d49c7 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1577,12 +1577,8 @@ pub(crate) mod parsing { // interactions, as they are fully contained. #[cfg(feature = "full")] fn atom_expr(input: ParseStream, allow_struct: AllowStruct) -> Result { - if input.peek(token::Group) - && !input.peek2(Token![::]) - && !input.peek2(Token![!]) - && !input.peek2(token::Brace) - { - input.call(expr_group).map(Expr::Group) + if input.peek(token::Group) { + expr_group(input, allow_struct) } else if input.peek(Lit) { input.parse().map(Expr::Lit) } else if input.peek(Token![async]) @@ -1680,12 +1676,8 @@ pub(crate) mod parsing { #[cfg(not(feature = "full"))] fn atom_expr(input: ParseStream) -> Result { - if input.peek(token::Group) - && !input.peek2(Token![::]) - && !input.peek2(Token![!]) - && !input.peek2(token::Brace) - { - input.call(expr_group).map(Expr::Group) + if input.peek(token::Group) { + expr_group(input) } else if input.peek(Lit) { input.parse().map(Expr::Lit) } else if input.peek(token::Paren) { @@ -1736,7 +1728,21 @@ pub(crate) mod parsing { #[cfg(feature = "full")] allow_struct: AllowStruct, ) -> Result { let (qself, path) = path::parsing::qpath(input, true)?; + rest_of_path_or_macro_or_struct( + qself, + path, + input, + #[cfg(feature = "full")] + allow_struct, + ) + } + fn rest_of_path_or_macro_or_struct( + qself: Option, + path: Path, + input: ParseStream, + #[cfg(feature = "full")] allow_struct: AllowStruct, + ) -> Result { if qself.is_none() && input.peek(Token![!]) && !input.peek(Token![!=]) @@ -1962,13 +1968,38 @@ pub(crate) mod parsing { } } - fn expr_group(input: ParseStream) -> Result { + fn expr_group( + input: ParseStream, + #[cfg(feature = "full")] allow_struct: AllowStruct, + ) -> Result { let group = crate::group::parse_group(input)?; - Ok(ExprGroup { + let mut inner: Expr = group.content.parse()?; + + match inner { + Expr::Path(mut expr) if expr.attrs.is_empty() => { + let grouped_len = expr.path.segments.len(); + Path::parse_rest(input, &mut expr.path, true)?; + match rest_of_path_or_macro_or_struct( + expr.qself, + expr.path, + input, + #[cfg(feature = "full")] + allow_struct, + )? { + Expr::Path(expr) if expr.path.segments.len() == grouped_len => { + inner = Expr::Path(expr); + } + extended => return Ok(extended), + } + } + _ => {} + } + + Ok(Expr::Group(ExprGroup { attrs: Vec::new(), group_token: group.token, - expr: group.content.parse()?, - }) + expr: Box::new(inner), + })) } #[cfg(feature = "full")] diff --git a/tests/test_expr.rs b/tests/test_expr.rs index efb7baca88..9d0b44f9ba 100644 --- a/tests/test_expr.rs +++ b/tests/test_expr.rs @@ -347,16 +347,18 @@ fn test_extended_interpolated_path() { let tokens = quote!(if #path {}); snapshot!(tokens as Expr, @r###" Expr::If { - cond: Expr::Path { - path: Path { - segments: [ - PathSegment { - ident: "a", - }, - PathSegment { - ident: "b", - }, - ], + cond: Expr::Group { + expr: Expr::Path { + path: Path { + segments: [ + PathSegment { + ident: "a", + }, + PathSegment { + ident: "b", + }, + ], + }, }, }, then_branch: Block { @@ -400,36 +402,37 @@ fn test_extended_interpolated_path() { } "###); - // FIXME let nested = Group::new(Delimiter::None, quote!(a::b || true)); let tokens = quote!(if #nested && false {}); snapshot!(tokens as Expr, @r###" Expr::If { cond: Expr::Binary { - left: Expr::Path { - path: Path { - segments: [ - PathSegment { - ident: "a", + left: Expr::Group { + expr: Expr::Binary { + left: Expr::Path { + path: Path { + segments: [ + PathSegment { + ident: "a", + }, + PathSegment { + ident: "b", + }, + ], }, - PathSegment { - ident: "b", + }, + op: BinOp::Or, + right: Expr::Lit { + lit: Lit::Bool { + value: true, }, - ], - }, - }, - op: BinOp::Or, - right: Expr::Binary { - left: Expr::Lit { - lit: Lit::Bool { - value: true, }, }, - op: BinOp::And, - right: Expr::Lit { - lit: Lit::Bool { - value: false, - }, + }, + op: BinOp::And, + right: Expr::Lit { + lit: Lit::Bool { + value: false, }, }, },