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