1 //! Fully type-check project and print various stats, like the number of type
6 time::{SystemTime, UNIX_EPOCH},
10 db::{AstDatabase, DefDatabase, HirDatabase},
11 AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
13 use hir_def::FunctionId;
14 use hir_ty::{Ty, TypeWalk};
15 use ide_db::base_db::{
16 salsa::{self, ParallelDatabase},
19 use itertools::Itertools;
21 use rayon::prelude::*;
22 use rustc_hash::FxHashSet;
27 load_cargo::load_cargo, print_memory_usage, progress_report::ProgressReport, report_metric,
30 use profile::StopWatch;
32 /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
34 impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
35 fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
36 Snap(self.0.snapshot())
40 pub struct AnalysisStatsCmd {
43 pub memory_usage: bool,
44 pub only: Option<String>,
47 pub load_output_dirs: bool,
48 pub with_proc_macro: bool,
51 impl AnalysisStatsCmd {
52 pub fn run(self, verbosity: Verbosity) -> Result<()> {
54 let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
58 let mut db_load_sw = self.stop_watch();
59 let (host, vfs) = load_cargo(&self.path, self.load_output_dirs, self.with_proc_macro)?;
60 let db = host.raw_database();
61 eprintln!("Database loaded {}", db_load_sw.elapsed());
63 let mut analysis_sw = self.stop_watch();
64 let mut num_crates = 0;
65 let mut visited_modules = FxHashSet::default();
66 let mut visit_queue = Vec::new();
68 let mut krates = Crate::all(db);
70 shuffle(&mut rng, &mut krates);
73 let module = krate.root_module(db);
74 let file_id = module.definition_source(db).file_id;
75 let file_id = file_id.original_file(db);
76 let source_root = db.file_source_root(file_id);
77 let source_root = db.source_root(source_root);
78 if !source_root.is_library || self.with_deps {
80 visit_queue.push(module);
85 shuffle(&mut rng, &mut visit_queue);
88 eprintln!("Crates in this dir: {}", num_crates);
89 let mut num_decls = 0;
90 let mut funcs = Vec::new();
91 while let Some(module) = visit_queue.pop() {
92 if visited_modules.insert(module) {
93 visit_queue.extend(module.children(db));
95 for decl in module.declarations(db) {
97 if let ModuleDef::Function(f) = decl {
102 for impl_def in module.impl_defs(db) {
103 for item in impl_def.items(db) {
105 if let AssocItem::Function(f) = item {
112 eprintln!("Total modules found: {}", visited_modules.len());
113 eprintln!("Total declarations: {}", num_decls);
114 eprintln!("Total functions: {}", funcs.len());
115 eprintln!("Item Collection: {}", analysis_sw.elapsed());
118 shuffle(&mut rng, &mut funcs);
121 let mut bar = match verbosity {
122 Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
123 _ if self.parallel => ProgressReport::hidden(),
124 _ => ProgressReport::new(funcs.len() as u64),
128 let mut inference_sw = self.stop_watch();
129 let snap = Snap(db.snapshot());
132 .map_with(snap, |snap, &f| {
133 let f_id = FunctionId::from(f);
134 snap.0.body(f_id.into());
135 snap.0.infer(f_id.into());
138 eprintln!("Parallel Inference: {}", inference_sw.elapsed());
141 let mut inference_sw = self.stop_watch();
143 let mut num_exprs = 0;
144 let mut num_exprs_unknown = 0;
145 let mut num_exprs_partially_unknown = 0;
146 let mut num_type_mismatches = 0;
148 let name = f.name(db);
154 .filter_map(|it| it.name(db))
155 .chain(Some(f.name(db)))
157 if let Some(only_name) = self.only.as_deref() {
158 if name.to_string() != only_name && full_name != only_name {
162 let mut msg = format!("processing: {}", full_name);
163 if verbosity.is_verbose() {
164 if let Some(src) = f.source(db) {
165 let original_file = src.file_id.original_file(db);
166 let path = vfs.file_path(original_file);
167 let syntax_range = src.value.syntax().text_range();
168 format_to!(msg, " ({} {:?})", path, syntax_range);
171 if verbosity.is_spammy() {
172 bar.println(msg.to_string());
174 bar.set_message(&msg);
175 let f_id = FunctionId::from(f);
176 let body = db.body(f_id.into());
177 let inference_result = db.infer(f_id.into());
178 let (previous_exprs, previous_unknown, previous_partially_unknown) =
179 (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
180 for (expr_id, _) in body.exprs.iter() {
181 let ty = &inference_result[expr_id];
183 if let Ty::Unknown = ty {
184 num_exprs_unknown += 1;
186 let mut is_partially_unknown = false;
188 if let Ty::Unknown = ty {
189 is_partially_unknown = true;
192 if is_partially_unknown {
193 num_exprs_partially_unknown += 1;
196 if self.only.is_some() && verbosity.is_spammy() {
197 // in super-verbose mode for just one function, we print every single expression
198 let (_, sm) = db.body_with_source_map(f_id.into());
199 let src = sm.expr_syntax(expr_id);
200 if let Ok(src) = src {
202 let root = db.parse_or_expand(src.file_id).unwrap();
203 src.value.to_node(&root)
205 let original_file = src.file_id.original_file(db);
206 let line_index = host.analysis().file_line_index(original_file).unwrap();
207 let text_range = node.syntax().text_range();
209 line_index.line_col(text_range.start()),
210 line_index.line_col(text_range.end()),
221 bar.println(format!("unknown location: {}", ty.display(db)));
224 if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
225 num_type_mismatches += 1;
226 if verbosity.is_verbose() {
227 let (_, sm) = db.body_with_source_map(f_id.into());
228 let src = sm.expr_syntax(expr_id);
229 if let Ok(src) = src {
230 // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
231 // But also, we should just turn the type mismatches into diagnostics and provide these
232 let root = db.parse_or_expand(src.file_id).unwrap();
233 let node = src.map(|e| e.to_node(&root).syntax().clone());
234 let original_range = node.as_ref().original_file_range(db);
235 let path = vfs.file_path(original_range.file_id);
237 host.analysis().file_line_index(original_range.file_id).unwrap();
238 let text_range = original_range.range;
240 line_index.line_col(text_range.start()),
241 line_index.line_col(text_range.end()),
244 "{} {}:{}-{}:{}: Expected {}, got {}",
250 mismatch.expected.display(db),
251 mismatch.actual.display(db)
255 "{}: Expected {}, got {}",
257 mismatch.expected.display(db),
258 mismatch.actual.display(db)
264 if verbosity.is_spammy() {
266 "In {}: {} exprs, {} unknown, {} partial",
268 num_exprs - previous_exprs,
269 num_exprs_unknown - previous_unknown,
270 num_exprs_partially_unknown - previous_partially_unknown
275 bar.finish_and_clear();
276 eprintln!("Total expressions: {}", num_exprs);
278 "Expressions of unknown type: {} ({}%)",
280 if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
282 report_metric("unknown type", num_exprs_unknown, "#");
285 "Expressions of partially unknown type: {} ({}%)",
286 num_exprs_partially_unknown,
287 if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
290 eprintln!("Type mismatches: {}", num_type_mismatches);
291 report_metric("type mismatches", num_type_mismatches, "#");
293 eprintln!("Inference: {}", inference_sw.elapsed());
295 let total_span = analysis_sw.elapsed();
296 eprintln!("Total: {}", total_span);
297 report_metric("total time", total_span.time.as_millis() as u64, "ms");
298 if let Some(instructions) = total_span.instructions {
299 report_metric("total instructions", instructions, "#instr");
301 if let Some(memory) = total_span.memory {
302 report_metric("total memory", memory.allocated.megabytes() as u64, "MB");
305 if self.memory_usage {
306 print_memory_usage(host, vfs);
312 fn stop_watch(&self) -> StopWatch {
313 StopWatch::start().memory(self.memory_usage)
317 fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
318 for i in 0..slice.len() {
319 randomize_first(rng, &mut slice[i..]);
322 fn randomize_first<T>(rng: &mut Rand32, slice: &mut [T]) {
323 assert!(!slice.is_empty());
324 let idx = rng.rand_range(0..slice.len() as u32) as usize;