Skip to main content

rust_analyzer/cli/
analysis_stats.rs

1//! Fully type-check project and print various stats, like the number of type
2//! errors.
3
4use std::{
5    cell::LazyCell,
6    env, fmt,
7    ops::AddAssign,
8    panic::{AssertUnwindSafe, catch_unwind},
9    time::{SystemTime, UNIX_EPOCH},
10};
11
12use cfg::{CfgAtom, CfgDiff};
13use hir::{
14    Adt, AssocItem, Crate, DefWithBody, FindPathConfig, GenericDef, HasCrate, HasSource,
15    HirDisplay, ModuleDef, Name, Variant, crate_lang_items,
16    db::{DefDatabase, ExpandDatabase, HirDatabase},
17    next_solver::{DbInterner, GenericArgs},
18};
19use hir_def::{
20    DefWithBodyId, ExpressionStoreOwnerId, GenericDefId, SyntheticSyntax,
21    expr_store::{Body, BodySourceMap, ExpressionStore},
22    hir::{ExprId, PatId, generics::GenericParams},
23};
24use hir_ty::InferenceResult;
25use ide::{
26    Analysis, AnalysisHost, AnnotationConfig, DiagnosticsConfig, Edition, InlayFieldsToResolve,
27    InlayHintsConfig, LineCol, RaFixtureConfig, RootDatabase,
28};
29use ide_db::{
30    EditionedFileId, SnippetCap,
31    base_db::{SourceDatabase, salsa::Database},
32    line_index,
33};
34use itertools::Itertools;
35use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace};
36use oorandom::Rand32;
37use profile::StopWatch;
38use project_model::{CargoConfig, CfgOverrides, ProjectManifest, ProjectWorkspace, RustLibSource};
39use rayon::prelude::*;
40use rustc_hash::{FxHashMap, FxHashSet};
41use rustc_type_ir::inherent::Ty as _;
42use syntax::AstNode;
43use vfs::{AbsPathBuf, Vfs, VfsPath};
44
45use crate::cli::{
46    Verbosity,
47    flags::{self, OutputFormat},
48    full_name_of_item, print_memory_usage,
49    progress_report::ProgressReport,
50    report_metric,
51};
52
53impl flags::AnalysisStats {
54    pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> {
55        let mut rng = {
56            let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
57            Rand32::new(seed)
58        };
59
60        let cargo_config = CargoConfig {
61            sysroot: match self.no_sysroot {
62                true => None,
63                false => Some(RustLibSource::Discover),
64            },
65            all_targets: true,
66            set_test: !self.no_test,
67            cfg_overrides: CfgOverrides {
68                global: CfgDiff::new(vec![CfgAtom::Flag(hir::sym::miri)], vec![]),
69                selective: Default::default(),
70            },
71            ..Default::default()
72        };
73        let no_progress = &|_| ();
74
75        let mut db_load_sw = self.stop_watch();
76
77        let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(&self.path));
78        let manifest = ProjectManifest::discover_single(&path)?;
79
80        let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
81        let metadata_time = db_load_sw.elapsed();
82        let load_cargo_config = LoadCargoConfig {
83            load_out_dirs_from_check: !self.disable_build_scripts,
84            with_proc_macro_server: if self.disable_proc_macros {
85                ProcMacroServerChoice::None
86            } else {
87                match self.proc_macro_srv {
88                    Some(ref path) => {
89                        let path = vfs::AbsPathBuf::assert_utf8(path.to_owned());
90                        ProcMacroServerChoice::Explicit(path)
91                    }
92                    None => ProcMacroServerChoice::Sysroot,
93                }
94            },
95            prefill_caches: false,
96            num_worker_threads: 1,
97            proc_macro_processes: 1,
98        };
99
100        let build_scripts_time = if self.disable_build_scripts {
101            None
102        } else {
103            let mut build_scripts_sw = self.stop_watch();
104            let bs = workspace.run_build_scripts(&cargo_config, no_progress)?;
105            workspace.set_build_scripts(bs);
106            Some(build_scripts_sw.elapsed())
107        };
108
109        let (db, vfs, _proc_macro) =
110            load_workspace(workspace.clone(), &cargo_config.extra_env, &load_cargo_config)?;
111        eprint!("{:<20} {}", "Database loaded:", db_load_sw.elapsed());
112        eprint!(" (metadata {metadata_time}");
113        if let Some(build_scripts_time) = build_scripts_time {
114            eprint!("; build {build_scripts_time}");
115        }
116        eprintln!(")");
117
118        let mut host = AnalysisHost::with_database(db);
119        let db = host.raw_database();
120
121        let mut analysis_sw = self.stop_watch();
122
123        let mut krates = Crate::all(db);
124        if self.randomize {
125            shuffle(&mut rng, &mut krates);
126        }
127
128        let mut item_tree_sw = self.stop_watch();
129        let source_roots = krates
130            .iter()
131            .cloned()
132            .map(|krate| (db.file_source_root(krate.root_file(db)).source_root_id(db), krate))
133            .unique_by(|(source_root_id, _)| *source_root_id);
134
135        let mut dep_loc = 0;
136        let mut workspace_loc = 0;
137        let mut dep_item_trees = 0;
138        let mut workspace_item_trees = 0;
139
140        let mut workspace_item_stats = PrettyItemStats::default();
141        let mut dep_item_stats = PrettyItemStats::default();
142
143        for (source_root_id, krate) in source_roots {
144            let source_root = db.source_root(source_root_id).source_root(db);
145            for file_id in source_root.iter() {
146                if let Some(p) = source_root.path_for_file(&file_id)
147                    && let Some((_, Some("rs"))) = p.name_and_extension()
148                {
149                    // measure workspace/project code
150                    if !source_root.is_library || self.with_deps {
151                        let length = db.file_text(file_id).text(db).lines().count();
152                        let item_stats = db
153                            .file_item_tree(
154                                EditionedFileId::current_edition(db, file_id).into(),
155                                krate.into(),
156                            )
157                            .item_tree_stats()
158                            .into();
159
160                        workspace_loc += length;
161                        workspace_item_trees += 1;
162                        workspace_item_stats += item_stats;
163                    } else {
164                        let length = db.file_text(file_id).text(db).lines().count();
165                        let item_stats = db
166                            .file_item_tree(
167                                EditionedFileId::current_edition(db, file_id).into(),
168                                krate.into(),
169                            )
170                            .item_tree_stats()
171                            .into();
172
173                        dep_loc += length;
174                        dep_item_trees += 1;
175                        dep_item_stats += item_stats;
176                    }
177                }
178            }
179        }
180        eprintln!("  item trees: {workspace_item_trees}");
181        let item_tree_time = item_tree_sw.elapsed();
182
183        eprintln!(
184            "  dependency lines of code: {}, item trees: {}",
185            UsizeWithUnderscore(dep_loc),
186            UsizeWithUnderscore(dep_item_trees),
187        );
188        eprintln!("  dependency item stats: {dep_item_stats}");
189
190        // FIXME(salsa-transition): bring back stats for ParseQuery (file size)
191        // and ParseMacroExpansionQuery (macro expansion "file") size whenever we implement
192        // Salsa's memory usage tracking works with tracked functions.
193
194        // let mut total_file_size = Bytes::default();
195        // for e in ide_db::base_db::ParseQuery.in_db(db).entries::<Vec<_>>() {
196        //     total_file_size += syntax_len(db.parse(e.key).syntax_node())
197        // }
198
199        // let mut total_macro_file_size = Bytes::default();
200        // for e in hir::db::ParseMacroExpansionQuery.in_db(db).entries::<Vec<_>>() {
201        //     let val = db.parse_macro_expansion(e.key).value.0;
202        //     total_macro_file_size += syntax_len(val.syntax_node())
203        // }
204        // eprintln!("source files: {total_file_size}, macro files: {total_macro_file_size}");
205
206        eprintln!("{:<20} {}", "Item Tree Collection:", item_tree_time);
207        report_metric("item tree time", item_tree_time.time.as_millis() as u64, "ms");
208        eprintln!("  Total Statistics:");
209
210        let mut crate_def_map_sw = self.stop_watch();
211        let mut num_crates = 0;
212        let mut visited_modules = FxHashSet::default();
213        let mut visit_queue = Vec::new();
214        for &krate in &krates {
215            let module = krate.root_module(db);
216            let file_id = module.definition_source_file_id(db);
217            let file_id = file_id.original_file(db);
218
219            let source_root = db.file_source_root(file_id.file_id(db)).source_root_id(db);
220            let source_root = db.source_root(source_root).source_root(db);
221            if !source_root.is_library || self.with_deps {
222                num_crates += 1;
223                visit_queue.push(module);
224            }
225        }
226
227        if self.randomize {
228            shuffle(&mut rng, &mut visit_queue);
229        }
230
231        eprint!("    crates: {num_crates}");
232        let mut num_decls = 0;
233        let mut bodies = Vec::new();
234        let mut signatures = Vec::new();
235        let mut variants = Vec::new();
236        let mut adts = Vec::new();
237        let mut file_ids = Vec::new();
238
239        let mut num_traits = 0;
240        let mut num_macro_rules_macros = 0;
241        let mut num_proc_macros = 0;
242
243        while let Some(module) = visit_queue.pop() {
244            if visited_modules.insert(module) {
245                file_ids.extend(module.as_source_file_id(db));
246                visit_queue.extend(module.children(db));
247
248                for decl in module.declarations(db) {
249                    num_decls += 1;
250                    match decl {
251                        ModuleDef::Function(f) => bodies.push(DefWithBody::from(f)),
252                        ModuleDef::Adt(a) => {
253                            match a {
254                                Adt::Enum(e) => {
255                                    for v in e.variants(db) {
256                                        bodies.push(DefWithBody::from(v));
257                                        variants.push(Variant::EnumVariant(v));
258                                    }
259                                }
260                                Adt::Struct(it) => variants.push(Variant::Struct(it)),
261                                Adt::Union(it) => variants.push(Variant::Union(it)),
262                            }
263                            adts.push(a)
264                        }
265                        ModuleDef::Const(c) => {
266                            bodies.push(DefWithBody::from(c));
267                        }
268                        ModuleDef::Static(s) => bodies.push(DefWithBody::from(s)),
269                        ModuleDef::Trait(_) => num_traits += 1,
270                        ModuleDef::Macro(m) => match m.kind(db) {
271                            hir::MacroKind::Declarative => num_macro_rules_macros += 1,
272                            hir::MacroKind::Derive
273                            | hir::MacroKind::Attr
274                            | hir::MacroKind::ProcMacro => num_proc_macros += 1,
275                            _ => (),
276                        },
277                        _ => (),
278                    };
279                    if let Some(g) = decl.as_generic_def() {
280                        signatures.push(g);
281                    }
282                }
283
284                for impl_def in module.impl_defs(db) {
285                    signatures.push(impl_def.into());
286                    for item in impl_def.items(db) {
287                        num_decls += 1;
288                        match item {
289                            AssocItem::Function(f) => {
290                                bodies.push(DefWithBody::from(f));
291                                signatures.push(f.into())
292                            }
293                            AssocItem::Const(c) => {
294                                bodies.push(DefWithBody::from(c));
295                                signatures.push(c.into());
296                            }
297                            AssocItem::TypeAlias(t) => signatures.push(t.into()),
298                        }
299                    }
300                }
301            }
302        }
303        eprintln!(
304            ", mods: {}, decls: {num_decls}, bodies: {}, adts: {}, consts: {}, signatures: {}, variants: {}",
305            visited_modules.len(),
306            bodies.len(),
307            adts.len(),
308            bodies
309                .iter()
310                .filter(|it| matches!(it, DefWithBody::Const(_) | DefWithBody::Static(_)))
311                .count(),
312            signatures.len(),
313            variants.len()
314        );
315
316        eprintln!("  Workspace:");
317        eprintln!(
318            "    traits: {num_traits}, macro_rules macros: {num_macro_rules_macros}, proc_macros: {num_proc_macros}"
319        );
320        eprintln!(
321            "    lines of code: {}, item trees: {}",
322            UsizeWithUnderscore(workspace_loc),
323            UsizeWithUnderscore(workspace_item_trees),
324        );
325        eprintln!("    usages: {workspace_item_stats}");
326
327        eprintln!("  Dependencies:");
328        eprintln!(
329            "    lines of code: {}, item trees: {}",
330            UsizeWithUnderscore(dep_loc),
331            UsizeWithUnderscore(dep_item_trees),
332        );
333        eprintln!("    declarations: {dep_item_stats}");
334
335        let crate_def_map_time = crate_def_map_sw.elapsed();
336        eprintln!("{:<20} {}", "Item Collection:", crate_def_map_time);
337        report_metric("crate def map time", crate_def_map_time.time.as_millis() as u64, "ms");
338
339        if self.randomize {
340            shuffle(&mut rng, &mut bodies);
341        }
342
343        hir::attach_db(db, || {
344            if !self.skip_lang_items {
345                self.run_lang_items(db, &krates, verbosity);
346            }
347
348            if !self.skip_lowering {
349                self.run_body_lowering(db, &vfs, &bodies, &signatures, &variants, verbosity);
350            }
351
352            if !self.skip_inference {
353                self.run_inference(db, &vfs, &bodies, &signatures, &variants, verbosity);
354            }
355
356            if !self.skip_mir_stats {
357                self.run_mir_lowering(db, &bodies, &signatures, &variants, verbosity);
358            }
359
360            if !self.skip_data_layout {
361                self.run_data_layout(db, &adts, verbosity);
362            }
363
364            if !self.skip_const_eval {
365                self.run_const_eval(db, &bodies, &signatures, &variants, verbosity);
366            }
367        });
368
369        file_ids.sort();
370        file_ids.dedup();
371
372        if self.run_all_ide_things {
373            self.run_ide_things(host.analysis(), &file_ids, db, &vfs, verbosity);
374        }
375
376        if self.run_term_search {
377            self.run_term_search(&workspace, db, &vfs, &file_ids, verbosity);
378        }
379
380        let db = host.raw_database_mut();
381        db.trigger_lru_eviction();
382        hir::clear_tls_solver_cache();
383        unsafe { hir::collect_ty_garbage() };
384
385        let total_span = analysis_sw.elapsed();
386        eprintln!("{:<20} {total_span}", "Total:");
387        report_metric("total time", total_span.time.as_millis() as u64, "ms");
388        if let Some(instructions) = total_span.instructions {
389            report_metric("total instructions", instructions, "#instr");
390        }
391        report_metric("total memory", total_span.memory.allocated.megabytes() as u64, "MB");
392
393        if verbosity.is_verbose() {
394            print_memory_usage(host, vfs);
395        }
396
397        Ok(())
398    }
399
400    fn run_data_layout(&self, db: &RootDatabase, adts: &[hir::Adt], verbosity: Verbosity) {
401        let mut sw = self.stop_watch();
402        let mut all = 0;
403        let mut fail = 0;
404        for &a in adts {
405            let interner = DbInterner::new_no_crate(db);
406            let generic_params = GenericParams::of(db, a.into());
407            if generic_params.iter_type_or_consts().next().is_some()
408                || generic_params.iter_lt().next().is_some()
409            {
410                // Data types with generics don't have layout.
411                continue;
412            }
413            all += 1;
414            let Err(e) = db.layout_of_adt(
415                hir_def::AdtId::from(a),
416                GenericArgs::empty(interner).store(),
417                hir_ty::ParamEnvAndCrate {
418                    param_env: db.trait_environment(GenericDefId::from(a).into()),
419                    krate: a.krate(db).into(),
420                }
421                .store(),
422            ) else {
423                continue;
424            };
425            if verbosity.is_spammy() {
426                let full_name = full_name_of_item(db, a.module(db), a.name(db));
427                println!("Data layout for {full_name} failed due {e:?}");
428            }
429            fail += 1;
430        }
431        let data_layout_time = sw.elapsed();
432        eprintln!("{:<20} {}", "Data layouts:", data_layout_time);
433        eprintln!("Failed data layouts: {fail} ({}%)", percentage(fail, all));
434        report_metric("failed data layouts", fail, "#");
435        report_metric("data layout time", data_layout_time.time.as_millis() as u64, "ms");
436    }
437
438    fn run_const_eval(
439        &self,
440        db: &RootDatabase,
441        bodies: &[DefWithBody],
442        _signatures: &[GenericDef],
443        _variants: &[Variant],
444        verbosity: Verbosity,
445    ) {
446        let len = bodies
447            .iter()
448            .filter(|body| matches!(body, DefWithBody::Const(_) | DefWithBody::Static(_)))
449            .count();
450        let mut bar = match verbosity {
451            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
452            _ if self.parallel || self.output.is_some() => ProgressReport::hidden(),
453            _ => ProgressReport::new(len),
454        };
455
456        let mut sw = self.stop_watch();
457        let mut all = 0;
458        let mut fail = 0;
459        for &b in bodies {
460            bar.set_message(move || {
461                format!("const eval: {}", full_name(db, || b.name(db), b.module(db)))
462            });
463            let res = match b {
464                DefWithBody::Const(c) => c.eval(db),
465                DefWithBody::Static(s) => s.eval(db),
466                _ => continue,
467            };
468            bar.inc(1);
469            all += 1;
470            let Err(error) = res else {
471                continue;
472            };
473            if verbosity.is_spammy() {
474                let full_name =
475                    full_name_of_item(db, b.module(db), b.name(db).unwrap_or(Name::missing()));
476                bar.println(format!("Const eval for {full_name} failed due {error:?}"));
477            }
478            fail += 1;
479        }
480        bar.finish_and_clear();
481        let const_eval_time = sw.elapsed();
482        eprintln!("{:<20} {}", "Const evaluation:", const_eval_time);
483        eprintln!("Failed const evals: {fail} ({}%)", percentage(fail, all));
484        report_metric("failed const evals", fail, "#");
485        report_metric("const eval time", const_eval_time.time.as_millis() as u64, "ms");
486    }
487
488    /// Invariant: `file_ids` must be sorted and deduped before passing into here
489    fn run_term_search(
490        &self,
491        ws: &ProjectWorkspace,
492        db: &RootDatabase,
493        vfs: &Vfs,
494        file_ids: &[EditionedFileId],
495        verbosity: Verbosity,
496    ) {
497        let cargo_config = CargoConfig {
498            sysroot: match self.no_sysroot {
499                true => None,
500                false => Some(RustLibSource::Discover),
501            },
502            all_targets: true,
503            ..Default::default()
504        };
505
506        let mut bar = match verbosity {
507            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
508            _ if self.parallel || self.output.is_some() => ProgressReport::hidden(),
509            _ => ProgressReport::new(file_ids.len()),
510        };
511
512        #[derive(Debug, Default)]
513        struct Acc {
514            tail_expr_syntax_hits: u64,
515            tail_expr_no_term: u64,
516            total_tail_exprs: u64,
517            error_codes: FxHashMap<String, u32>,
518            syntax_errors: u32,
519        }
520
521        let mut acc: Acc = Default::default();
522        bar.tick();
523        let mut sw = self.stop_watch();
524
525        for &file_id in file_ids {
526            let file_id = file_id.span_file_id(db);
527            let sema = hir::Semantics::new(db);
528            let display_target = match sema.first_crate(file_id.file_id()) {
529                Some(krate) => krate.to_display_target(sema.db),
530                None => continue,
531            };
532
533            let parse = sema.parse_guess_edition(file_id.into());
534            let file_txt = db.file_text(file_id.into());
535            let path = vfs.file_path(file_id.into()).as_path().unwrap();
536
537            for node in parse.syntax().descendants() {
538                let expr = match syntax::ast::Expr::cast(node.clone()) {
539                    Some(it) => it,
540                    None => continue,
541                };
542                let block = match syntax::ast::BlockExpr::cast(expr.syntax().clone()) {
543                    Some(it) => it,
544                    None => continue,
545                };
546                let target_ty = match sema.type_of_expr(&expr) {
547                    Some(it) => it.adjusted(),
548                    None => continue, // Failed to infer type
549                };
550
551                let expected_tail = match block.tail_expr() {
552                    Some(it) => it,
553                    None => continue,
554                };
555
556                if expected_tail.is_block_like() {
557                    continue;
558                }
559
560                let range = sema.original_range(expected_tail.syntax()).range;
561                let original_text: String = db
562                    .file_text(file_id.into())
563                    .text(db)
564                    .chars()
565                    .skip(usize::from(range.start()))
566                    .take(usize::from(range.end()) - usize::from(range.start()))
567                    .collect();
568
569                let scope = match sema.scope(expected_tail.syntax()) {
570                    Some(it) => it,
571                    None => continue,
572                };
573
574                let ctx = hir::term_search::TermSearchCtx {
575                    sema: &sema,
576                    scope: &scope,
577                    goal: target_ty,
578                    config: hir::term_search::TermSearchConfig {
579                        enable_borrowcheck: true,
580                        ..Default::default()
581                    },
582                };
583                let found_terms = hir::term_search::term_search(&ctx);
584
585                if found_terms.is_empty() {
586                    acc.tail_expr_no_term += 1;
587                    acc.total_tail_exprs += 1;
588                    // println!("\n{original_text}\n");
589                    continue;
590                };
591
592                fn drop_whitespace(s: &str) -> String {
593                    s.chars().filter(|c| !parser::is_rust_whitespace(*c)).collect()
594                }
595
596                let todo = syntax::ast::make::ext::expr_todo().to_string();
597                let mut formatter = |_: &hir::Type<'_>| todo.clone();
598                let mut syntax_hit_found = false;
599                for term in found_terms {
600                    let generated = term
601                        .gen_source_code(
602                            &scope,
603                            &mut formatter,
604                            FindPathConfig {
605                                prefer_no_std: false,
606                                prefer_prelude: true,
607                                prefer_absolute: false,
608                                allow_unstable: true,
609                            },
610                            display_target,
611                        )
612                        .unwrap();
613                    syntax_hit_found |=
614                        drop_whitespace(&original_text) == drop_whitespace(&generated);
615
616                    // Validate if type-checks
617                    let mut txt = file_txt.text(db).to_string();
618
619                    let edit = ide::TextEdit::replace(range, generated.clone());
620                    edit.apply(&mut txt);
621
622                    if self.validate_term_search {
623                        std::fs::write(path, txt).unwrap();
624
625                        let res = ws.run_build_scripts(&cargo_config, &|_| ()).unwrap();
626                        if let Some(err) = res.error()
627                            && err.contains("error: could not compile")
628                        {
629                            if let Some(mut err_idx) = err.find("error[E") {
630                                err_idx += 7;
631                                let err_code = &err[err_idx..err_idx + 4];
632                                match err_code {
633                                    "0282" | "0283" => continue, // Byproduct of testing method
634                                    "0277" | "0308" if generated.contains(&todo) => continue, // See https://github.com/rust-lang/rust/issues/69882
635                                    // FIXME: In some rare cases `AssocItem::container_or_implemented_trait` returns `None` for trait methods.
636                                    // Generated code is valid in case traits are imported
637                                    "0599"
638                                        if err.contains(
639                                            "the following trait is implemented but not in scope",
640                                        ) =>
641                                    {
642                                        continue;
643                                    }
644                                    _ => (),
645                                }
646                                bar.println(err);
647                                bar.println(generated);
648                                acc.error_codes
649                                    .entry(err_code.to_owned())
650                                    .and_modify(|n| *n += 1)
651                                    .or_insert(1);
652                            } else {
653                                acc.syntax_errors += 1;
654                                bar.println(format!("Syntax error: \n{err}"));
655                            }
656                        }
657                    }
658                }
659
660                if syntax_hit_found {
661                    acc.tail_expr_syntax_hits += 1;
662                }
663                acc.total_tail_exprs += 1;
664
665                let msg = move || {
666                    format!(
667                        "processing: {:<50}",
668                        drop_whitespace(&original_text).chars().take(50).collect::<String>()
669                    )
670                };
671                if verbosity.is_spammy() {
672                    bar.println(msg());
673                }
674                bar.set_message(msg);
675            }
676            // Revert file back to original state
677            if self.validate_term_search {
678                std::fs::write(path, file_txt.text(db).to_string()).unwrap();
679            }
680
681            bar.inc(1);
682        }
683        let term_search_time = sw.elapsed();
684
685        bar.println(format!(
686            "Tail Expr syntactic hits: {}/{} ({}%)",
687            acc.tail_expr_syntax_hits,
688            acc.total_tail_exprs,
689            percentage(acc.tail_expr_syntax_hits, acc.total_tail_exprs)
690        ));
691        bar.println(format!(
692            "Tail Exprs found: {}/{} ({}%)",
693            acc.total_tail_exprs - acc.tail_expr_no_term,
694            acc.total_tail_exprs,
695            percentage(acc.total_tail_exprs - acc.tail_expr_no_term, acc.total_tail_exprs)
696        ));
697        if self.validate_term_search {
698            bar.println(format!(
699                "Tail Exprs total errors: {}, syntax errors: {}, error codes:",
700                acc.error_codes.values().sum::<u32>() + acc.syntax_errors,
701                acc.syntax_errors,
702            ));
703            for (err, count) in acc.error_codes {
704                bar.println(format!(
705                    "    E{err}: {count:>5}  (https://doc.rust-lang.org/error_codes/E{err}.html)"
706                ));
707            }
708        }
709        bar.println(format!(
710            "Term search avg time: {}ms",
711            term_search_time.time.as_millis() as u64 / acc.total_tail_exprs
712        ));
713        bar.println(format!("{:<20} {}", "Term search:", term_search_time));
714        report_metric("term search time", term_search_time.time.as_millis() as u64, "ms");
715
716        bar.finish_and_clear();
717    }
718
719    fn run_mir_lowering(
720        &self,
721        db: &RootDatabase,
722        bodies: &[DefWithBody],
723        _signatures: &[GenericDef],
724        _variants: &[Variant],
725        verbosity: Verbosity,
726    ) {
727        let mut bar = match verbosity {
728            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
729            _ if self.parallel || self.output.is_some() => ProgressReport::hidden(),
730            _ => ProgressReport::new(bodies.len()),
731        };
732        let mut sw = self.stop_watch();
733        let mut all = 0;
734        let mut fail = 0;
735        for &body in bodies {
736            bar.set_message(move || {
737                format!("mir lowering: {}", full_name(db, || body.name(db), body.module(db)))
738            });
739            bar.inc(1);
740            if matches!(body, DefWithBody::EnumVariant(_)) {
741                continue;
742            }
743            let module = body.module(db);
744            if !self.should_process(db, || body.name(db), module) {
745                continue;
746            }
747
748            all += 1;
749            #[expect(deprecated)]
750            let Err(e) = body.run_mir_body(db) else {
751                continue;
752            };
753            if verbosity.is_spammy() {
754                let full_name = module
755                    .path_to_root(db)
756                    .into_iter()
757                    .rev()
758                    .filter_map(|it| it.name(db))
759                    .chain(Some(body.name(db).unwrap_or_else(Name::missing)))
760                    .map(|it| it.display(db, Edition::LATEST).to_string())
761                    .join("::");
762                bar.println(format!("Mir body for {full_name} failed due {e:?}"));
763            }
764            fail += 1;
765            bar.tick();
766        }
767        let mir_lowering_time = sw.elapsed();
768        bar.finish_and_clear();
769        eprintln!("{:<20} {}", "MIR lowering:", mir_lowering_time);
770        eprintln!("Mir failed bodies: {fail} ({}%)", percentage(fail, all));
771        report_metric("mir failed bodies", fail, "#");
772        report_metric("mir lowering time", mir_lowering_time.time.as_millis() as u64, "ms");
773    }
774
775    fn run_inference(
776        &self,
777        db: &RootDatabase,
778        vfs: &Vfs,
779        bodies: &[DefWithBody],
780        signatures: &[GenericDef],
781        _variants: &[Variant],
782        verbosity: Verbosity,
783    ) {
784        let mut bar = match verbosity {
785            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
786            _ if self.parallel || self.output.is_some() => ProgressReport::hidden(),
787            _ => ProgressReport::new(bodies.len()),
788        };
789
790        if self.parallel {
791            let mut inference_sw = self.stop_watch();
792            let bodies = bodies
793                .iter()
794                .filter_map(|&body| body.try_into().ok())
795                .collect::<Vec<DefWithBodyId>>();
796            bodies
797                .par_iter()
798                .map_with(db.clone(), |snap, &body| {
799                    InferenceResult::of(snap, body);
800                })
801                .count();
802            let _signatures = signatures
803                .iter()
804                .filter_map(|&signatures| signatures.try_into().ok())
805                .collect::<Vec<GenericDefId>>();
806            // FIXME: We don't have access to the necessary types here (nor we should have).
807            // signatures
808            //     .par_iter()
809            //     .map_with(db.clone(), |snap, &signatures| {
810            //         InferenceResult::of(snap, signatures);
811            //     })
812            //     .count();
813            // let variants = variants.iter().copied().map(Into::into).collect::<Vec<VariantId>>();
814            // variants
815            //     .par_iter()
816            //     .map_with(db.clone(), |snap, &variants| {
817            //         InferenceResult::of(snap, variants);
818            //     })
819            //     .count();
820            eprintln!("{:<20} {}", "Parallel Inference:", inference_sw.elapsed());
821        }
822
823        let mut inference_sw = self.stop_watch();
824        bar.tick();
825        let mut num_exprs = 0;
826        let mut num_exprs_unknown = 0;
827        let mut num_exprs_partially_unknown = 0;
828        let mut num_expr_type_mismatches = 0;
829        let mut num_pats = 0;
830        let mut num_pats_unknown = 0;
831        let mut num_pats_partially_unknown = 0;
832        let mut num_pat_type_mismatches = 0;
833        let mut panics = 0;
834        for &body_id in bodies {
835            let Ok(body_def_id) = body_id.try_into() else { continue };
836            let name = body_id.name(db).unwrap_or_else(Name::missing);
837            let module = body_id.module(db);
838            let display_target = module.krate(db).to_display_target(db);
839            if let Some(only_name) = self.only.as_deref()
840                && name.display(db, Edition::LATEST).to_string() != only_name
841                && full_name(db, || body_id.name(db), module) != only_name
842            {
843                continue;
844            }
845            let msg = move || {
846                if verbosity.is_verbose() {
847                    let source = match body_id {
848                        DefWithBody::Function(it) => it.source(db).map(|it| it.syntax().cloned()),
849                        DefWithBody::Static(it) => it.source(db).map(|it| it.syntax().cloned()),
850                        DefWithBody::Const(it) => it.source(db).map(|it| it.syntax().cloned()),
851                        DefWithBody::EnumVariant(it) => {
852                            it.source(db).map(|it| it.syntax().cloned())
853                        }
854                    };
855                    if let Some(src) = source {
856                        let original_file = src.file_id.original_file(db);
857                        let path = vfs.file_path(original_file.file_id(db));
858                        let syntax_range = src.text_range();
859                        format!(
860                            "processing: {} ({} {:?})",
861                            full_name(db, || body_id.name(db), module),
862                            path,
863                            syntax_range
864                        )
865                    } else {
866                        format!("processing: {}", full_name(db, || body_id.name(db), module))
867                    }
868                } else {
869                    format!("processing: {}", full_name(db, || body_id.name(db), module))
870                }
871            };
872            if verbosity.is_spammy() {
873                bar.println(msg());
874            }
875            bar.set_message(msg);
876            let body = Body::of(db, body_def_id);
877            let inference_result =
878                catch_unwind(AssertUnwindSafe(|| InferenceResult::of(db, body_def_id)));
879            let inference_result = match inference_result {
880                Ok(inference_result) => inference_result,
881                Err(p) => {
882                    if let Some(s) = p.downcast_ref::<&str>() {
883                        eprintln!(
884                            "infer panicked for {}: {}",
885                            full_name(db, || body_id.name(db), module),
886                            s
887                        );
888                    } else if let Some(s) = p.downcast_ref::<String>() {
889                        eprintln!(
890                            "infer panicked for {}: {}",
891                            full_name(db, || body_id.name(db), module),
892                            s
893                        );
894                    } else {
895                        eprintln!(
896                            "infer panicked for {}",
897                            full_name(db, || body_id.name(db), module)
898                        );
899                    }
900                    panics += 1;
901                    bar.inc(1);
902                    continue;
903                }
904            };
905            // This query is LRU'd, so actually calling it will skew the timing results.
906            let sm = || &Body::with_source_map(db, body_def_id).1;
907
908            // region:expressions
909            let (previous_exprs, previous_unknown, previous_partially_unknown) =
910                (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
911            let type_mismatch_for_node = LazyCell::new(|| {
912                inference_result
913                    .diagnostics()
914                    .iter()
915                    .filter_map(|diag| match diag {
916                        hir_ty::InferenceDiagnostic::TypeMismatch { node, expected, found } => {
917                            Some((*node, (expected.as_ref(), found.as_ref())))
918                        }
919                        _ => None,
920                    })
921                    .collect::<FxHashMap<_, _>>()
922            });
923            for (expr_id, _) in body.exprs() {
924                let ty = inference_result.expr_ty(expr_id);
925                num_exprs += 1;
926                let unknown_or_partial = if ty.is_ty_error() {
927                    num_exprs_unknown += 1;
928                    if verbosity.is_spammy() {
929                        if let Some((path, start, end)) = expr_syntax_range(db, vfs, sm(), expr_id)
930                        {
931                            bar.println(format!(
932                                "{} {}:{}-{}:{}: Unknown type",
933                                path,
934                                start.line + 1,
935                                start.col,
936                                end.line + 1,
937                                end.col,
938                            ));
939                        } else {
940                            bar.println(format!(
941                                "{}: Unknown type",
942                                name.display(db, Edition::LATEST)
943                            ));
944                        }
945                    }
946                    true
947                } else {
948                    let is_partially_unknown = ty.references_non_lt_error();
949                    if is_partially_unknown {
950                        num_exprs_partially_unknown += 1;
951                    }
952                    is_partially_unknown
953                };
954                if self.only.is_some() && verbosity.is_spammy() {
955                    // in super-verbose mode for just one function, we print every single expression
956                    if let Some((_, start, end)) = expr_syntax_range(db, vfs, sm(), expr_id) {
957                        bar.println(format!(
958                            "{}:{}-{}:{}: {}",
959                            start.line + 1,
960                            start.col,
961                            end.line + 1,
962                            end.col,
963                            ty.display(db, display_target)
964                        ));
965                    } else {
966                        bar.println(format!(
967                            "unknown location: {}",
968                            ty.display(db, display_target)
969                        ));
970                    }
971                }
972                if unknown_or_partial && self.output == Some(OutputFormat::Csv) {
973                    println!(
974                        r#"{},type,"{}""#,
975                        location_csv_expr(db, vfs, sm(), expr_id),
976                        ty.display(db, display_target)
977                    );
978                }
979                if inference_result.expr_has_type_mismatch(expr_id) {
980                    num_expr_type_mismatches += 1;
981                    if verbosity.is_verbose() {
982                        let (expected, actual) = type_mismatch_for_node[&expr_id.into()];
983                        if let Some((path, start, end)) = expr_syntax_range(db, vfs, sm(), expr_id)
984                        {
985                            bar.println(format!(
986                                "{} {}:{}-{}:{}: Expected {}, got {}",
987                                path,
988                                start.line + 1,
989                                start.col,
990                                end.line + 1,
991                                end.col,
992                                expected.display(db, display_target),
993                                actual.display(db, display_target)
994                            ));
995                        } else {
996                            bar.println(format!(
997                                "{}: Expected {}, got {}",
998                                name.display(db, Edition::LATEST),
999                                expected.display(db, display_target),
1000                                actual.display(db, display_target)
1001                            ));
1002                        }
1003                    }
1004                    if self.output == Some(OutputFormat::Csv) {
1005                        let (expected, actual) = type_mismatch_for_node[&expr_id.into()];
1006                        println!(
1007                            r#"{},mismatch,"{}","{}""#,
1008                            location_csv_expr(db, vfs, sm(), expr_id),
1009                            expected.display(db, display_target),
1010                            actual.display(db, display_target)
1011                        );
1012                    }
1013                }
1014            }
1015            if verbosity.is_spammy() {
1016                bar.println(format!(
1017                    "In {}: {} exprs, {} unknown, {} partial",
1018                    full_name(db, || body_id.name(db), module),
1019                    num_exprs - previous_exprs,
1020                    num_exprs_unknown - previous_unknown,
1021                    num_exprs_partially_unknown - previous_partially_unknown
1022                ));
1023            }
1024            // endregion:expressions
1025
1026            // region:patterns
1027            let (previous_pats, previous_unknown, previous_partially_unknown) =
1028                (num_pats, num_pats_unknown, num_pats_partially_unknown);
1029            for (pat_id, _) in body.pats() {
1030                let ty = inference_result.pat_ty(pat_id);
1031                num_pats += 1;
1032                let unknown_or_partial = if ty.is_ty_error() {
1033                    num_pats_unknown += 1;
1034                    if verbosity.is_spammy() {
1035                        if let Some((path, start, end)) = pat_syntax_range(db, vfs, sm(), pat_id) {
1036                            bar.println(format!(
1037                                "{} {}:{}-{}:{}: Unknown type",
1038                                path,
1039                                start.line + 1,
1040                                start.col,
1041                                end.line + 1,
1042                                end.col,
1043                            ));
1044                        } else {
1045                            bar.println(format!(
1046                                "{}: Unknown type",
1047                                name.display(db, Edition::LATEST)
1048                            ));
1049                        }
1050                    }
1051                    true
1052                } else {
1053                    let is_partially_unknown = ty.references_non_lt_error();
1054                    if is_partially_unknown {
1055                        num_pats_partially_unknown += 1;
1056                    }
1057                    is_partially_unknown
1058                };
1059                if self.only.is_some() && verbosity.is_spammy() {
1060                    // in super-verbose mode for just one function, we print every single pattern
1061                    if let Some((_, start, end)) = pat_syntax_range(db, vfs, sm(), pat_id) {
1062                        bar.println(format!(
1063                            "{}:{}-{}:{}: {}",
1064                            start.line + 1,
1065                            start.col,
1066                            end.line + 1,
1067                            end.col,
1068                            ty.display(db, display_target)
1069                        ));
1070                    } else {
1071                        bar.println(format!(
1072                            "unknown location: {}",
1073                            ty.display(db, display_target)
1074                        ));
1075                    }
1076                }
1077                if unknown_or_partial && self.output == Some(OutputFormat::Csv) {
1078                    println!(
1079                        r#"{},type,"{}""#,
1080                        location_csv_pat(db, vfs, sm(), pat_id),
1081                        ty.display(db, display_target)
1082                    );
1083                }
1084                if inference_result.pat_has_type_mismatch(pat_id) {
1085                    num_pat_type_mismatches += 1;
1086                    if verbosity.is_verbose() {
1087                        let (expected, actual) = type_mismatch_for_node[&pat_id.into()];
1088                        if let Some((path, start, end)) = pat_syntax_range(db, vfs, sm(), pat_id) {
1089                            bar.println(format!(
1090                                "{} {}:{}-{}:{}: Expected {}, got {}",
1091                                path,
1092                                start.line + 1,
1093                                start.col,
1094                                end.line + 1,
1095                                end.col,
1096                                expected.display(db, display_target),
1097                                actual.display(db, display_target)
1098                            ));
1099                        } else {
1100                            bar.println(format!(
1101                                "{}: Expected {}, got {}",
1102                                name.display(db, Edition::LATEST),
1103                                expected.display(db, display_target),
1104                                actual.display(db, display_target)
1105                            ));
1106                        }
1107                    }
1108                    if self.output == Some(OutputFormat::Csv) {
1109                        let (expected, actual) = type_mismatch_for_node[&pat_id.into()];
1110                        println!(
1111                            r#"{},mismatch,"{}","{}""#,
1112                            location_csv_pat(db, vfs, sm(), pat_id),
1113                            expected.display(db, display_target),
1114                            actual.display(db, display_target)
1115                        );
1116                    }
1117                }
1118            }
1119            if verbosity.is_spammy() {
1120                bar.println(format!(
1121                    "In {}: {} pats, {} unknown, {} partial",
1122                    full_name(db, || body_id.name(db), module),
1123                    num_pats - previous_pats,
1124                    num_pats_unknown - previous_unknown,
1125                    num_pats_partially_unknown - previous_partially_unknown
1126                ));
1127            }
1128            // endregion:patterns
1129            bar.inc(1);
1130        }
1131
1132        bar.finish_and_clear();
1133        let inference_time = inference_sw.elapsed();
1134        eprintln!(
1135            "  exprs: {}, ??ty: {} ({}%), ?ty: {} ({}%), !ty: {}",
1136            num_exprs,
1137            num_exprs_unknown,
1138            percentage(num_exprs_unknown, num_exprs),
1139            num_exprs_partially_unknown,
1140            percentage(num_exprs_partially_unknown, num_exprs),
1141            num_expr_type_mismatches
1142        );
1143        eprintln!(
1144            "  pats: {}, ??ty: {} ({}%), ?ty: {} ({}%), !ty: {}",
1145            num_pats,
1146            num_pats_unknown,
1147            percentage(num_pats_unknown, num_pats),
1148            num_pats_partially_unknown,
1149            percentage(num_pats_partially_unknown, num_pats),
1150            num_pat_type_mismatches
1151        );
1152        eprintln!("  panics: {panics}");
1153        eprintln!("{:<20} {}", "Inference:", inference_time);
1154        report_metric("unknown type", num_exprs_unknown, "#");
1155        report_metric("type mismatches", num_expr_type_mismatches, "#");
1156        report_metric("pattern unknown type", num_pats_unknown, "#");
1157        report_metric("pattern type mismatches", num_pat_type_mismatches, "#");
1158        report_metric("inference time", inference_time.time.as_millis() as u64, "ms");
1159    }
1160
1161    fn run_body_lowering(
1162        &self,
1163        db: &RootDatabase,
1164        vfs: &Vfs,
1165        bodies: &[DefWithBody],
1166        signatures: &[GenericDef],
1167        variants: &[Variant],
1168        verbosity: Verbosity,
1169    ) {
1170        let mut bar = match verbosity {
1171            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
1172            _ if self.output.is_some() => ProgressReport::hidden(),
1173            _ => ProgressReport::new(bodies.len() + signatures.len() + variants.len()),
1174        };
1175
1176        let mut sw = self.stop_watch();
1177        bar.tick();
1178        for &signature in signatures {
1179            let Ok(signature_id) = signature.try_into() else { continue };
1180            let module = signature.module(db);
1181            if !self.should_process(db, || signature.name(db), module) {
1182                continue;
1183            }
1184            let msg = move || {
1185                if verbosity.is_verbose() {
1186                    let source = match signature {
1187                        GenericDef::Function(it) => it.source(db).map(|it| it.syntax().cloned()),
1188                        GenericDef::Static(it) => it.source(db).map(|it| it.syntax().cloned()),
1189                        GenericDef::Const(it) => it.source(db).map(|it| it.syntax().cloned()),
1190                        GenericDef::Adt(adt) => adt.source(db).map(|it| it.syntax().cloned()),
1191                        GenericDef::Trait(it) => it.source(db).map(|it| it.syntax().cloned()),
1192                        GenericDef::TypeAlias(type_alias) => {
1193                            type_alias.source(db).map(|it| it.syntax().cloned())
1194                        }
1195                        GenericDef::Impl(it) => it.source(db).map(|it| it.syntax().cloned()),
1196                    };
1197                    if let Some(src) = source {
1198                        let original_file = src.file_id.original_file(db);
1199                        let path = vfs.file_path(original_file.file_id(db));
1200                        let syntax_range = src.text_range();
1201                        format!(
1202                            "processing: {} ({} {:?})",
1203                            full_name(db, || signature.name(db), module),
1204                            path,
1205                            syntax_range
1206                        )
1207                    } else {
1208                        format!("processing: {}", full_name(db, || signature.name(db), module))
1209                    }
1210                } else {
1211                    format!("processing: {}", full_name(db, || signature.name(db), module))
1212                }
1213            };
1214            if verbosity.is_spammy() {
1215                bar.println(msg());
1216            }
1217            bar.set_message(msg);
1218            ExpressionStore::of(db, ExpressionStoreOwnerId::Signature(signature_id));
1219            bar.inc(1);
1220        }
1221
1222        for &variant in variants {
1223            let variant_id = variant.into();
1224            let module = variant.module(db);
1225            if !self.should_process(db, || Some(variant.name(db)), module) {
1226                continue;
1227            }
1228            let msg = move || {
1229                if verbosity.is_verbose() {
1230                    let source = match variant {
1231                        Variant::EnumVariant(it) => it.source(db).map(|it| it.syntax().cloned()),
1232                        Variant::Struct(it) => it.source(db).map(|it| it.syntax().cloned()),
1233                        Variant::Union(it) => it.source(db).map(|it| it.syntax().cloned()),
1234                    };
1235                    if let Some(src) = source {
1236                        let original_file = src.file_id.original_file(db);
1237                        let path = vfs.file_path(original_file.file_id(db));
1238                        let syntax_range = src.text_range();
1239                        format!(
1240                            "processing: {} ({} {:?})",
1241                            full_name(db, || Some(variant.name(db)), module),
1242                            path,
1243                            syntax_range
1244                        )
1245                    } else {
1246                        format!("processing: {}", full_name(db, || Some(variant.name(db)), module))
1247                    }
1248                } else {
1249                    format!("processing: {}", full_name(db, || Some(variant.name(db)), module))
1250                }
1251            };
1252            if verbosity.is_spammy() {
1253                bar.println(msg());
1254            }
1255            bar.set_message(msg);
1256            ExpressionStore::of(db, ExpressionStoreOwnerId::VariantFields(variant_id));
1257            bar.inc(1);
1258        }
1259
1260        for &body_id in bodies {
1261            let Ok(body_def_id) = body_id.try_into() else { continue };
1262            let module = body_id.module(db);
1263            if !self.should_process(db, || body_id.name(db), module) {
1264                continue;
1265            }
1266            let msg = move || {
1267                if verbosity.is_verbose() {
1268                    let source = match body_id {
1269                        DefWithBody::Function(it) => it.source(db).map(|it| it.syntax().cloned()),
1270                        DefWithBody::Static(it) => it.source(db).map(|it| it.syntax().cloned()),
1271                        DefWithBody::Const(it) => it.source(db).map(|it| it.syntax().cloned()),
1272                        DefWithBody::EnumVariant(it) => {
1273                            it.source(db).map(|it| it.syntax().cloned())
1274                        }
1275                    };
1276                    if let Some(src) = source {
1277                        let original_file = src.file_id.original_file(db);
1278                        let path = vfs.file_path(original_file.file_id(db));
1279                        let syntax_range = src.text_range();
1280                        format!(
1281                            "processing: {} ({} {:?})",
1282                            full_name(db, || body_id.name(db), module),
1283                            path,
1284                            syntax_range
1285                        )
1286                    } else {
1287                        format!("processing: {}", full_name(db, || body_id.name(db), module))
1288                    }
1289                } else {
1290                    format!("processing: {}", full_name(db, || body_id.name(db), module))
1291                }
1292            };
1293            if verbosity.is_spammy() {
1294                bar.println(msg());
1295            }
1296            bar.set_message(msg);
1297            Body::of(db, body_def_id);
1298            bar.inc(1);
1299        }
1300
1301        bar.finish_and_clear();
1302        let body_lowering_time = sw.elapsed();
1303        eprintln!("{:<20} {}", "Expression Store Lowering:", body_lowering_time);
1304        report_metric("body lowering time", body_lowering_time.time.as_millis() as u64, "ms");
1305    }
1306
1307    fn run_lang_items(&self, db: &RootDatabase, crates: &[Crate], verbosity: Verbosity) {
1308        let mut bar = match verbosity {
1309            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
1310            _ if self.output.is_some() => ProgressReport::hidden(),
1311            _ => ProgressReport::new(crates.len()),
1312        };
1313
1314        let mut sw = self.stop_watch();
1315        bar.tick();
1316        for &krate in crates {
1317            crate_lang_items(db, krate.into());
1318            bar.inc(1);
1319        }
1320
1321        bar.finish_and_clear();
1322        let time = sw.elapsed();
1323        eprintln!("{:<20} {}", "Crate lang items:", time);
1324        report_metric("crate lang items time", time.time.as_millis() as u64, "ms");
1325    }
1326
1327    /// Invariant: `file_ids` must be sorted and deduped before passing into here
1328    fn run_ide_things(
1329        &self,
1330        analysis: Analysis,
1331        file_ids: &[EditionedFileId],
1332        db: &RootDatabase,
1333        vfs: &Vfs,
1334        verbosity: Verbosity,
1335    ) {
1336        let len = file_ids.len();
1337        let create_bar = || match verbosity {
1338            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
1339            _ if self.parallel || self.output.is_some() => ProgressReport::hidden(),
1340            _ => ProgressReport::new(len),
1341        };
1342
1343        let mut sw = self.stop_watch();
1344
1345        let mut bar = create_bar();
1346        for &file_id in file_ids {
1347            let msg = format!("diagnostics: {}", vfs.file_path(file_id.file_id(db)));
1348            bar.set_message(move || msg.clone());
1349            _ = analysis.full_diagnostics(
1350                &DiagnosticsConfig {
1351                    enabled: true,
1352                    proc_macros_enabled: true,
1353                    proc_attr_macros_enabled: true,
1354                    disable_experimental: false,
1355                    disabled: Default::default(),
1356                    expr_fill_default: Default::default(),
1357                    snippet_cap: SnippetCap::new(true),
1358                    insert_use: ide_db::imports::insert_use::InsertUseConfig {
1359                        granularity: ide_db::imports::insert_use::ImportGranularity::Crate,
1360                        enforce_granularity: true,
1361                        prefix_kind: hir::PrefixKind::ByCrate,
1362                        group: true,
1363                        skip_glob_imports: true,
1364                    },
1365                    prefer_no_std: false,
1366                    prefer_prelude: true,
1367                    prefer_absolute: false,
1368                    style_lints: false,
1369                    term_search_fuel: 400,
1370                    term_search_borrowck: true,
1371                    show_rename_conflicts: true,
1372                },
1373                ide::AssistResolveStrategy::All,
1374                analysis.editioned_file_id_to_vfs(file_id),
1375            );
1376            bar.inc(1);
1377        }
1378        bar.finish_and_clear();
1379
1380        let mut bar = create_bar();
1381        for &file_id in file_ids {
1382            let msg = format!("inlay hints: {}", vfs.file_path(file_id.file_id(db)));
1383            bar.set_message(move || msg.clone());
1384            _ = analysis.inlay_hints(
1385                &InlayHintsConfig {
1386                    render_colons: false,
1387                    type_hints: true,
1388                    type_hints_placement: ide::TypeHintsPlacement::Inline,
1389                    sized_bound: false,
1390                    discriminant_hints: ide::DiscriminantHints::Always,
1391                    parameter_hints: true,
1392                    parameter_hints_for_missing_arguments: false,
1393                    generic_parameter_hints: ide::GenericParameterHints {
1394                        type_hints: true,
1395                        lifetime_hints: true,
1396                        const_hints: true,
1397                    },
1398                    chaining_hints: true,
1399                    adjustment_hints: ide::AdjustmentHints::Always,
1400                    adjustment_hints_disable_reborrows: true,
1401                    adjustment_hints_mode: ide::AdjustmentHintsMode::Postfix,
1402                    adjustment_hints_hide_outside_unsafe: false,
1403                    closure_return_type_hints: ide::ClosureReturnTypeHints::Always,
1404                    closure_capture_hints: true,
1405                    binding_mode_hints: true,
1406                    implicit_drop_hints: true,
1407                    implied_dyn_trait_hints: true,
1408                    lifetime_elision_hints: ide::LifetimeElisionHints::Always,
1409                    param_names_for_lifetime_elision_hints: true,
1410                    hide_inferred_type_hints: false,
1411                    hide_named_constructor_hints: false,
1412                    hide_closure_initialization_hints: false,
1413                    hide_closure_parameter_hints: false,
1414                    closure_style: hir::ClosureStyle::ImplFn,
1415                    max_length: Some(25),
1416                    closing_brace_hints_min_lines: Some(20),
1417                    fields_to_resolve: InlayFieldsToResolve::empty(),
1418                    range_exclusive_hints: true,
1419                    ra_fixture: RaFixtureConfig::default(),
1420                },
1421                analysis.editioned_file_id_to_vfs(file_id),
1422                None,
1423            );
1424            bar.inc(1);
1425        }
1426        bar.finish_and_clear();
1427
1428        let mut bar = create_bar();
1429        let annotation_config = AnnotationConfig {
1430            binary_target: true,
1431            annotate_runnables: true,
1432            annotate_impls: true,
1433            annotate_references: false,
1434            annotate_method_references: false,
1435            annotate_enum_variant_references: false,
1436            location: ide::AnnotationLocation::AboveName,
1437            filter_adjacent_derive_implementations: false,
1438            ra_fixture: RaFixtureConfig::default(),
1439        };
1440        for &file_id in file_ids {
1441            let msg = format!("annotations: {}", vfs.file_path(file_id.file_id(db)));
1442            bar.set_message(move || msg.clone());
1443            analysis
1444                .annotations(&annotation_config, analysis.editioned_file_id_to_vfs(file_id))
1445                .unwrap()
1446                .into_iter()
1447                .for_each(|annotation| {
1448                    _ = analysis.resolve_annotation(&annotation_config, annotation);
1449                });
1450            bar.inc(1);
1451        }
1452        bar.finish_and_clear();
1453
1454        let ide_time = sw.elapsed();
1455        eprintln!("{:<20} {} ({} files)", "IDE:", ide_time, file_ids.len());
1456    }
1457
1458    fn should_process(
1459        &self,
1460        db: &RootDatabase,
1461        name_fn: impl Fn() -> Option<Name>,
1462        module: hir::Module,
1463    ) -> bool {
1464        if let Some(only_name) = self.only.as_deref() {
1465            let name = name_fn().unwrap_or_else(Name::missing);
1466
1467            if name.display(db, Edition::LATEST).to_string() != only_name
1468                && full_name(db, name_fn, module) != only_name
1469            {
1470                return false;
1471            }
1472        }
1473        true
1474    }
1475
1476    fn stop_watch(&self) -> StopWatch {
1477        StopWatch::start()
1478    }
1479}
1480
1481fn full_name(db: &RootDatabase, name: impl Fn() -> Option<Name>, module: hir::Module) -> String {
1482    module
1483        .krate(db)
1484        .display_name(db)
1485        .map(|it| it.canonical_name().as_str().to_owned())
1486        .into_iter()
1487        .chain(
1488            module
1489                .path_to_root(db)
1490                .into_iter()
1491                .filter_map(|it| it.name(db))
1492                .rev()
1493                .chain(Some(name().unwrap_or_else(Name::missing)))
1494                .map(|it| it.display(db, Edition::LATEST).to_string()),
1495        )
1496        .join("::")
1497}
1498
1499fn location_csv_expr(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, expr_id: ExprId) -> String {
1500    let src = match sm.expr_syntax(expr_id) {
1501        Ok(s) => s,
1502        Err(SyntheticSyntax) => return "synthetic,,".to_owned(),
1503    };
1504    let root = db.parse_or_expand(src.file_id);
1505    let node = src.map(|e| e.to_node(&root).syntax().clone());
1506    let original_range = node.as_ref().original_file_range_rooted(db);
1507    let path = vfs.file_path(original_range.file_id.file_id(db));
1508    let line_index = line_index(db, original_range.file_id.file_id(db));
1509    let text_range = original_range.range;
1510    let (start, end) =
1511        (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
1512    format!("{path},{}:{},{}:{}", start.line + 1, start.col, end.line + 1, end.col)
1513}
1514
1515fn location_csv_pat(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, pat_id: PatId) -> String {
1516    let src = match sm.pat_syntax(pat_id) {
1517        Ok(s) => s,
1518        Err(SyntheticSyntax) => return "synthetic,,".to_owned(),
1519    };
1520    let root = db.parse_or_expand(src.file_id);
1521    let node = src.map(|e| e.to_node(&root).syntax().clone());
1522    let original_range = node.as_ref().original_file_range_rooted(db);
1523    let path = vfs.file_path(original_range.file_id.file_id(db));
1524    let line_index = line_index(db, original_range.file_id.file_id(db));
1525    let text_range = original_range.range;
1526    let (start, end) =
1527        (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
1528    format!("{path},{}:{},{}:{}", start.line + 1, start.col, end.line + 1, end.col)
1529}
1530
1531fn expr_syntax_range<'a>(
1532    db: &RootDatabase,
1533    vfs: &'a Vfs,
1534    sm: &BodySourceMap,
1535    expr_id: ExprId,
1536) -> Option<(&'a VfsPath, LineCol, LineCol)> {
1537    let src = sm.expr_syntax(expr_id);
1538    if let Ok(src) = src {
1539        let root = db.parse_or_expand(src.file_id);
1540        let node = src.map(|e| e.to_node(&root).syntax().clone());
1541        let original_range = node.as_ref().original_file_range_rooted(db);
1542        let path = vfs.file_path(original_range.file_id.file_id(db));
1543        let line_index = line_index(db, original_range.file_id.file_id(db));
1544        let text_range = original_range.range;
1545        let (start, end) =
1546            (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
1547        Some((path, start, end))
1548    } else {
1549        None
1550    }
1551}
1552fn pat_syntax_range<'a>(
1553    db: &RootDatabase,
1554    vfs: &'a Vfs,
1555    sm: &BodySourceMap,
1556    pat_id: PatId,
1557) -> Option<(&'a VfsPath, LineCol, LineCol)> {
1558    let src = sm.pat_syntax(pat_id);
1559    if let Ok(src) = src {
1560        let root = db.parse_or_expand(src.file_id);
1561        let node = src.map(|e| e.to_node(&root).syntax().clone());
1562        let original_range = node.as_ref().original_file_range_rooted(db);
1563        let path = vfs.file_path(original_range.file_id.file_id(db));
1564        let line_index = line_index(db, original_range.file_id.file_id(db));
1565        let text_range = original_range.range;
1566        let (start, end) =
1567            (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
1568        Some((path, start, end))
1569    } else {
1570        None
1571    }
1572}
1573
1574fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
1575    for i in 0..slice.len() {
1576        randomize_first(rng, &mut slice[i..]);
1577    }
1578
1579    fn randomize_first<T>(rng: &mut Rand32, slice: &mut [T]) {
1580        assert!(!slice.is_empty());
1581        let idx = rng.rand_range(0..slice.len() as u32) as usize;
1582        slice.swap(0, idx);
1583    }
1584}
1585
1586fn percentage(n: u64, total: u64) -> u64 {
1587    (n * 100).checked_div(total).unwrap_or(100)
1588}
1589
1590#[derive(Default, Debug, Eq, PartialEq)]
1591struct UsizeWithUnderscore(usize);
1592
1593impl fmt::Display for UsizeWithUnderscore {
1594    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1595        let num_str = self.0.to_string();
1596
1597        if num_str.len() <= 3 {
1598            return write!(f, "{num_str}");
1599        }
1600
1601        let mut result = String::new();
1602
1603        for (count, ch) in num_str.chars().rev().enumerate() {
1604            if count > 0 && count % 3 == 0 {
1605                result.push('_');
1606            }
1607            result.push(ch);
1608        }
1609
1610        let result = result.chars().rev().collect::<String>();
1611        write!(f, "{result}")
1612    }
1613}
1614
1615impl std::ops::AddAssign for UsizeWithUnderscore {
1616    fn add_assign(&mut self, other: UsizeWithUnderscore) {
1617        self.0 += other.0;
1618    }
1619}
1620
1621#[derive(Default, Debug, Eq, PartialEq)]
1622struct PrettyItemStats {
1623    traits: UsizeWithUnderscore,
1624    impls: UsizeWithUnderscore,
1625    mods: UsizeWithUnderscore,
1626    macro_calls: UsizeWithUnderscore,
1627    macro_rules: UsizeWithUnderscore,
1628}
1629
1630impl From<hir_def::item_tree::ItemTreeDataStats> for PrettyItemStats {
1631    fn from(value: hir_def::item_tree::ItemTreeDataStats) -> Self {
1632        Self {
1633            traits: UsizeWithUnderscore(value.traits),
1634            impls: UsizeWithUnderscore(value.impls),
1635            mods: UsizeWithUnderscore(value.mods),
1636            macro_calls: UsizeWithUnderscore(value.macro_calls),
1637            macro_rules: UsizeWithUnderscore(value.macro_rules),
1638        }
1639    }
1640}
1641
1642impl AddAssign for PrettyItemStats {
1643    fn add_assign(&mut self, rhs: Self) {
1644        self.traits += rhs.traits;
1645        self.impls += rhs.impls;
1646        self.mods += rhs.mods;
1647        self.macro_calls += rhs.macro_calls;
1648        self.macro_rules += rhs.macro_rules;
1649    }
1650}
1651
1652impl fmt::Display for PrettyItemStats {
1653    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1654        write!(
1655            f,
1656            "traits: {}, impl: {}, mods: {}, macro calls: {}, macro rules: {}",
1657            self.traits, self.impls, self.mods, self.macro_calls, self.macro_rules
1658        )
1659    }
1660}
1661
1662// FIXME(salsa-transition): bring this back whenever we implement
1663// Salsa's memory usage tracking to work with tracked functions.
1664// fn syntax_len(node: SyntaxNode) -> usize {
1665//     // Macro expanded code doesn't contain whitespace, so erase *all* whitespace
1666//     // to make macro and non-macro code comparable.
1667//     drop_whitespace(&node.to_string()).len()
1668// }