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, VariantId, 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            let Ok(body_id) = body.try_into() else {
750                continue;
751            };
752            let Err(e) = db.mir_body(body_id) 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            signatures
809                .par_iter()
810                .map_with(db.clone(), |snap, &signatures| {
811                    InferenceResult::of(snap, signatures);
812                })
813                .count();
814            let variants = variants.iter().copied().map(Into::into).collect::<Vec<VariantId>>();
815            variants
816                .par_iter()
817                .map_with(db.clone(), |snap, &variants| {
818                    InferenceResult::of(snap, variants);
819                })
820                .count();
821            eprintln!("{:<20} {}", "Parallel Inference:", inference_sw.elapsed());
822        }
823
824        let mut inference_sw = self.stop_watch();
825        bar.tick();
826        let mut num_exprs = 0;
827        let mut num_exprs_unknown = 0;
828        let mut num_exprs_partially_unknown = 0;
829        let mut num_expr_type_mismatches = 0;
830        let mut num_pats = 0;
831        let mut num_pats_unknown = 0;
832        let mut num_pats_partially_unknown = 0;
833        let mut num_pat_type_mismatches = 0;
834        let mut panics = 0;
835        for &body_id in bodies {
836            let Ok(body_def_id) = body_id.try_into() else { continue };
837            let name = body_id.name(db).unwrap_or_else(Name::missing);
838            let module = body_id.module(db);
839            let display_target = module.krate(db).to_display_target(db);
840            if let Some(only_name) = self.only.as_deref()
841                && name.display(db, Edition::LATEST).to_string() != only_name
842                && full_name(db, || body_id.name(db), module) != only_name
843            {
844                continue;
845            }
846            let msg = move || {
847                if verbosity.is_verbose() {
848                    let source = match body_id {
849                        DefWithBody::Function(it) => it.source(db).map(|it| it.syntax().cloned()),
850                        DefWithBody::Static(it) => it.source(db).map(|it| it.syntax().cloned()),
851                        DefWithBody::Const(it) => it.source(db).map(|it| it.syntax().cloned()),
852                        DefWithBody::EnumVariant(it) => {
853                            it.source(db).map(|it| it.syntax().cloned())
854                        }
855                    };
856                    if let Some(src) = source {
857                        let original_file = src.file_id.original_file(db);
858                        let path = vfs.file_path(original_file.file_id(db));
859                        let syntax_range = src.text_range();
860                        format!(
861                            "processing: {} ({} {:?})",
862                            full_name(db, || body_id.name(db), module),
863                            path,
864                            syntax_range
865                        )
866                    } else {
867                        format!("processing: {}", full_name(db, || body_id.name(db), module))
868                    }
869                } else {
870                    format!("processing: {}", full_name(db, || body_id.name(db), module))
871                }
872            };
873            if verbosity.is_spammy() {
874                bar.println(msg());
875            }
876            bar.set_message(msg);
877            let body = Body::of(db, body_def_id);
878            let inference_result =
879                catch_unwind(AssertUnwindSafe(|| InferenceResult::of(db, body_def_id)));
880            let inference_result = match inference_result {
881                Ok(inference_result) => inference_result,
882                Err(p) => {
883                    if let Some(s) = p.downcast_ref::<&str>() {
884                        eprintln!(
885                            "infer panicked for {}: {}",
886                            full_name(db, || body_id.name(db), module),
887                            s
888                        );
889                    } else if let Some(s) = p.downcast_ref::<String>() {
890                        eprintln!(
891                            "infer panicked for {}: {}",
892                            full_name(db, || body_id.name(db), module),
893                            s
894                        );
895                    } else {
896                        eprintln!(
897                            "infer panicked for {}",
898                            full_name(db, || body_id.name(db), module)
899                        );
900                    }
901                    panics += 1;
902                    bar.inc(1);
903                    continue;
904                }
905            };
906            // This query is LRU'd, so actually calling it will skew the timing results.
907            let sm = || &Body::with_source_map(db, body_def_id).1;
908
909            // region:expressions
910            let (previous_exprs, previous_unknown, previous_partially_unknown) =
911                (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
912            let type_mismatch_for_node = LazyCell::new(|| {
913                inference_result
914                    .diagnostics()
915                    .iter()
916                    .filter_map(|diag| match diag {
917                        hir_ty::InferenceDiagnostic::TypeMismatch { node, expected, found } => {
918                            Some((*node, (expected.as_ref(), found.as_ref())))
919                        }
920                        _ => None,
921                    })
922                    .collect::<FxHashMap<_, _>>()
923            });
924            for (expr_id, _) in body.exprs() {
925                let ty = inference_result.expr_ty(expr_id);
926                num_exprs += 1;
927                let unknown_or_partial = if ty.is_ty_error() {
928                    num_exprs_unknown += 1;
929                    if verbosity.is_spammy() {
930                        if let Some((path, start, end)) = expr_syntax_range(db, vfs, sm(), expr_id)
931                        {
932                            bar.println(format!(
933                                "{} {}:{}-{}:{}: Unknown type",
934                                path,
935                                start.line + 1,
936                                start.col,
937                                end.line + 1,
938                                end.col,
939                            ));
940                        } else {
941                            bar.println(format!(
942                                "{}: Unknown type",
943                                name.display(db, Edition::LATEST)
944                            ));
945                        }
946                    }
947                    true
948                } else {
949                    let is_partially_unknown = ty.references_non_lt_error();
950                    if is_partially_unknown {
951                        num_exprs_partially_unknown += 1;
952                    }
953                    is_partially_unknown
954                };
955                if self.only.is_some() && verbosity.is_spammy() {
956                    // in super-verbose mode for just one function, we print every single expression
957                    if let Some((_, start, end)) = expr_syntax_range(db, vfs, sm(), expr_id) {
958                        bar.println(format!(
959                            "{}:{}-{}:{}: {}",
960                            start.line + 1,
961                            start.col,
962                            end.line + 1,
963                            end.col,
964                            ty.display(db, display_target)
965                        ));
966                    } else {
967                        bar.println(format!(
968                            "unknown location: {}",
969                            ty.display(db, display_target)
970                        ));
971                    }
972                }
973                if unknown_or_partial && self.output == Some(OutputFormat::Csv) {
974                    println!(
975                        r#"{},type,"{}""#,
976                        location_csv_expr(db, vfs, sm(), expr_id),
977                        ty.display(db, display_target)
978                    );
979                }
980                if inference_result.expr_has_type_mismatch(expr_id) {
981                    num_expr_type_mismatches += 1;
982                    if verbosity.is_verbose() {
983                        let (expected, actual) = type_mismatch_for_node[&expr_id.into()];
984                        if let Some((path, start, end)) = expr_syntax_range(db, vfs, sm(), expr_id)
985                        {
986                            bar.println(format!(
987                                "{} {}:{}-{}:{}: Expected {}, got {}",
988                                path,
989                                start.line + 1,
990                                start.col,
991                                end.line + 1,
992                                end.col,
993                                expected.display(db, display_target),
994                                actual.display(db, display_target)
995                            ));
996                        } else {
997                            bar.println(format!(
998                                "{}: Expected {}, got {}",
999                                name.display(db, Edition::LATEST),
1000                                expected.display(db, display_target),
1001                                actual.display(db, display_target)
1002                            ));
1003                        }
1004                    }
1005                    if self.output == Some(OutputFormat::Csv) {
1006                        let (expected, actual) = type_mismatch_for_node[&expr_id.into()];
1007                        println!(
1008                            r#"{},mismatch,"{}","{}""#,
1009                            location_csv_expr(db, vfs, sm(), expr_id),
1010                            expected.display(db, display_target),
1011                            actual.display(db, display_target)
1012                        );
1013                    }
1014                }
1015            }
1016            if verbosity.is_spammy() {
1017                bar.println(format!(
1018                    "In {}: {} exprs, {} unknown, {} partial",
1019                    full_name(db, || body_id.name(db), module),
1020                    num_exprs - previous_exprs,
1021                    num_exprs_unknown - previous_unknown,
1022                    num_exprs_partially_unknown - previous_partially_unknown
1023                ));
1024            }
1025            // endregion:expressions
1026
1027            // region:patterns
1028            let (previous_pats, previous_unknown, previous_partially_unknown) =
1029                (num_pats, num_pats_unknown, num_pats_partially_unknown);
1030            for (pat_id, _) in body.pats() {
1031                let ty = inference_result.pat_ty(pat_id);
1032                num_pats += 1;
1033                let unknown_or_partial = if ty.is_ty_error() {
1034                    num_pats_unknown += 1;
1035                    if verbosity.is_spammy() {
1036                        if let Some((path, start, end)) = pat_syntax_range(db, vfs, sm(), pat_id) {
1037                            bar.println(format!(
1038                                "{} {}:{}-{}:{}: Unknown type",
1039                                path,
1040                                start.line + 1,
1041                                start.col,
1042                                end.line + 1,
1043                                end.col,
1044                            ));
1045                        } else {
1046                            bar.println(format!(
1047                                "{}: Unknown type",
1048                                name.display(db, Edition::LATEST)
1049                            ));
1050                        }
1051                    }
1052                    true
1053                } else {
1054                    let is_partially_unknown = ty.references_non_lt_error();
1055                    if is_partially_unknown {
1056                        num_pats_partially_unknown += 1;
1057                    }
1058                    is_partially_unknown
1059                };
1060                if self.only.is_some() && verbosity.is_spammy() {
1061                    // in super-verbose mode for just one function, we print every single pattern
1062                    if let Some((_, start, end)) = pat_syntax_range(db, vfs, sm(), pat_id) {
1063                        bar.println(format!(
1064                            "{}:{}-{}:{}: {}",
1065                            start.line + 1,
1066                            start.col,
1067                            end.line + 1,
1068                            end.col,
1069                            ty.display(db, display_target)
1070                        ));
1071                    } else {
1072                        bar.println(format!(
1073                            "unknown location: {}",
1074                            ty.display(db, display_target)
1075                        ));
1076                    }
1077                }
1078                if unknown_or_partial && self.output == Some(OutputFormat::Csv) {
1079                    println!(
1080                        r#"{},type,"{}""#,
1081                        location_csv_pat(db, vfs, sm(), pat_id),
1082                        ty.display(db, display_target)
1083                    );
1084                }
1085                if inference_result.pat_has_type_mismatch(pat_id) {
1086                    num_pat_type_mismatches += 1;
1087                    if verbosity.is_verbose() {
1088                        let (expected, actual) = type_mismatch_for_node[&pat_id.into()];
1089                        if let Some((path, start, end)) = pat_syntax_range(db, vfs, sm(), pat_id) {
1090                            bar.println(format!(
1091                                "{} {}:{}-{}:{}: Expected {}, got {}",
1092                                path,
1093                                start.line + 1,
1094                                start.col,
1095                                end.line + 1,
1096                                end.col,
1097                                expected.display(db, display_target),
1098                                actual.display(db, display_target)
1099                            ));
1100                        } else {
1101                            bar.println(format!(
1102                                "{}: Expected {}, got {}",
1103                                name.display(db, Edition::LATEST),
1104                                expected.display(db, display_target),
1105                                actual.display(db, display_target)
1106                            ));
1107                        }
1108                    }
1109                    if self.output == Some(OutputFormat::Csv) {
1110                        let (expected, actual) = type_mismatch_for_node[&pat_id.into()];
1111                        println!(
1112                            r#"{},mismatch,"{}","{}""#,
1113                            location_csv_pat(db, vfs, sm(), pat_id),
1114                            expected.display(db, display_target),
1115                            actual.display(db, display_target)
1116                        );
1117                    }
1118                }
1119            }
1120            if verbosity.is_spammy() {
1121                bar.println(format!(
1122                    "In {}: {} pats, {} unknown, {} partial",
1123                    full_name(db, || body_id.name(db), module),
1124                    num_pats - previous_pats,
1125                    num_pats_unknown - previous_unknown,
1126                    num_pats_partially_unknown - previous_partially_unknown
1127                ));
1128            }
1129            // endregion:patterns
1130            bar.inc(1);
1131        }
1132
1133        bar.finish_and_clear();
1134        let inference_time = inference_sw.elapsed();
1135        eprintln!(
1136            "  exprs: {}, ??ty: {} ({}%), ?ty: {} ({}%), !ty: {}",
1137            num_exprs,
1138            num_exprs_unknown,
1139            percentage(num_exprs_unknown, num_exprs),
1140            num_exprs_partially_unknown,
1141            percentage(num_exprs_partially_unknown, num_exprs),
1142            num_expr_type_mismatches
1143        );
1144        eprintln!(
1145            "  pats: {}, ??ty: {} ({}%), ?ty: {} ({}%), !ty: {}",
1146            num_pats,
1147            num_pats_unknown,
1148            percentage(num_pats_unknown, num_pats),
1149            num_pats_partially_unknown,
1150            percentage(num_pats_partially_unknown, num_pats),
1151            num_pat_type_mismatches
1152        );
1153        eprintln!("  panics: {panics}");
1154        eprintln!("{:<20} {}", "Inference:", inference_time);
1155        report_metric("unknown type", num_exprs_unknown, "#");
1156        report_metric("type mismatches", num_expr_type_mismatches, "#");
1157        report_metric("pattern unknown type", num_pats_unknown, "#");
1158        report_metric("pattern type mismatches", num_pat_type_mismatches, "#");
1159        report_metric("inference time", inference_time.time.as_millis() as u64, "ms");
1160    }
1161
1162    fn run_body_lowering(
1163        &self,
1164        db: &RootDatabase,
1165        vfs: &Vfs,
1166        bodies: &[DefWithBody],
1167        signatures: &[GenericDef],
1168        variants: &[Variant],
1169        verbosity: Verbosity,
1170    ) {
1171        let mut bar = match verbosity {
1172            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
1173            _ if self.output.is_some() => ProgressReport::hidden(),
1174            _ => ProgressReport::new(bodies.len() + signatures.len() + variants.len()),
1175        };
1176
1177        let mut sw = self.stop_watch();
1178        bar.tick();
1179        for &signature in signatures {
1180            let Ok(signature_id) = signature.try_into() else { continue };
1181            let module = signature.module(db);
1182            if !self.should_process(db, || signature.name(db), module) {
1183                continue;
1184            }
1185            let msg = move || {
1186                if verbosity.is_verbose() {
1187                    let source = match signature {
1188                        GenericDef::Function(it) => it.source(db).map(|it| it.syntax().cloned()),
1189                        GenericDef::Static(it) => it.source(db).map(|it| it.syntax().cloned()),
1190                        GenericDef::Const(it) => it.source(db).map(|it| it.syntax().cloned()),
1191                        GenericDef::Adt(adt) => adt.source(db).map(|it| it.syntax().cloned()),
1192                        GenericDef::Trait(it) => it.source(db).map(|it| it.syntax().cloned()),
1193                        GenericDef::TypeAlias(type_alias) => {
1194                            type_alias.source(db).map(|it| it.syntax().cloned())
1195                        }
1196                        GenericDef::Impl(it) => it.source(db).map(|it| it.syntax().cloned()),
1197                    };
1198                    if let Some(src) = source {
1199                        let original_file = src.file_id.original_file(db);
1200                        let path = vfs.file_path(original_file.file_id(db));
1201                        let syntax_range = src.text_range();
1202                        format!(
1203                            "processing: {} ({} {:?})",
1204                            full_name(db, || signature.name(db), module),
1205                            path,
1206                            syntax_range
1207                        )
1208                    } else {
1209                        format!("processing: {}", full_name(db, || signature.name(db), module))
1210                    }
1211                } else {
1212                    format!("processing: {}", full_name(db, || signature.name(db), module))
1213                }
1214            };
1215            if verbosity.is_spammy() {
1216                bar.println(msg());
1217            }
1218            bar.set_message(msg);
1219            ExpressionStore::of(db, ExpressionStoreOwnerId::Signature(signature_id));
1220            bar.inc(1);
1221        }
1222
1223        for &variant in variants {
1224            let variant_id = variant.into();
1225            let module = variant.module(db);
1226            if !self.should_process(db, || Some(variant.name(db)), module) {
1227                continue;
1228            }
1229            let msg = move || {
1230                if verbosity.is_verbose() {
1231                    let source = match variant {
1232                        Variant::EnumVariant(it) => it.source(db).map(|it| it.syntax().cloned()),
1233                        Variant::Struct(it) => it.source(db).map(|it| it.syntax().cloned()),
1234                        Variant::Union(it) => it.source(db).map(|it| it.syntax().cloned()),
1235                    };
1236                    if let Some(src) = source {
1237                        let original_file = src.file_id.original_file(db);
1238                        let path = vfs.file_path(original_file.file_id(db));
1239                        let syntax_range = src.text_range();
1240                        format!(
1241                            "processing: {} ({} {:?})",
1242                            full_name(db, || Some(variant.name(db)), module),
1243                            path,
1244                            syntax_range
1245                        )
1246                    } else {
1247                        format!("processing: {}", full_name(db, || Some(variant.name(db)), module))
1248                    }
1249                } else {
1250                    format!("processing: {}", full_name(db, || Some(variant.name(db)), module))
1251                }
1252            };
1253            if verbosity.is_spammy() {
1254                bar.println(msg());
1255            }
1256            bar.set_message(msg);
1257            ExpressionStore::of(db, ExpressionStoreOwnerId::VariantFields(variant_id));
1258            bar.inc(1);
1259        }
1260
1261        for &body_id in bodies {
1262            let Ok(body_def_id) = body_id.try_into() else { continue };
1263            let module = body_id.module(db);
1264            if !self.should_process(db, || body_id.name(db), module) {
1265                continue;
1266            }
1267            let msg = move || {
1268                if verbosity.is_verbose() {
1269                    let source = match body_id {
1270                        DefWithBody::Function(it) => it.source(db).map(|it| it.syntax().cloned()),
1271                        DefWithBody::Static(it) => it.source(db).map(|it| it.syntax().cloned()),
1272                        DefWithBody::Const(it) => it.source(db).map(|it| it.syntax().cloned()),
1273                        DefWithBody::EnumVariant(it) => {
1274                            it.source(db).map(|it| it.syntax().cloned())
1275                        }
1276                    };
1277                    if let Some(src) = source {
1278                        let original_file = src.file_id.original_file(db);
1279                        let path = vfs.file_path(original_file.file_id(db));
1280                        let syntax_range = src.text_range();
1281                        format!(
1282                            "processing: {} ({} {:?})",
1283                            full_name(db, || body_id.name(db), module),
1284                            path,
1285                            syntax_range
1286                        )
1287                    } else {
1288                        format!("processing: {}", full_name(db, || body_id.name(db), module))
1289                    }
1290                } else {
1291                    format!("processing: {}", full_name(db, || body_id.name(db), module))
1292                }
1293            };
1294            if verbosity.is_spammy() {
1295                bar.println(msg());
1296            }
1297            bar.set_message(msg);
1298            Body::of(db, body_def_id);
1299            bar.inc(1);
1300        }
1301
1302        bar.finish_and_clear();
1303        let body_lowering_time = sw.elapsed();
1304        eprintln!("{:<20} {}", "Expression Store Lowering:", body_lowering_time);
1305        report_metric("body lowering time", body_lowering_time.time.as_millis() as u64, "ms");
1306    }
1307
1308    fn run_lang_items(&self, db: &RootDatabase, crates: &[Crate], verbosity: Verbosity) {
1309        let mut bar = match verbosity {
1310            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
1311            _ if self.output.is_some() => ProgressReport::hidden(),
1312            _ => ProgressReport::new(crates.len()),
1313        };
1314
1315        let mut sw = self.stop_watch();
1316        bar.tick();
1317        for &krate in crates {
1318            crate_lang_items(db, krate.into());
1319            bar.inc(1);
1320        }
1321
1322        bar.finish_and_clear();
1323        let time = sw.elapsed();
1324        eprintln!("{:<20} {}", "Crate lang items:", time);
1325        report_metric("crate lang items time", time.time.as_millis() as u64, "ms");
1326    }
1327
1328    /// Invariant: `file_ids` must be sorted and deduped before passing into here
1329    fn run_ide_things(
1330        &self,
1331        analysis: Analysis,
1332        file_ids: &[EditionedFileId],
1333        db: &RootDatabase,
1334        vfs: &Vfs,
1335        verbosity: Verbosity,
1336    ) {
1337        let len = file_ids.len();
1338        let create_bar = || match verbosity {
1339            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
1340            _ if self.parallel || self.output.is_some() => ProgressReport::hidden(),
1341            _ => ProgressReport::new(len),
1342        };
1343
1344        let mut sw = self.stop_watch();
1345
1346        let mut bar = create_bar();
1347        for &file_id in file_ids {
1348            let msg = format!("diagnostics: {}", vfs.file_path(file_id.file_id(db)));
1349            bar.set_message(move || msg.clone());
1350            _ = analysis.full_diagnostics(
1351                &DiagnosticsConfig {
1352                    enabled: true,
1353                    proc_macros_enabled: true,
1354                    proc_attr_macros_enabled: true,
1355                    disable_experimental: false,
1356                    disabled: Default::default(),
1357                    expr_fill_default: Default::default(),
1358                    snippet_cap: SnippetCap::new(true),
1359                    insert_use: ide_db::imports::insert_use::InsertUseConfig {
1360                        granularity: ide_db::imports::insert_use::ImportGranularity::Crate,
1361                        enforce_granularity: true,
1362                        prefix_kind: hir::PrefixKind::ByCrate,
1363                        group: true,
1364                        skip_glob_imports: true,
1365                    },
1366                    prefer_no_std: false,
1367                    prefer_prelude: true,
1368                    prefer_absolute: false,
1369                    style_lints: false,
1370                    term_search_fuel: 400,
1371                    term_search_borrowck: true,
1372                    show_rename_conflicts: true,
1373                },
1374                ide::AssistResolveStrategy::All,
1375                analysis.editioned_file_id_to_vfs(file_id),
1376            );
1377            bar.inc(1);
1378        }
1379        bar.finish_and_clear();
1380
1381        let mut bar = create_bar();
1382        for &file_id in file_ids {
1383            let msg = format!("inlay hints: {}", vfs.file_path(file_id.file_id(db)));
1384            bar.set_message(move || msg.clone());
1385            _ = analysis.inlay_hints(
1386                &InlayHintsConfig {
1387                    render_colons: false,
1388                    type_hints: true,
1389                    type_hints_placement: ide::TypeHintsPlacement::Inline,
1390                    sized_bound: false,
1391                    discriminant_hints: ide::DiscriminantHints::Always,
1392                    parameter_hints: true,
1393                    parameter_hints_for_missing_arguments: false,
1394                    generic_parameter_hints: ide::GenericParameterHints {
1395                        type_hints: true,
1396                        lifetime_hints: true,
1397                        const_hints: true,
1398                    },
1399                    chaining_hints: true,
1400                    adjustment_hints: ide::AdjustmentHints::Always,
1401                    adjustment_hints_disable_reborrows: true,
1402                    adjustment_hints_mode: ide::AdjustmentHintsMode::Postfix,
1403                    adjustment_hints_hide_outside_unsafe: false,
1404                    closure_return_type_hints: ide::ClosureReturnTypeHints::Always,
1405                    closure_capture_hints: true,
1406                    binding_mode_hints: true,
1407                    implicit_drop_hints: true,
1408                    implied_dyn_trait_hints: true,
1409                    lifetime_elision_hints: ide::LifetimeElisionHints::Always,
1410                    param_names_for_lifetime_elision_hints: true,
1411                    hide_inferred_type_hints: false,
1412                    hide_named_constructor_hints: false,
1413                    hide_closure_initialization_hints: false,
1414                    hide_closure_parameter_hints: false,
1415                    closure_style: hir::ClosureStyle::ImplFn,
1416                    max_length: Some(25),
1417                    closing_brace_hints_min_lines: Some(20),
1418                    fields_to_resolve: InlayFieldsToResolve::empty(),
1419                    range_exclusive_hints: true,
1420                    ra_fixture: RaFixtureConfig::default(),
1421                },
1422                analysis.editioned_file_id_to_vfs(file_id),
1423                None,
1424            );
1425            bar.inc(1);
1426        }
1427        bar.finish_and_clear();
1428
1429        let mut bar = create_bar();
1430        let annotation_config = AnnotationConfig {
1431            binary_target: true,
1432            annotate_runnables: true,
1433            annotate_impls: true,
1434            annotate_references: false,
1435            annotate_method_references: false,
1436            annotate_enum_variant_references: false,
1437            location: ide::AnnotationLocation::AboveName,
1438            filter_adjacent_derive_implementations: false,
1439            ra_fixture: RaFixtureConfig::default(),
1440        };
1441        for &file_id in file_ids {
1442            let msg = format!("annotations: {}", vfs.file_path(file_id.file_id(db)));
1443            bar.set_message(move || msg.clone());
1444            analysis
1445                .annotations(&annotation_config, analysis.editioned_file_id_to_vfs(file_id))
1446                .unwrap()
1447                .into_iter()
1448                .for_each(|annotation| {
1449                    _ = analysis.resolve_annotation(&annotation_config, annotation);
1450                });
1451            bar.inc(1);
1452        }
1453        bar.finish_and_clear();
1454
1455        let ide_time = sw.elapsed();
1456        eprintln!("{:<20} {} ({} files)", "IDE:", ide_time, file_ids.len());
1457    }
1458
1459    fn should_process(
1460        &self,
1461        db: &RootDatabase,
1462        name_fn: impl Fn() -> Option<Name>,
1463        module: hir::Module,
1464    ) -> bool {
1465        if let Some(only_name) = self.only.as_deref() {
1466            let name = name_fn().unwrap_or_else(Name::missing);
1467
1468            if name.display(db, Edition::LATEST).to_string() != only_name
1469                && full_name(db, name_fn, module) != only_name
1470            {
1471                return false;
1472            }
1473        }
1474        true
1475    }
1476
1477    fn stop_watch(&self) -> StopWatch {
1478        StopWatch::start()
1479    }
1480}
1481
1482fn full_name(db: &RootDatabase, name: impl Fn() -> Option<Name>, module: hir::Module) -> String {
1483    module
1484        .krate(db)
1485        .display_name(db)
1486        .map(|it| it.canonical_name().as_str().to_owned())
1487        .into_iter()
1488        .chain(
1489            module
1490                .path_to_root(db)
1491                .into_iter()
1492                .filter_map(|it| it.name(db))
1493                .rev()
1494                .chain(Some(name().unwrap_or_else(Name::missing)))
1495                .map(|it| it.display(db, Edition::LATEST).to_string()),
1496        )
1497        .join("::")
1498}
1499
1500fn location_csv_expr(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, expr_id: ExprId) -> String {
1501    let src = match sm.expr_syntax(expr_id) {
1502        Ok(s) => s,
1503        Err(SyntheticSyntax) => return "synthetic,,".to_owned(),
1504    };
1505    let root = db.parse_or_expand(src.file_id);
1506    let node = src.map(|e| e.to_node(&root).syntax().clone());
1507    let original_range = node.as_ref().original_file_range_rooted(db);
1508    let path = vfs.file_path(original_range.file_id.file_id(db));
1509    let line_index = line_index(db, original_range.file_id.file_id(db));
1510    let text_range = original_range.range;
1511    let (start, end) =
1512        (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
1513    format!("{path},{}:{},{}:{}", start.line + 1, start.col, end.line + 1, end.col)
1514}
1515
1516fn location_csv_pat(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, pat_id: PatId) -> String {
1517    let src = match sm.pat_syntax(pat_id) {
1518        Ok(s) => s,
1519        Err(SyntheticSyntax) => return "synthetic,,".to_owned(),
1520    };
1521    let root = db.parse_or_expand(src.file_id);
1522    let node = src.map(|e| e.to_node(&root).syntax().clone());
1523    let original_range = node.as_ref().original_file_range_rooted(db);
1524    let path = vfs.file_path(original_range.file_id.file_id(db));
1525    let line_index = line_index(db, original_range.file_id.file_id(db));
1526    let text_range = original_range.range;
1527    let (start, end) =
1528        (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
1529    format!("{path},{}:{},{}:{}", start.line + 1, start.col, end.line + 1, end.col)
1530}
1531
1532fn expr_syntax_range<'a>(
1533    db: &RootDatabase,
1534    vfs: &'a Vfs,
1535    sm: &BodySourceMap,
1536    expr_id: ExprId,
1537) -> Option<(&'a VfsPath, LineCol, LineCol)> {
1538    let src = sm.expr_syntax(expr_id);
1539    if let Ok(src) = src {
1540        let root = db.parse_or_expand(src.file_id);
1541        let node = src.map(|e| e.to_node(&root).syntax().clone());
1542        let original_range = node.as_ref().original_file_range_rooted(db);
1543        let path = vfs.file_path(original_range.file_id.file_id(db));
1544        let line_index = line_index(db, original_range.file_id.file_id(db));
1545        let text_range = original_range.range;
1546        let (start, end) =
1547            (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
1548        Some((path, start, end))
1549    } else {
1550        None
1551    }
1552}
1553fn pat_syntax_range<'a>(
1554    db: &RootDatabase,
1555    vfs: &'a Vfs,
1556    sm: &BodySourceMap,
1557    pat_id: PatId,
1558) -> Option<(&'a VfsPath, LineCol, LineCol)> {
1559    let src = sm.pat_syntax(pat_id);
1560    if let Ok(src) = src {
1561        let root = db.parse_or_expand(src.file_id);
1562        let node = src.map(|e| e.to_node(&root).syntax().clone());
1563        let original_range = node.as_ref().original_file_range_rooted(db);
1564        let path = vfs.file_path(original_range.file_id.file_id(db));
1565        let line_index = line_index(db, original_range.file_id.file_id(db));
1566        let text_range = original_range.range;
1567        let (start, end) =
1568            (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
1569        Some((path, start, end))
1570    } else {
1571        None
1572    }
1573}
1574
1575fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
1576    for i in 0..slice.len() {
1577        randomize_first(rng, &mut slice[i..]);
1578    }
1579
1580    fn randomize_first<T>(rng: &mut Rand32, slice: &mut [T]) {
1581        assert!(!slice.is_empty());
1582        let idx = rng.rand_range(0..slice.len() as u32) as usize;
1583        slice.swap(0, idx);
1584    }
1585}
1586
1587fn percentage(n: u64, total: u64) -> u64 {
1588    (n * 100).checked_div(total).unwrap_or(100)
1589}
1590
1591#[derive(Default, Debug, Eq, PartialEq)]
1592struct UsizeWithUnderscore(usize);
1593
1594impl fmt::Display for UsizeWithUnderscore {
1595    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1596        let num_str = self.0.to_string();
1597
1598        if num_str.len() <= 3 {
1599            return write!(f, "{num_str}");
1600        }
1601
1602        let mut result = String::new();
1603
1604        for (count, ch) in num_str.chars().rev().enumerate() {
1605            if count > 0 && count % 3 == 0 {
1606                result.push('_');
1607            }
1608            result.push(ch);
1609        }
1610
1611        let result = result.chars().rev().collect::<String>();
1612        write!(f, "{result}")
1613    }
1614}
1615
1616impl std::ops::AddAssign for UsizeWithUnderscore {
1617    fn add_assign(&mut self, other: UsizeWithUnderscore) {
1618        self.0 += other.0;
1619    }
1620}
1621
1622#[derive(Default, Debug, Eq, PartialEq)]
1623struct PrettyItemStats {
1624    traits: UsizeWithUnderscore,
1625    impls: UsizeWithUnderscore,
1626    mods: UsizeWithUnderscore,
1627    macro_calls: UsizeWithUnderscore,
1628    macro_rules: UsizeWithUnderscore,
1629}
1630
1631impl From<hir_def::item_tree::ItemTreeDataStats> for PrettyItemStats {
1632    fn from(value: hir_def::item_tree::ItemTreeDataStats) -> Self {
1633        Self {
1634            traits: UsizeWithUnderscore(value.traits),
1635            impls: UsizeWithUnderscore(value.impls),
1636            mods: UsizeWithUnderscore(value.mods),
1637            macro_calls: UsizeWithUnderscore(value.macro_calls),
1638            macro_rules: UsizeWithUnderscore(value.macro_rules),
1639        }
1640    }
1641}
1642
1643impl AddAssign for PrettyItemStats {
1644    fn add_assign(&mut self, rhs: Self) {
1645        self.traits += rhs.traits;
1646        self.impls += rhs.impls;
1647        self.mods += rhs.mods;
1648        self.macro_calls += rhs.macro_calls;
1649        self.macro_rules += rhs.macro_rules;
1650    }
1651}
1652
1653impl fmt::Display for PrettyItemStats {
1654    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1655        write!(
1656            f,
1657            "traits: {}, impl: {}, mods: {}, macro calls: {}, macro rules: {}",
1658            self.traits, self.impls, self.mods, self.macro_calls, self.macro_rules
1659        )
1660    }
1661}
1662
1663// FIXME(salsa-transition): bring this back whenever we implement
1664// Salsa's memory usage tracking to work with tracked functions.
1665// fn syntax_len(node: SyntaxNode) -> usize {
1666//     // Macro expanded code doesn't contain whitespace, so erase *all* whitespace
1667//     // to make macro and non-macro code comparable.
1668//     drop_whitespace(&node.to_string()).len()
1669// }