Skip to main content

a9_prettyplease/
heuristics.rs

1use syn::{Expr, Item, Local, Pat, Stmt, StmtMacro, UseTree, Visibility};
2
3// ---------------------------------------------------------------------------
4// Use-group classification (unchanged)
5// ---------------------------------------------------------------------------
6
7#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UseGroup {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                UseGroup::Std => "Std",
                UseGroup::External => "External",
                UseGroup::CrateLocal => "CrateLocal",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for UseGroup {
    #[inline]
    fn clone(&self) -> UseGroup { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for UseGroup { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for UseGroup {
    #[inline]
    fn eq(&self, other: &UseGroup) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for UseGroup {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq)]
8pub enum UseGroup {
9    Std,
10    External,
11    CrateLocal,
12}
13
14pub fn classify_use(item: &syn::ItemUse) -> UseGroup {
15    let root_ident = use_tree_root_ident(&item.tree);
16    match root_ident.as_deref() {
17        Some("std" | "alloc" | "core") => UseGroup::Std,
18        Some("crate" | "super" | "self") => UseGroup::CrateLocal,
19        _ => UseGroup::External,
20    }
21}
22
23fn use_tree_root_ident(tree: &UseTree) -> Option<String> {
24    match tree {
25        UseTree::Path(path) => Some(path.ident.to_string()),
26        UseTree::Name(name) => Some(name.ident.to_string()),
27        UseTree::Rename(rename) => Some(rename.ident.to_string()),
28        UseTree::Glob(_) => None,
29        UseTree::Group(group) => group.items.first().and_then(use_tree_root_ident),
30    }
31}
32
33// ---------------------------------------------------------------------------
34// Item-kind classification
35// ---------------------------------------------------------------------------
36
37#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ItemKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ItemKind::Use => "Use",
                ItemKind::ExternCrate => "ExternCrate",
                ItemKind::Mod => "Mod",
                ItemKind::Const => "Const",
                ItemKind::Static => "Static",
                ItemKind::TypeAlias => "TypeAlias",
                ItemKind::Definition => "Definition",
                ItemKind::Other => "Other",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ItemKind {
    #[inline]
    fn clone(&self) -> ItemKind { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ItemKind { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for ItemKind {
    #[inline]
    fn eq(&self, other: &ItemKind) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ItemKind {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq)]
38enum ItemKind {
39    Use,
40    ExternCrate,
41    Mod,
42    Const,
43    Static,
44    TypeAlias,
45    Definition,
46    Other,
47}
48
49/// Returns `true` if a static initialiser expression contains a closure or
50/// block at any depth.  Used to distinguish simple statics (`static FOO: &str
51/// = "foo";`) from heavyweight ones (`static BAR: LazyLock<…> =
52/// LazyLock::new(|| { … });`) so that the latter get blank-line separation.
53fn static_init_is_heavy(expr: &Expr) -> bool {
54    match expr {
55        Expr::Closure(_) | Expr::Block(_) | Expr::Async(_) | Expr::Unsafe(_) => true,
56        Expr::Call(c) => c.args.iter().any(static_init_is_heavy),
57        Expr::MethodCall(mc) => {
58            static_init_is_heavy(&mc.receiver) || mc.args.iter().any(static_init_is_heavy)
59        }
60        _ => false,
61    }
62}
63
64fn classify_item_kind(item: &Item) -> ItemKind {
65    match item {
66        Item::Use(_) => ItemKind::Use,
67        Item::ExternCrate(_) => ItemKind::ExternCrate,
68        Item::Mod(_) => ItemKind::Mod,
69        Item::Const(_) => ItemKind::Const,
70        Item::Static(s) => {
71            if static_init_is_heavy(&s.expr) {
72                ItemKind::Definition
73            } else {
74                ItemKind::Static
75            }
76        }
77        Item::Type(_) => ItemKind::TypeAlias,
78        Item::Fn(_)
79        | Item::Struct(_)
80        | Item::Enum(_)
81        | Item::Union(_)
82        | Item::Trait(_)
83        | Item::TraitAlias(_)
84        | Item::Impl(_)
85        | Item::Macro(_) => ItemKind::Definition,
86        _ => ItemKind::Other,
87    }
88}
89
90pub fn should_blank_between_items(prev: &Item, next: &Item) -> bool {
91    let pk = classify_item_kind(prev);
92    let nk = classify_item_kind(next);
93
94    // Same lightweight kind clusters together
95    match (pk, nk) {
96        (ItemKind::Use, ItemKind::Use) => {
97            let prev_group = classify_use(match prev {
98                Item::Use(u) => u,
99                _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
100            });
101            let next_group = classify_use(match next {
102                Item::Use(u) => u,
103                _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
104            });
105            prev_group != next_group
106        }
107        (ItemKind::ExternCrate, ItemKind::ExternCrate) => false,
108        (ItemKind::Mod, ItemKind::Mod) => {
109            let prev_pub = #[allow(non_exhaustive_omitted_patterns)] match prev {
    Item::Mod(m) if
        #[allow(non_exhaustive_omitted_patterns)] match m.vis {
            Visibility::Public(_) => true,
            _ => false,
        } => true,
    _ => false,
}matches!(prev, Item::Mod(m) if matches!(m.vis, Visibility::Public(_)));
110            let next_pub = #[allow(non_exhaustive_omitted_patterns)] match next {
    Item::Mod(m) if
        #[allow(non_exhaustive_omitted_patterns)] match m.vis {
            Visibility::Public(_) => true,
            _ => false,
        } => true,
    _ => false,
}matches!(next, Item::Mod(m) if matches!(m.vis, Visibility::Public(_)));
111            prev_pub != next_pub
112        }
113        (ItemKind::Const, ItemKind::Const) => false,
114        (ItemKind::Static, ItemKind::Static) => false,
115        (ItemKind::TypeAlias, ItemKind::TypeAlias) => false,
116        _ => true,
117    }
118}
119
120// ---------------------------------------------------------------------------
121// Statement weight classification
122// ---------------------------------------------------------------------------
123
124#[derive(#[automatically_derived]
impl ::core::fmt::Debug for StmtWeight {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                StmtWeight::Light => "Light",
                StmtWeight::Binding => "Binding",
                StmtWeight::Medium => "Medium",
                StmtWeight::Heavy => "Heavy",
                StmtWeight::Item => "Item",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for StmtWeight {
    #[inline]
    fn clone(&self) -> StmtWeight { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for StmtWeight { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for StmtWeight {
    #[inline]
    fn eq(&self, other: &StmtWeight) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for StmtWeight {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq)]
125enum StmtWeight {
126    Light,
127    Binding,
128    Medium,
129    Heavy,
130    Item,
131}
132
133fn expr_is_heavy(expr: &Expr) -> bool {
134    match expr {
135        Expr::If(_)
136        | Expr::Match(_)
137        | Expr::ForLoop(_)
138        | Expr::While(_)
139        | Expr::Loop(_)
140        | Expr::Block(_)
141        | Expr::Unsafe(_)
142        | Expr::TryBlock(_) => true,
143        Expr::Closure(c) => #[allow(non_exhaustive_omitted_patterns)] match *c.body {
    Expr::Block(_) => true,
    _ => false,
}matches!(*c.body, Expr::Block(_)),
144        Expr::Assign(a) => expr_is_heavy(&a.right),
145        _ => false,
146    }
147}
148
149fn pat_contains_mut(pat: &Pat) -> bool {
150    match pat {
151        Pat::Ident(p) => p.mutability.is_some(),
152        Pat::Reference(p) => p.mutability.is_some() || pat_contains_mut(&p.pat),
153        Pat::Struct(p) => p.fields.iter().any(|f| pat_contains_mut(&f.pat)),
154        Pat::Tuple(p) => p.elems.iter().any(pat_contains_mut),
155        Pat::TupleStruct(p) => p.elems.iter().any(pat_contains_mut),
156        Pat::Slice(p) => p.elems.iter().any(pat_contains_mut),
157        Pat::Or(p) => p.cases.iter().any(pat_contains_mut),
158        Pat::Type(p) => pat_contains_mut(&p.pat),
159        _ => false,
160    }
161}
162
163fn classify_local(local: &Local) -> StmtWeight {
164    if let Some(init) = &local.init {
165        if init.diverge.is_some() {
166            return StmtWeight::Heavy;
167        }
168
169        if expr_is_heavy(&init.expr) {
170            return StmtWeight::Heavy;
171        }
172    }
173    if pat_contains_mut(&local.pat) {
174        StmtWeight::Binding
175    } else {
176        StmtWeight::Light
177    }
178}
179
180// ---------------------------------------------------------------------------
181// Tracing / logging macro detection
182// ---------------------------------------------------------------------------
183
184#[derive(#[automatically_derived]
impl ::core::fmt::Debug for TracingLevel {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                TracingLevel::Trace => "Trace",
                TracingLevel::Debug => "Debug",
                TracingLevel::Info => "Info",
                TracingLevel::Warn => "Warn",
                TracingLevel::Error => "Error",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for TracingLevel {
    #[inline]
    fn clone(&self) -> TracingLevel { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for TracingLevel { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for TracingLevel {
    #[inline]
    fn eq(&self, other: &TracingLevel) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for TracingLevel {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq)]
185enum TracingLevel {
186    Trace,
187    Debug,
188    Info,
189    Warn,
190    Error,
191}
192
193fn tracing_level_from_macro(mac: &syn::Macro) -> Option<TracingLevel> {
194    let name = mac.path.segments.last()?.ident.to_string();
195    match name.as_str() {
196        "trace" => Some(TracingLevel::Trace),
197        "debug" => Some(TracingLevel::Debug),
198        "info" => Some(TracingLevel::Info),
199        "warn" => Some(TracingLevel::Warn),
200        "error" => Some(TracingLevel::Error),
201        _ => None,
202    }
203}
204
205/// Detects tracing macros in both `Stmt::Expr(Expr::Macro, _)` and `Stmt::Macro` variants.
206fn stmt_is_tracing(stmt: &Stmt) -> Option<TracingLevel> {
207    match stmt {
208        Stmt::Expr(Expr::Macro(m), _) => tracing_level_from_macro(&m.mac),
209        Stmt::Macro(StmtMacro { mac, .. }) => tracing_level_from_macro(mac),
210        _ => None,
211    }
212}
213
214/// Returns Some(should_blank) if tracing attachment rules apply.
215fn tracing_blank_line(prev: &Stmt, next: &Stmt) -> Option<bool> {
216    // Check if prev is a tracing macro
217    if let Some(level) = stmt_is_tracing(prev) {
218        return Some(match level {
219            TracingLevel::Trace => false,
220            TracingLevel::Debug => true,
221            TracingLevel::Info | TracingLevel::Warn | TracingLevel::Error => true,
222        });
223    }
224
225    // Check if next is a tracing macro
226    if let Some(level) = stmt_is_tracing(next) {
227        return Some(match level {
228            TracingLevel::Trace => true,
229            TracingLevel::Debug => false,
230            TracingLevel::Info | TracingLevel::Warn | TracingLevel::Error => true,
231        });
232    }
233
234    None
235}
236
237// ---------------------------------------------------------------------------
238// Statement blank-line decisions
239// ---------------------------------------------------------------------------
240
241fn classify_weight(stmt: &Stmt) -> StmtWeight {
242    match stmt {
243        Stmt::Local(local) => classify_local(local),
244        Stmt::Expr(expr, _) => {
245            if expr_is_heavy(expr) {
246                StmtWeight::Heavy
247            } else {
248                StmtWeight::Medium
249            }
250        }
251        Stmt::Item(_) => StmtWeight::Item,
252        Stmt::Macro(_) => StmtWeight::Medium,
253    }
254}
255
256fn is_jump_stmt(stmt: &Stmt) -> bool {
257    #[allow(non_exhaustive_omitted_patterns)] match stmt {
    Stmt::Expr(Expr::Return(_) | Expr::Continue(_) | Expr::Break(_), _) =>
        true,
    _ => false,
}matches!(
258        stmt,
259        Stmt::Expr(
260            Expr::Return(_) | Expr::Continue(_) | Expr::Break(_),
261            _
262        )
263    )
264}
265
266fn is_let_else(stmt: &Stmt) -> bool {
267    if let Stmt::Local(local) = stmt {
268        if let Some(init) = &local.init {
269            return init.diverge.is_some();
270        }
271    }
272    false
273}
274
275fn should_blank_between_stmts(prev: &Stmt, next: &Stmt) -> bool {
276    // Tracing macro attachment takes priority
277    if let Some(decision) = tracing_blank_line(prev, next) {
278        return decision;
279    }
280
281    // return / continue / break always get breathing room before them
282    if is_jump_stmt(next) {
283        return true;
284    }
285
286    // let...else always gets a blank line before it
287    if is_let_else(next) {
288        return true;
289    }
290
291    let pw = classify_weight(prev);
292    let nw = classify_weight(next);
293
294    // Item stmts get separation
295    if nw == StmtWeight::Item || pw == StmtWeight::Item {
296        return true;
297    }
298
299    // Heavy constructs get breathing room
300    if pw == StmtWeight::Heavy || nw == StmtWeight::Heavy {
301        return true;
302    }
303
304    // Same weight clusters together
305    if pw == nw {
306        return false;
307    }
308
309    // Any weight transition
310    true
311}
312
313/// Returns a Vec of length `stmts.len()` where `result[i]` is true
314/// if a blank line should be inserted BEFORE `stmts[i]`.
315/// `result[0]` is always false (no blank line before the first statement).
316pub fn stmt_blank_lines(stmts: &[Stmt]) -> Vec<bool> {
317    let len = stmts.len();
318    let mut blanks = ::alloc::vec::from_elem(false, len)vec![false; len];
319    if len <= 1 {
320        return blanks;
321    }
322    for i in 1..len {
323        blanks[i] = should_blank_between_stmts(&stmts[i - 1], &stmts[i]);
324    }
325    
326    // Returning or last expr which is also implicit returning should be on a separate line 
327    // for multi-statement blocks.
328    if len > 1 {
329        if let syn::Stmt::Expr(_, None) = &stmts[len - 1] {
330            blanks[len - 1] = true;
331        }
332    }
333
334    blanks
335}