1use 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 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 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 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 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, };
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 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 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, "0277" | "0308" if generated.contains(&todo) => continue, "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 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 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 let sm = || &Body::with_source_map(db, body_def_id).1;
909
910 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 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 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 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 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 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