rust-lang / rustfmt

Format Rust code
https://rust-lang.github.io/rustfmt/
Apache License 2.0
6.01k stars 884 forks source link

Preserve blank line between `match` inner attrs and first arm #6005

Open dtolnay opened 9 months ago

dtolnay commented 9 months ago

Example real-world code, which I have manually formatted exactly as I would want; this is the input and desired output:

#![feature(non_exhaustive_omitted_patterns_lint)]

use syn::*;

pub fn attrs_mut(e: &mut Expr) -> Option<&mut Vec<Attribute>> {
    match e {
        #![deny(non_exhaustive_omitted_patterns)]

        Expr::Array(ExprArray { attrs, .. })
        | Expr::Assign(ExprAssign { attrs, .. })
        | Expr::Async(ExprAsync { attrs, .. })
        | Expr::Await(ExprAwait { attrs, .. })
        | Expr::Binary(ExprBinary { attrs, .. })
        | Expr::Block(ExprBlock { attrs, .. })
        | Expr::Break(ExprBreak { attrs, .. })
        | Expr::Call(ExprCall { attrs, .. })
        | Expr::Cast(ExprCast { attrs, .. })
        | Expr::Closure(ExprClosure { attrs, .. })
        | Expr::Const(ExprConst { attrs, .. })
        | Expr::Continue(ExprContinue { attrs, .. })
        | Expr::Field(ExprField { attrs, .. })
        | Expr::ForLoop(ExprForLoop { attrs, .. })
        | Expr::Group(ExprGroup { attrs, .. })
        | Expr::If(ExprIf { attrs, .. })
        | Expr::Index(ExprIndex { attrs, .. })
        | Expr::Infer(ExprInfer { attrs, .. })
        | Expr::Let(ExprLet { attrs, .. })
        | Expr::Lit(ExprLit { attrs, .. })
        | Expr::Loop(ExprLoop { attrs, .. })
        | Expr::Macro(ExprMacro { attrs, .. })
        | Expr::Match(ExprMatch { attrs, .. })
        | Expr::MethodCall(ExprMethodCall { attrs, .. })
        | Expr::Paren(ExprParen { attrs, .. })
        | Expr::Path(ExprPath { attrs, .. })
        | Expr::Range(ExprRange { attrs, .. })
        | Expr::Reference(ExprReference { attrs, .. })
        | Expr::Repeat(ExprRepeat { attrs, .. })
        | Expr::Return(ExprReturn { attrs, .. })
        | Expr::Struct(ExprStruct { attrs, .. })
        | Expr::Try(ExprTry { attrs, .. })
        | Expr::TryBlock(ExprTryBlock { attrs, .. })
        | Expr::Tuple(ExprTuple { attrs, .. })
        | Expr::Unary(ExprUnary { attrs, .. })
        | Expr::Unsafe(ExprUnsafe { attrs, .. })
        | Expr::While(ExprWhile { attrs, .. })
        | Expr::Yield(ExprYield { attrs, .. }) => Some(attrs),

        Expr::Verbatim(_) => None,
        _ => None,
    }
}

But, rustfmt applies the following diff, which is undesirable.

 pub fn attrs_mut(e: &mut Expr) -> Option<&mut Vec<Attribute>> {
     match e {
         #![deny(non_exhaustive_omitted_patterns)]
-
         Expr::Array(ExprArray { attrs, .. })
         | Expr::Assign(ExprAssign { attrs, .. })
         | Expr::Async(ExprAsync { attrs, .. })

Rustfmt's formatting makes it look as though the inner attr is an outer attr on the first arm.

pub fn attrs_mut(e: &mut Expr) -> Option<&mut Vec<Attribute>> {
    match e {
        #![deny(non_exhaustive_omitted_patterns)]
        Expr::Array(ExprArray { attrs, .. })
        | Expr::Assign(ExprAssign { attrs, .. })
        | Expr::Async(ExprAsync { attrs, .. })

Module-level inner attrs, like the #![feature(...)] above, are not affected. If rustfmt were removing blank line between module-level inner attrs and the first item, I think it's obvious that would be considered undesirable:

#![feature(non_exhaustive_omitted_patterns_lint)]
use syn::*;

pub fn attrs_mut(e: &mut Expr) -> Option<&mut Vec<Attribute>> {
    match e {...}
}
dtolnay commented 9 months ago

Amusingly, this is the reverse of https://github.com/rust-lang/rustfmt/issues/5309, where a blank line is preserved between outer attributes and the item. For outer attrs the blank line should not be preserved. For inner attrs (this issue), I want it to be preserved.

ytmimi commented 9 months ago

This is where we're rewriting the inner attributes: https://github.com/rust-lang/rustfmt/blob/85e21fabf437a2a9cbee73136a895f4440120b19/src/matches.rs#L101-L109

ding-young commented 7 months ago

@rustbot claim