rust-lang / rust-lang/rustfmt

extremely slow performance with deeply indented bag of stuff

Open
#5,500 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug I-slow
Dominant language
Rust
Stars
7k
Forks
1.1k
Avg merge
2d 13h
Merged PRs (30d)
24

Description

A user reported rustfmt hanging at https://github.com/dtolnay/cargo-expand/issues/161.

Here's a semi-reduced example:

fn foo() {
    {
        {
            {
                pub fn file (state : :: std :: boxed :: Box < :: pest :: ParserState < Rule > >) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < Rule > > > { state . rule (Rule :: file , | state | { state . sequence (| state | { self :: SOI (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { state . sequence (| state | { state . optional (| state | { self :: A (state) . or_else (| state | { self :: B (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: NEWLINE (state) }) }) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: A (state) . or_else (| state | { self :: B (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: NEWLINE (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: EOI (state) }) }) }) }
            }
        }
    }
}

That takes about 80s to format on my system.

Here is the original code in full:

# ! [feature (prelude_import)] # [prelude_import] use std :: prelude :: rust_2021 :: * ; # [macro_use] extern crate std ; extern crate pest ; # [macro_use] extern crate pest_derive ; # [grammar = "grm.pest"] pub struct MyParser ; # [allow (non_upper_case_globals)] const _PEST_GRAMMAR_MyParser : & 'static str = "A = { \"A\" }\nB = { \"B\" }\nfile = {\n    SOI ~\n    (( A | B )? ~ NEWLINE)* ~\n    EOI\n}\n\n" ; # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] pub enum Rule { EOI , A , B , file , } # [automatically_derived] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: clone :: Clone for Rule { # [inline] fn clone (& self) -> Rule { * self } } # [automatically_derived] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: marker :: Copy for Rule { } # [automatically_derived] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: fmt :: Debug for Rule { fn fmt (& self , f : & mut :: core :: fmt :: Formatter) -> :: core :: fmt :: Result { match self { Rule :: EOI => :: core :: fmt :: Formatter :: write_str (f , "EOI") , Rule :: A => :: core :: fmt :: Formatter :: write_str (f , "A") , Rule :: B => :: core :: fmt :: Formatter :: write_str (f , "B") , Rule :: file => :: core :: fmt :: Formatter :: write_str (f , "file") , } } } # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: marker :: StructuralEq for Rule { } # [automatically_derived] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: cmp :: Eq for Rule { # [inline] # [doc (hidden)] # [no_coverage] fn assert_receiver_is_total_eq (& self) -> () { } } # [automatically_derived] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: hash :: Hash for Rule { fn hash < __H : :: core :: hash :: Hasher > (& self , state : & mut __H) -> () { let __self_tag = :: core :: intrinsics :: discriminant_value (self) ; :: core :: hash :: Hash :: hash (& __self_tag , state) } } # [automatically_derived] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: cmp :: Ord for Rule { # [inline] fn cmp (& self , other : & Rule) -> :: core :: cmp :: Ordering { let __self_tag = :: core :: intrinsics :: discriminant_value (self) ; let __arg1_tag = :: core :: intrinsics :: discriminant_value (other) ; :: core :: cmp :: Ord :: cmp (& __self_tag , & __arg1_tag) } } # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: marker :: StructuralPartialEq for Rule { } # [automatically_derived] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: cmp :: PartialEq for Rule { # [inline] fn eq (& self , other : & Rule) -> bool { let __self_tag = :: core :: intrinsics :: discriminant_value (self) ; let __arg1_tag = :: core :: intrinsics :: discriminant_value (other) ; __self_tag == __arg1_tag } } # [automatically_derived] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] impl :: core :: cmp :: PartialOrd for Rule { # [inline] fn partial_cmp (& self , other : & Rule) -> :: core :: option :: Option < :: core :: cmp :: Ordering > { let __self_tag = :: core :: intrinsics :: discriminant_value (self) ; let __arg1_tag = :: core :: intrinsics :: discriminant_value (other) ; :: core :: cmp :: PartialOrd :: partial_cmp (& __self_tag , & __arg1_tag) } } # [allow (clippy :: all)] impl :: pest :: Parser < Rule > for MyParser { fn parse < 'i > (rule : Rule , input : & 'i str) -> :: std :: result :: Result < :: pest :: iterators :: Pairs < 'i , Rule > , :: pest :: error :: Error < Rule > > { mod rules { # ! [allow (clippy :: upper_case_acronyms)] pub mod hidden { use super :: super :: Rule ; # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn skip (state : :: std :: boxed :: Box < :: pest :: ParserState < Rule > >) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < Rule > > > { Ok (state) } } pub mod visible { use super :: super :: Rule ; # [inline] # [allow (non_snake_case , unused_variables)] pub fn A (state : :: std :: boxed :: Box < :: pest :: ParserState < Rule > >) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < Rule > > > { state . rule (Rule :: A , | state | { state . match_string ("A") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn B (state : :: std :: boxed :: Box < :: pest :: ParserState < Rule > >) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < Rule > > > { state . rule (Rule :: B , | state | { state . match_string ("B") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn file (state : :: std :: boxed :: Box < :: pest :: ParserState < Rule > >) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < Rule > > > { state . rule (Rule :: file , | state | { state . sequence (| state | { self :: SOI (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { state . sequence (| state | { state . optional (| state | { self :: A (state) . or_else (| state | { self :: B (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: NEWLINE (state) }) }) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: A (state) . or_else (| state | { self :: B (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: NEWLINE (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: EOI (state) }) }) }) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn EOI (state : :: std :: boxed :: Box < :: pest :: ParserState < Rule > >) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < Rule > > > { state . rule (Rule :: EOI , | state | state . end_of_input ()) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn SOI (state : :: std :: boxed :: Box < :: pest :: ParserState < Rule > >) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < Rule > > > { state . start_of_input () } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn NEWLINE (state : :: std :: boxed :: Box < :: pest :: ParserState < Rule > >) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < Rule > > > { state . match_string ("\n") . or_else (| state | state . match_string ("\r\n")) . or_else (| state | state . match_string ("\r")) } } pub use self :: visible :: * ; } :: pest :: state (input , | state | { match rule { Rule :: A => rules :: A (state) , Rule :: B => rules :: B (state) , Rule :: file => rules :: file (state) , Rule :: EOI => rules :: EOI (state) , } }) } } fn main () { }

And formatting that code looks like:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
extern crate pest;
#[macro_use]
extern crate pest_derive;

#[grammar = "grm.pest"]
pub struct MyParser;
#[allow(non_upper_case_globals)]
const _PEST_GRAMMAR_MyParser: &'static str =
    "A = { \"A\" }\nB = { \"B\" }\nfile = {\n    SOI ~\n    (( A | B )? ~ NEWLINE)* ~\n    EOI\n}\n\n";
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
pub enum Rule {
    EOI,
    A,
    B,
    file,
}
#[automatically_derived]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::clone::Clone for Rule {
    #[inline]
    fn clone(&self) -> Rule {
        *self
    }
}
#[automatically_derived]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::marker::Copy for Rule {}
#[automatically_derived]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::fmt::Debug for Rule {
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Rule::EOI => ::core::fmt::Formatter::write_str(f, "EOI"),
            Rule::A => ::core::fmt::Formatter::write_str(f, "A"),
            Rule::B => ::core::fmt::Formatter::write_str(f, "B"),
            Rule::file => ::core::fmt::Formatter::write_str(f, "file"),
        }
    }
}
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::marker::StructuralEq for Rule {}
#[automatically_derived]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::cmp::Eq for Rule {
    #[inline]
    #[doc(hidden)]
    #[no_coverage]
    fn assert_receiver_is_total_eq(&self) -> () {}
}
#[automatically_derived]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::hash::Hash for Rule {
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
        let __self_tag = ::core::intrinsics::discriminant_value(self);
        ::core::hash::Hash::hash(&__self_tag, state)
    }
}
#[automatically_derived]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::cmp::Ord for Rule {
    #[inline]
    fn cmp(&self, other: &Rule) -> ::core::cmp::Ordering {
        let __self_tag = ::core::intrinsics::discriminant_value(self);
        let __arg1_tag = ::core::intrinsics::discriminant_value(other);
        ::core::cmp::Ord::cmp(&__self_tag, &__arg1_tag)
    }
}
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::marker::StructuralPartialEq for Rule {}
#[automatically_derived]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::cmp::PartialEq for Rule {
    #[inline]
    fn eq(&self, other: &Rule) -> bool {
        let __self_tag = ::core::intrinsics::discriminant_value(self);
        let __arg1_tag = ::core::intrinsics::discriminant_value(other);
        __self_tag == __arg1_tag
    }
}
#[automatically_derived]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
impl ::core::cmp::PartialOrd for Rule {
    #[inline]
    fn partial_cmp(&self, other: &Rule) -> ::core::option::Option<::core::cmp::Ordering> {
        let __self_tag = ::core::intrinsics::discriminant_value(self);
        let __arg1_tag = ::core::intrinsics::discriminant_value(other);
        ::core::cmp::PartialOrd::partial_cmp(&__self_tag, &__arg1_tag)
    }
}
#[allow(clippy::all)]
impl ::pest::Parser<Rule> for MyParser {
    fn parse<'i>(
        rule: Rule,
        input: &'i str,
    ) -> ::std::result::Result<::pest::iterators::Pairs<'i, Rule>, ::pest::error::Error<Rule>> {
        mod rules {
            #![allow(clippy::upper_case_acronyms)]
            pub mod hidden {
                use super::super::Rule;
                #[inline]
                #[allow(dead_code, non_snake_case, unused_variables)]
                pub fn skip(
                    state: ::std::boxed::Box<::pest::ParserState<Rule>>,
                ) -> ::pest::ParseResult<::std::boxed::Box<::pest::ParserState<Rule>>>
                {
                    Ok(state)
                }
            }
            pub mod visible {
                use super::super::Rule;
                #[inline]
                #[allow(non_snake_case, unused_variables)]
                pub fn A(
                    state: ::std::boxed::Box<::pest::ParserState<Rule>>,
                ) -> ::pest::ParseResult<::std::boxed::Box<::pest::ParserState<Rule>>>
                {
                    state.rule(Rule::A, |state| state.match_string("A"))
                }
                #[inline]
                #[allow(non_snake_case, unused_variables)]
                pub fn B(
                    state: ::std::boxed::Box<::pest::ParserState<Rule>>,
                ) -> ::pest::ParseResult<::std::boxed::Box<::pest::ParserState<Rule>>>
                {
                    state.rule(Rule::B, |state| state.match_string("B"))
                }
                #[inline]
                #[allow(non_snake_case, unused_variables)]
                pub fn file(
                    state: ::std::boxed::Box<::pest::ParserState<Rule>>,
                ) -> ::pest::ParseResult<::std::boxed::Box<::pest::ParserState<Rule>>>
                {
                    state.rule(Rule::file,
                        |state|
                            {
                                state.sequence(|state|
                                        {
                                            self::SOI(state).and_then(|state|
                                                                {
                                                                    super::hidden::skip(state)
                                                                }).and_then(|state|
                                                            {
                                                                state.sequence(|state|
                                                                        {
                                                                            state.optional(|state|
                                                                                    {
                                                                                        state.sequence(|state|
                                                                                                    {
                                                                                                        state.optional(|state|
                                                                                                                        {
                                                                                                                            self::A(state).or_else(|state| { self::B(state) })
                                                                                                                        }).and_then(|state|
                                                                                                                    {
                                                                                                                        super::hidden::skip(state)
                                                                                                                    }).and_then(|state| { self::NEWLINE(state) })
                                                                                                    }).and_then(|state|
                                                                                                {
                                                                                                    state.repeat(|state|
                                                                                                            {
                                                                                                                state.sequence(|state|
                                                                                                                        {
                                                                                                                            super::hidden::skip(state).and_then(|state|
                                                                                                                                    {
                                                                                                                                        state.sequence(|state|
                                                                                                                                                {
                                                                                                                                                    state.optional(|state|
                                                                                                                                                                    {
                                                                                                                                                                        self::A(state).or_else(|state| { self::B(state) })
                                                                                                                                                                    }).and_then(|state|
                                                                                                                                                                {
                                                                                                                                                                    super::hidden::skip(state)
                                                                                                                                                                }).and_then(|state| { self::NEWLINE(state) })
                                                                                                                                                })
                                                                                                                                    })
                                                                                                                        })
                                                                                                            })
                                                                                                })
                                                                                    })
                                                                        })
                                                            }).and_then(|state|
                                                        {
                                                            super::hidden::skip(state)
                                                        }).and_then(|state| { self::EOI(state) })
                                        })
                            })
                }
                #[inline]
                #[allow(dead_code, non_snake_case, unused_variables)]
                pub fn EOI(
                    state: ::std::boxed::Box<::pest::ParserState<Rule>>,
                ) -> ::pest::ParseResult<::std::boxed::Box<::pest::ParserState<Rule>>>
                {
                    state.rule(Rule::EOI, |state| state.end_of_input())
                }
                #[inline]
                #[allow(dead_code, non_snake_case, unused_variables)]
                pub fn SOI(
                    state: ::std::boxed::Box<::pest::ParserState<Rule>>,
                ) -> ::pest::ParseResult<::std::boxed::Box<::pest::ParserState<Rule>>>
                {
                    state.start_of_input()
                }
                #[inline]
                #[allow(dead_code, non_snake_case, unused_variables)]
                pub fn NEWLINE(
                    state: ::std::boxed::Box<::pest::ParserState<Rule>>,
                ) -> ::pest::ParseResult<::std::boxed::Box<::pest::ParserState<Rule>>>
                {
                    state
                        .match_string("\n")
                        .or_else(|state| state.match_string("\r\n"))
                        .or_else(|state| state.match_string("\r"))
                }
            }
            pub use self::visible::*;
        }
        ::pest::state(input, |state| match rule {
            Rule::A => rules::A(state),
            Rule::B => rules::B(state),
            Rule::file => rules::file(state),
            Rule::EOI => rules::EOI(state),
        })
    }
}

fn main() {}
rustfmt 1.5.1-nightly (20ffea69 2022-08-11)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the report with the semi-reduced Rust example in this issue and measure rustfmt's formatting time. Compare the result with the reported roughly 80-second run; done means deeply indented generated code formats without the severe slowdown or hang.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
performance, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.