Skip to main content

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