From 592d113ff24b91290022d9f1aae271598ff235fa Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 31 Mar 2025 16:32:55 +1100 Subject: [PATCH] Fix problem causing `rusqlite` compilation to OOM. This makes the expression re-parsing more like how it's originally done in `parse_nonterminal`. --- compiler/rustc_parse/src/parser/expr.rs | 4 +- tests/ui/attributes/dont-dup-expr-attrs.rs | 133 +++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 tests/ui/attributes/dont-dup-expr-attrs.rs diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 1c2df3ffcd10..e1e6b93abf35 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1397,7 +1397,9 @@ impl<'a> Parser<'a> { } else if let Some(expr) = self.eat_metavar_seq_with_matcher( |mv_kind| matches!(mv_kind, MetaVarKind::Expr { .. }), |this| { - let expr = this.parse_expr(); + // Force collection (as opposed to just `parse_expr`) is required to avoid the + // attribute duplication seen in #138478. + let expr = this.parse_expr_force_collect(); // FIXME(nnethercote) Sometimes with expressions we get a trailing comma, possibly // related to the FIXME in `collect_tokens_for_expr`. Examples are the multi-line // `assert_eq!` calls involving arguments annotated with `#[rustfmt::skip]` in diff --git a/tests/ui/attributes/dont-dup-expr-attrs.rs b/tests/ui/attributes/dont-dup-expr-attrs.rs new file mode 100644 index 000000000000..21da6820a538 --- /dev/null +++ b/tests/ui/attributes/dont-dup-expr-attrs.rs @@ -0,0 +1,133 @@ +//@ check-pass +// +// During development of #124141 at one point expression on attributes were +// being duplicated and `m1` caused an exponential blowup that caused OOM. +// The number of recursive calls depends on the number of doc comments on the +// expr block. On each recursive call, the `#[allow(deprecated)]` attribute(s) on +// the `0` somehow get duplicated, resulting in 1, 2, 4, 8, ... identical +// attributes. +// +// After the fix, the code compiles quickly and normally. + +macro_rules! m1 { + ($(#[$meta:meta])* { $e:expr }) => { + m1! { expr: { $e }, unprocessed: [$(#[$meta])*] } + }; + + (expr: { $e:expr }, unprocessed: [ #[$meta:meta] $($metas:tt)* ]) => { + m1! { expr: { $e }, unprocessed: [ $($metas)* ] } + }; + + (expr: { $e:expr }, unprocessed: []) => { + { $e } + } +} + +macro_rules! m2 { + ($(#[$meta:meta])* { $e:stmt }) => { + m2! { stmt: { $e }, unprocessed: [$(#[$meta])*] } + }; + + (stmt: { $e:stmt }, unprocessed: [ #[$meta:meta] $($metas:tt)* ]) => { + m2! { stmt: { $e }, unprocessed: [ $($metas)* ] } + }; + + (stmt: { $e:stmt }, unprocessed: []) => { + { $e } + } +} + +macro_rules! m3 { + ($(#[$meta:meta])* { $e:item }) => { + m3! { item: { $e }, unprocessed: [$(#[$meta])*] } + }; + + (item: { $e:item }, unprocessed: [ #[$meta:meta] $($metas:tt)* ]) => { + m3! { item: { $e }, unprocessed: [ $($metas)* ] } + }; + + (item: { $e:item }, unprocessed: []) => { + { $e } + } +} + +fn main() { + // Each additional doc comment line doubles the compile time. + m1!( + /// a1 + /// a2 + /// a3 + /// a4 + /// a5 + /// a6 + /// a7 + /// a8 + /// a9 + /// a10 + /// a11 + /// a12 + /// a13 + /// a14 + /// a15 + /// a16 + /// a17 + /// a18 + /// a19 + /// a20 + { + #[allow(deprecated)] 0 + } + ); + + m2!( + /// a1 + /// a2 + /// a3 + /// a4 + /// a5 + /// a6 + /// a7 + /// a8 + /// a9 + /// a10 + /// a11 + /// a12 + /// a13 + /// a14 + /// a15 + /// a16 + /// a17 + /// a18 + /// a19 + /// a20 + { + #[allow(deprecated)] let x = 5 + } + ); + + m3!( + /// a1 + /// a2 + /// a3 + /// a4 + /// a5 + /// a6 + /// a7 + /// a8 + /// a9 + /// a10 + /// a11 + /// a12 + /// a13 + /// a14 + /// a15 + /// a16 + /// a17 + /// a18 + /// a19 + /// a20 + { + #[allow(deprecated)] struct S; + } + ); +}