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