diff --git a/Cargo.toml b/Cargo.toml index 1d4231c58c..38386c98cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syn" -version = "2.0.38" # don't forget to update html_root_url and syn.json +version = "2.0.39" # don't forget to update html_root_url and syn.json authors = ["David Tolnay "] categories = ["development-tools::procedural-macro-helpers", "parser-implementations"] description = "Parser for Rust source code" diff --git a/src/custom_keyword.rs b/src/custom_keyword.rs index 9f3ad87029..6ce23db41e 100644 --- a/src/custom_keyword.rs +++ b/src/custom_keyword.rs @@ -128,7 +128,7 @@ macro_rules! custom_keyword { macro_rules! impl_parse_for_custom_keyword { ($ident:ident) => { // For peek. - impl $crate::token::CustomToken for $ident { + impl $crate::__private::CustomToken for $ident { fn peek(cursor: $crate::buffer::Cursor) -> $crate::__private::bool { if let $crate::__private::Some((ident, _rest)) = cursor.ident() { ident == $crate::__private::stringify!($ident) diff --git a/src/custom_punctuation.rs b/src/custom_punctuation.rs index 062fe51696..1b2c768f44 100644 --- a/src/custom_punctuation.rs +++ b/src/custom_punctuation.rs @@ -113,7 +113,7 @@ macro_rules! custom_punctuation { #[macro_export] macro_rules! impl_parse_for_custom_punctuation { ($ident:ident, $($tt:tt)+) => { - impl $crate::token::CustomToken for $ident { + impl $crate::__private::CustomToken for $ident { fn peek(cursor: $crate::buffer::Cursor) -> $crate::__private::bool { $crate::__private::peek_punct(cursor, $crate::stringify_punct!($($tt)+)) } diff --git a/src/export.rs b/src/export.rs index febd322e11..b9ea5c747b 100644 --- a/src/export.rs +++ b/src/export.rs @@ -57,6 +57,10 @@ pub use crate::token::parsing::{peek_punct, punct as parse_punct}; #[doc(hidden)] pub use crate::token::printing::punct as print_punct; +#[cfg(feature = "parsing")] +#[doc(hidden)] +pub use crate::token::private::CustomToken; + #[cfg(feature = "proc-macro")] #[doc(hidden)] pub type TokenStream = proc_macro::TokenStream; diff --git a/src/expr.rs b/src/expr.rs index ae723242eb..22400be329 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -957,6 +957,8 @@ pub(crate) fn requires_terminator(expr: &Expr) -> bool { #[cfg(feature = "parsing")] pub(crate) mod parsing { use super::*; + #[cfg(feature = "full")] + use crate::ext::IdentExt; use crate::parse::discouraged::Speculative; #[cfg(feature = "full")] use crate::parse::ParseBuffer; @@ -1155,6 +1157,25 @@ pub(crate) mod parsing { } } + #[cfg(feature = "full")] + fn can_begin_expr(input: ParseStream) -> bool { + input.peek(Ident::peek_any) // value name or keyword + || input.peek(token::Paren) // tuple + || input.peek(token::Bracket) // array + || input.peek(token::Brace) // block + || input.peek(Lit) // literal + || input.peek(Token![!]) && !input.peek(Token![!=]) // operator not + || input.peek(Token![-]) && !input.peek(Token![-=]) && !input.peek(Token![->]) // unary minus + || input.peek(Token![*]) && !input.peek(Token![*=]) // dereference + || input.peek(Token![|]) && !input.peek(Token![|=]) // closure + || input.peek(Token![&]) && !input.peek(Token![&=]) // reference + || input.peek(Token![..]) // range notation + || input.peek(Token![<]) && !input.peek(Token![<=]) && !input.peek(Token![<<=]) // associated path + || input.peek(Token![::]) // global path + || input.peek(Lifetime) // labeled loop + || input.peek(Token![#]) // expression attributes + } + #[cfg(feature = "full")] fn parse_expr( input: ParseStream, @@ -1614,7 +1635,7 @@ pub(crate) mod parsing { } else if input.peek(Token![continue]) { input.parse().map(Expr::Continue) } else if input.peek(Token![return]) { - expr_ret(input, allow_struct).map(Expr::Return) + expr_return(input, allow_struct).map(Expr::Return) } else if input.peek(token::Bracket) { array_or_repeat(input) } else if input.peek(Token![let]) { @@ -2223,7 +2244,7 @@ pub(crate) mod parsing { impl Parse for ExprReturn { fn parse(input: ParseStream) -> Result { let allow_struct = AllowStruct(true); - expr_ret(input, allow_struct) + expr_return(input, allow_struct) } } @@ -2247,7 +2268,7 @@ pub(crate) mod parsing { attrs: Vec::new(), yield_token: input.parse()?, expr: { - if !input.is_empty() && !input.peek(Token![,]) && !input.peek(Token![;]) { + if can_begin_expr(input) { Some(input.parse()?) } else { None @@ -2442,34 +2463,46 @@ pub(crate) mod parsing { #[cfg(feature = "full")] fn expr_break(input: ParseStream, allow_struct: AllowStruct) -> Result { + let break_token: Token![break] = input.parse()?; + + let ahead = input.fork(); + let label: Option = ahead.parse()?; + if label.is_some() && ahead.peek(Token![:]) { + // Not allowed: `break 'label: loop {...}` + // Parentheses are required. `break ('label: loop {...})` + let _ = ambiguous_expr(input, allow_struct)?; + let start_span = label.unwrap().apostrophe; + let end_span = input.cursor().prev_span(); + return Err(crate::error::new2( + start_span, + end_span, + "parentheses required", + )); + } + + input.advance_to(&ahead); + let expr = if can_begin_expr(input) && (allow_struct.0 || !input.peek(token::Brace)) { + let expr = ambiguous_expr(input, allow_struct)?; + Some(Box::new(expr)) + } else { + None + }; + Ok(ExprBreak { attrs: Vec::new(), - break_token: input.parse()?, - label: input.parse()?, - expr: { - if input.is_empty() - || input.peek(Token![,]) - || input.peek(Token![;]) - || !allow_struct.0 && input.peek(token::Brace) - { - None - } else { - let expr = ambiguous_expr(input, allow_struct)?; - Some(Box::new(expr)) - } - }, + break_token, + label, + expr, }) } #[cfg(feature = "full")] - fn expr_ret(input: ParseStream, allow_struct: AllowStruct) -> Result { + fn expr_return(input: ParseStream, allow_struct: AllowStruct) -> Result { Ok(ExprReturn { attrs: Vec::new(), return_token: input.parse()?, expr: { - if input.is_empty() || input.peek(Token![,]) || input.peek(Token![;]) { - None - } else { + if can_begin_expr(input) { // NOTE: return is greedy and eats blocks after it even when in a // position where structs are not allowed, such as in if statement // conditions. For example: @@ -2477,6 +2510,8 @@ pub(crate) mod parsing { // if return { println!("A") } {} // Prints "A" let expr = ambiguous_expr(input, allow_struct)?; Some(Box::new(expr)) + } else { + None } }, }) diff --git a/src/lib.rs b/src/lib.rs index a74d4b1174..a3df727a97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -249,7 +249,7 @@ //! dynamic library libproc_macro from rustc toolchain. // Syn types in rustdoc of other crates get linked to here. -#![doc(html_root_url = "https://docs.rs/syn/2.0.38")] +#![doc(html_root_url = "https://docs.rs/syn/2.0.39")] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![allow(non_camel_case_types)] #![allow( @@ -265,6 +265,7 @@ clippy::explicit_auto_deref, clippy::if_not_else, clippy::inherent_to_string, + clippy::into_iter_without_iter, clippy::items_after_statements, clippy::large_enum_variant, clippy::let_underscore_untyped, // https://github.com/rust-lang/rust-clippy/issues/10410 @@ -811,6 +812,8 @@ mod gen { #[path = "../gen_helper.rs"] mod helper; } + +#[cfg(any(feature = "fold", feature = "visit", feature = "visit-mut"))] pub use crate::gen::*; // Not public API. diff --git a/src/parse.rs b/src/parse.rs index 5a2aeb628b..50fe110b58 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -513,8 +513,8 @@ impl<'a> ParseBuffer<'a> { /// /// - `input.peek(Token![struct])` /// - `input.peek(Token![==])` - /// - `input.peek(Ident)` *(does not accept keywords)* - /// - `input.peek(Ident::peek_any)` + /// - `input.peek(syn::Ident)` *(does not accept keywords)* + /// - `input.peek(syn::Ident::peek_any)` /// - `input.peek(Lifetime)` /// - `input.peek(token::Brace)` /// diff --git a/src/token.rs b/src/token.rs index af7f25c42b..05d8f5601b 100644 --- a/src/token.rs +++ b/src/token.rs @@ -88,6 +88,8 @@ //! [Printing]: https://docs.rs/quote/1.0/quote/trait.ToTokens.html //! [`Span`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.Span.html +#[cfg(feature = "parsing")] +pub(crate) use self::private::CustomToken; use self::private::WithSpan; #[cfg(feature = "parsing")] use crate::buffer::Cursor; @@ -134,7 +136,9 @@ pub trait Token: private::Sealed { fn display() -> &'static str; } -mod private { +pub(crate) mod private { + #[cfg(feature = "parsing")] + use crate::buffer::Cursor; use proc_macro2::Span; #[cfg(feature = "parsing")] @@ -147,6 +151,14 @@ mod private { pub struct WithSpan { pub span: Span, } + + // Not public API. + #[doc(hidden)] + #[cfg(feature = "parsing")] + pub trait CustomToken { + fn peek(cursor: Cursor) -> bool; + fn display() -> &'static str; + } } #[cfg(feature = "parsing")] @@ -218,14 +230,6 @@ impl_low_level_token!("punctuation token" Punct punct); impl_low_level_token!("literal" Literal literal); impl_low_level_token!("token" TokenTree token_tree); -// Not public API. -#[doc(hidden)] -#[cfg(feature = "parsing")] -pub trait CustomToken { - fn peek(cursor: Cursor) -> bool; - fn display() -> &'static str; -} - #[cfg(feature = "parsing")] impl private::Sealed for T {} diff --git a/syn.json b/syn.json index 3c2752d6a7..e938985a1d 100644 --- a/syn.json +++ b/syn.json @@ -1,5 +1,5 @@ { - "version": "2.0.38", + "version": "2.0.39", "types": [ { "ident": "Abi", diff --git a/tests/common/eq.rs b/tests/common/eq.rs index 3c2b1c124f..8e334c3305 100644 --- a/tests/common/eq.rs +++ b/tests/common/eq.rs @@ -65,6 +65,7 @@ use rustc_ast::ast::FormatOptions; use rustc_ast::ast::FormatPlaceholder; use rustc_ast::ast::FormatSign; use rustc_ast::ast::FormatTrait; +use rustc_ast::ast::GenBlockKind; use rustc_ast::ast::GenericArg; use rustc_ast::ast::GenericArgs; use rustc_ast::ast::GenericBound; @@ -545,6 +546,7 @@ spanless_eq_enum!(FormatCount; Literal(0) Argument(0)); spanless_eq_enum!(FormatDebugHex; Lower Upper); spanless_eq_enum!(FormatSign; Plus Minus); spanless_eq_enum!(FormatTrait; Display Debug LowerExp UpperExp Octal Pointer Binary LowerHex UpperHex); +spanless_eq_enum!(GenBlockKind; Async Gen); spanless_eq_enum!(GenericArg; Lifetime(0) Type(0) Const(0)); spanless_eq_enum!(GenericArgs; AngleBracketed(0) Parenthesized(0)); spanless_eq_enum!(GenericBound; Trait(0 1) Outlives(0)); @@ -582,7 +584,7 @@ spanless_eq_enum!(WherePredicate; BoundPredicate(0) RegionPredicate(0) EqPredica spanless_eq_enum!(ExprKind; Array(0) ConstBlock(0) Call(0 1) MethodCall(0) Tup(0) Binary(0 1 2) Unary(0 1) Lit(0) Cast(0 1) Type(0 1) Let(0 1 2 3) If(0 1 2) While(0 1 2) ForLoop(0 1 2 3) Loop(0 1 2) Match(0 1) Closure(0) - Block(0 1) Async(0 1) Await(0 1) TryBlock(0) Assign(0 1 2) AssignOp(0 1 2) + Block(0 1) Gen(0 1 2) Await(0 1) TryBlock(0) Assign(0 1 2) AssignOp(0 1 2) Field(0 1) Index(0 1 2) Underscore Range(0 1 2) Path(0 1) AddrOf(0 1 2) Break(0 1) Continue(0) Ret(0) InlineAsm(0) OffsetOf(0 1) MacCall(0) Struct(0) Repeat(0 1) Paren(0) Try(0) Yield(0) Yeet(0) Become(0) diff --git a/tests/repo/mod.rs b/tests/repo/mod.rs index 61d5ff35f4..9d5dfa2f71 100644 --- a/tests/repo/mod.rs +++ b/tests/repo/mod.rs @@ -13,12 +13,14 @@ use std::path::{Path, PathBuf}; use tar::Archive; use walkdir::{DirEntry, WalkDir}; -const REVISION: &str = "9f5fc1bd443f59583e7af0d94d289f95fe1e20c4"; +const REVISION: &str = "a2f5f9691b6ce64c1703feaf9363710dfd7a56cf"; #[rustfmt::skip] static EXCLUDE_FILES: &[&str] = &[ // TODO: CStr literals: c"…", cr"…" // https://github.com/dtolnay/syn/issues/1502 + "src/tools/clippy/tests/ui/needless_raw_string.rs", + "src/tools/clippy/tests/ui/needless_raw_string_hashes.rs", "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0085_expr_literals.rs", // TODO: explicit tail calls: `become _g()` @@ -27,7 +29,12 @@ static EXCLUDE_FILES: &[&str] = &[ // TODO: non-lifetime binders: `where for<'a, T> &'a Struct: Trait` // https://github.com/dtolnay/syn/issues/1435 + "src/tools/rustfmt/tests/source/issue_5721.rs", + "src/tools/rustfmt/tests/source/non-lifetime-binders.rs", + "src/tools/rustfmt/tests/target/issue_5721.rs", + "src/tools/rustfmt/tests/target/non-lifetime-binders.rs", "tests/rustdoc-json/non_lifetime_binders.rs", + "tests/rustdoc/inline_cross/auxiliary/non_lifetime_binders.rs", "tests/rustdoc/non_lifetime_binders.rs", // TODO: return type notation: `where T: Trait` @@ -36,6 +43,19 @@ static EXCLUDE_FILES: &[&str] = &[ "tests/ui/associated-type-bounds/return-type-notation/basic.rs", "tests/ui/feature-gates/feature-gate-return_type_notation.rs", + // TODO: lazy type alias syntax with where-clause in trailing position + // https://github.com/dtolnay/syn/issues/1525 + "tests/rustdoc/typedef-inner-variants-lazy_type_alias.rs", + + // TODO: gen blocks and functions + // https://github.com/dtolnay/syn/issues/1526 + "tests/ui/coroutine/gen_block_is_iter.rs", + "tests/ui/coroutine/gen_block_iterate.rs", + + // TODO: struct literal in match guard + // https://github.com/dtolnay/syn/issues/1527 + "tests/ui/parser/struct-literal-in-match-guard.rs", + // Compile-fail expr parameter in const generic position: f::<1 + 2>() "tests/ui/const-generics/early/closing-args-token.rs", "tests/ui/const-generics/early/const-expression-parameter.rs", @@ -108,9 +128,6 @@ static EXCLUDE_FILES: &[&str] = &[ "tests/ui/lifetimes/bare-trait-object.rs", "tests/ui/parser/bounds-obj-parens.rs", - // Obsolete box syntax - "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0132_box_expr.rs", - // Invalid unparenthesized range pattern inside slice pattern: `[1..]` "tests/ui/consts/miri_unleashed/const_refers_to_static_cross_crate.rs", diff --git a/tests/test_expr.rs b/tests/test_expr.rs index 5d529bf144..2574ea5487 100644 --- a/tests/test_expr.rs +++ b/tests/test_expr.rs @@ -1,11 +1,11 @@ -#![allow(clippy::uninlined_format_args)] +#![allow(clippy::single_element_loop, clippy::uninlined_format_args)] #[macro_use] mod macros; use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; use quote::quote; -use syn::{Expr, ExprRange}; +use syn::{Expr, ExprRange, Stmt}; #[test] fn test_expr_parse() { @@ -310,3 +310,32 @@ fn test_ranges() { syn::parse_str::("lo...").unwrap_err(); syn::parse_str::("lo...hi").unwrap_err(); } + +#[test] +fn test_ambiguous_label() { + for stmt in [ + quote! { + return 'label: loop { break 'label 42; }; + }, + quote! { + break ('label: loop { break 'label 42; }); + }, + quote! { + break 1 + 'label: loop { break 'label 42; }; + }, + quote! { + break 'outer 'inner: loop { break 'inner 42; }; + }, + ] { + syn::parse2::(stmt).unwrap(); + } + + for stmt in [ + // Parentheses required. See https://github.com/rust-lang/rust/pull/87026. + quote! { + break 'label: loop { break 'label 42; }; + }, + ] { + syn::parse2::(stmt).unwrap_err(); + } +}