Skip to main content

a9_prettyplease/
heuristics.rs

1use syn::{Expr, Item, Local, Pat, Stmt, 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
49fn classify_item_kind(item: &Item) -> ItemKind {
50    match item {
51        Item::Use(_) => ItemKind::Use,
52        Item::ExternCrate(_) => ItemKind::ExternCrate,
53        Item::Mod(_) => ItemKind::Mod,
54        Item::Const(_) => ItemKind::Const,
55        Item::Static(_) => ItemKind::Static,
56        Item::Type(_) => ItemKind::TypeAlias,
57        Item::Fn(_)
58        | Item::Struct(_)
59        | Item::Enum(_)
60        | Item::Union(_)
61        | Item::Trait(_)
62        | Item::TraitAlias(_)
63        | Item::Impl(_)
64        | Item::Macro(_) => ItemKind::Definition,
65        _ => ItemKind::Other,
66    }
67}
68
69pub fn should_blank_between_items(prev: &Item, next: &Item) -> bool {
70    let pk = classify_item_kind(prev);
71    let nk = classify_item_kind(next);
72
73    // Same lightweight kind clusters together
74    match (pk, nk) {
75        (ItemKind::Use, ItemKind::Use) => {
76            let prev_group = classify_use(match prev {
77                Item::Use(u) => u,
78                _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
79            });
80            let next_group = classify_use(match next {
81                Item::Use(u) => u,
82                _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
83            });
84            prev_group != next_group
85        }
86        (ItemKind::ExternCrate, ItemKind::ExternCrate) => false,
87        (ItemKind::Mod, ItemKind::Mod) => {
88            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(_)));
89            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(_)));
90            prev_pub != next_pub
91        }
92        (ItemKind::Const, ItemKind::Const) => false,
93        (ItemKind::Static, ItemKind::Static) => false,
94        (ItemKind::TypeAlias, ItemKind::TypeAlias) => false,
95        _ => true,
96    }
97}
98
99// ---------------------------------------------------------------------------
100// Statement weight classification
101// ---------------------------------------------------------------------------
102
103#[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)]
104enum StmtWeight {
105    Light,
106    Binding,
107    Medium,
108    Heavy,
109    Item,
110}
111
112fn expr_is_heavy(expr: &Expr) -> bool {
113    match expr {
114        Expr::If(_)
115        | Expr::Match(_)
116        | Expr::ForLoop(_)
117        | Expr::While(_)
118        | Expr::Loop(_)
119        | Expr::Block(_)
120        | Expr::Unsafe(_)
121        | Expr::TryBlock(_) => true,
122        Expr::Closure(c) => #[allow(non_exhaustive_omitted_patterns)] match *c.body {
    Expr::Block(_) => true,
    _ => false,
}matches!(*c.body, Expr::Block(_)),
123        Expr::Assign(a) => expr_is_heavy(&a.right),
124        _ => false,
125    }
126}
127
128fn pat_contains_mut(pat: &Pat) -> bool {
129    match pat {
130        Pat::Ident(p) => p.mutability.is_some(),
131        Pat::Reference(p) => p.mutability.is_some() || pat_contains_mut(&p.pat),
132        Pat::Struct(p) => p.fields.iter().any(|f| pat_contains_mut(&f.pat)),
133        Pat::Tuple(p) => p.elems.iter().any(pat_contains_mut),
134        Pat::TupleStruct(p) => p.elems.iter().any(pat_contains_mut),
135        Pat::Slice(p) => p.elems.iter().any(pat_contains_mut),
136        Pat::Or(p) => p.cases.iter().any(pat_contains_mut),
137        Pat::Type(p) => pat_contains_mut(&p.pat),
138        _ => false,
139    }
140}
141
142fn classify_local(local: &Local) -> StmtWeight {
143    if let Some(init) = &local.init {
144        if init.diverge.is_some() {
145            return StmtWeight::Heavy;
146        }
147
148        if expr_is_heavy(&init.expr) {
149            return StmtWeight::Heavy;
150        }
151    }
152    if pat_contains_mut(&local.pat) {
153        StmtWeight::Binding
154    } else {
155        StmtWeight::Light
156    }
157}
158
159// ---------------------------------------------------------------------------
160// Tracing / logging macro detection
161// ---------------------------------------------------------------------------
162
163#[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)]
164enum TracingLevel {
165    Trace,
166    Debug,
167    Info,
168    Warn,
169    Error,
170}
171
172fn is_tracing_macro(expr: &Expr) -> Option<TracingLevel> {
173    if let Expr::Macro(m) = expr {
174        let name = m.mac.path.segments.last()?.ident.to_string();
175        match name.as_str() {
176            "trace" => Some(TracingLevel::Trace),
177            "debug" => Some(TracingLevel::Debug),
178            "info" => Some(TracingLevel::Info),
179            "warn" => Some(TracingLevel::Warn),
180            "error" => Some(TracingLevel::Error),
181            _ => None,
182        }
183    } else {
184        None
185    }
186}
187
188fn stmt_expr(stmt: &Stmt) -> Option<&Expr> {
189    match stmt {
190        Stmt::Expr(expr, _) => Some(expr),
191        _ => None,
192    }
193}
194
195/// Returns Some(should_blank) if tracing attachment rules apply.
196fn tracing_blank_line(prev: &Stmt, next: &Stmt) -> Option<bool> {
197    // Check if prev is a tracing macro
198    if let Some(prev_expr) = stmt_expr(prev) {
199        if let Some(level) = is_tracing_macro(prev_expr) {
200            return Some(match level {
201                TracingLevel::Trace => false,
202                TracingLevel::Debug => true,
203                TracingLevel::Info | TracingLevel::Warn | TracingLevel::Error => true,
204            });
205        }
206    }
207
208    // Check if next is a tracing macro
209    if let Some(next_expr) = stmt_expr(next) {
210        if let Some(level) = is_tracing_macro(next_expr) {
211            return Some(match level {
212                TracingLevel::Trace => true,
213                TracingLevel::Debug => false,
214                TracingLevel::Info | TracingLevel::Warn | TracingLevel::Error => true,
215            });
216        }
217    }
218
219    None
220}
221
222// ---------------------------------------------------------------------------
223// Statement blank-line decisions
224// ---------------------------------------------------------------------------
225
226fn classify_weight(stmt: &Stmt) -> StmtWeight {
227    match stmt {
228        Stmt::Local(local) => classify_local(local),
229        Stmt::Expr(expr, _) => {
230            if expr_is_heavy(expr) {
231                StmtWeight::Heavy
232            } else {
233                StmtWeight::Medium
234            }
235        }
236        Stmt::Item(_) => StmtWeight::Item,
237        Stmt::Macro(_) => StmtWeight::Medium,
238    }
239}
240
241fn is_jump_stmt(stmt: &Stmt) -> bool {
242    #[allow(non_exhaustive_omitted_patterns)] match stmt {
    Stmt::Expr(Expr::Return(_) | Expr::Continue(_) | Expr::Break(_), _) =>
        true,
    _ => false,
}matches!(
243        stmt,
244        Stmt::Expr(
245            Expr::Return(_) | Expr::Continue(_) | Expr::Break(_),
246            _
247        )
248    )
249}
250
251fn is_let_else(stmt: &Stmt) -> bool {
252    if let Stmt::Local(local) = stmt {
253        if let Some(init) = &local.init {
254            return init.diverge.is_some();
255        }
256    }
257    false
258}
259
260fn should_blank_between_stmts(prev: &Stmt, next: &Stmt) -> bool {
261    // Tracing macro attachment takes priority
262    if let Some(decision) = tracing_blank_line(prev, next) {
263        return decision;
264    }
265
266    // return / continue / break always get breathing room before them
267    if is_jump_stmt(next) {
268        return true;
269    }
270
271    // let...else always gets a blank line before it
272    if is_let_else(next) {
273        return true;
274    }
275
276    let pw = classify_weight(prev);
277    let nw = classify_weight(next);
278
279    // Item stmts get separation
280    if nw == StmtWeight::Item || pw == StmtWeight::Item {
281        return true;
282    }
283
284    // Heavy constructs get breathing room
285    if pw == StmtWeight::Heavy || nw == StmtWeight::Heavy {
286        return true;
287    }
288
289    // Same weight clusters together
290    if pw == nw {
291        return false;
292    }
293
294    // Any weight transition
295    true
296}
297
298/// Returns a Vec of length `stmts.len()` where `result[i]` is true
299/// if a blank line should be inserted BEFORE `stmts[i]`.
300/// `result[0]` is always false (no blank line before the first statement).
301pub fn stmt_blank_lines(stmts: &[Stmt]) -> Vec<bool> {
302    let len = stmts.len();
303    let mut blanks = ::alloc::vec::from_elem(false, len)vec![false; len];
304    if len <= 1 {
305        return blanks;
306    }
307    for i in 1..len {
308        blanks[i] = should_blank_between_stmts(&stmts[i - 1], &stmts[i]);
309    }
310    
311    // Returning or last expr which is also implicit returning should be on a separate line 
312    // for multi-statement blocks.
313    if len > 1 {
314        if let syn::Stmt::Expr(_, None) = &stmts[len - 1] {
315            blanks[len - 1] = true;
316        }
317    }
318
319    blanks
320}