]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/cli/analysis_stats.rs
Update completions test output
[rust.git] / crates / rust-analyzer / src / cli / analysis_stats.rs
1 //! Fully type-check project and print various stats, like the number of type
2 //! errors.
3
4 use std::{
5     path::PathBuf,
6     time::{SystemTime, UNIX_EPOCH},
7 };
8
9 use hir::{
10     db::{AstDatabase, DefDatabase, HirDatabase},
11     AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
12 };
13 use hir_def::FunctionId;
14 use hir_ty::{Ty, TypeWalk};
15 use ide_db::base_db::{
16     salsa::{self, ParallelDatabase},
17     SourceDatabaseExt,
18 };
19 use itertools::Itertools;
20 use oorandom::Rand32;
21 use rayon::prelude::*;
22 use rustc_hash::FxHashSet;
23 use stdx::format_to;
24 use syntax::AstNode;
25
26 use crate::cli::{
27     load_cargo::load_cargo, print_memory_usage, progress_report::ProgressReport, report_metric,
28     Result, Verbosity,
29 };
30 use profile::StopWatch;
31
32 /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
33 struct Snap<DB>(DB);
34 impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
35     fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
36         Snap(self.0.snapshot())
37     }
38 }
39
40 pub struct AnalysisStatsCmd {
41     pub randomize: bool,
42     pub parallel: bool,
43     pub memory_usage: bool,
44     pub only: Option<String>,
45     pub with_deps: bool,
46     pub path: PathBuf,
47     pub load_output_dirs: bool,
48     pub with_proc_macro: bool,
49 }
50
51 impl AnalysisStatsCmd {
52     pub fn run(self, verbosity: Verbosity) -> Result<()> {
53         let mut rng = {
54             let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
55             Rand32::new(seed)
56         };
57
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!("{:<20} {}", "Database loaded:", db_load_sw.elapsed());
62
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();
67
68         let mut krates = Crate::all(db);
69         if self.randomize {
70             shuffle(&mut rng, &mut krates);
71         }
72         for krate in 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 {
79                 num_crates += 1;
80                 visit_queue.push(module);
81             }
82         }
83
84         if self.randomize {
85             shuffle(&mut rng, &mut visit_queue);
86         }
87
88         eprint!("  crates: {}", 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));
94
95                 for decl in module.declarations(db) {
96                     num_decls += 1;
97                     if let ModuleDef::Function(f) = decl {
98                         funcs.push(f);
99                     }
100                 }
101
102                 for impl_def in module.impl_defs(db) {
103                     for item in impl_def.items(db) {
104                         num_decls += 1;
105                         if let AssocItem::Function(f) = item {
106                             funcs.push(f);
107                         }
108                     }
109                 }
110             }
111         }
112         eprintln!(", mods: {}, decls: {}, fns: {}", visited_modules.len(), num_decls, funcs.len());
113         eprintln!("{:<20} {}", "Item Collection:", analysis_sw.elapsed());
114
115         if self.randomize {
116             shuffle(&mut rng, &mut funcs);
117         }
118
119         let mut bar = match verbosity {
120             Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
121             _ if self.parallel => ProgressReport::hidden(),
122             _ => ProgressReport::new(funcs.len() as u64),
123         };
124
125         if self.parallel {
126             let mut inference_sw = self.stop_watch();
127             let snap = Snap(db.snapshot());
128             funcs
129                 .par_iter()
130                 .map_with(snap, |snap, &f| {
131                     let f_id = FunctionId::from(f);
132                     snap.0.body(f_id.into());
133                     snap.0.infer(f_id.into());
134                 })
135                 .count();
136             eprintln!("{:<20} {}", "Parallel Inference:", inference_sw.elapsed());
137         }
138
139         let mut inference_sw = self.stop_watch();
140         bar.tick();
141         let mut num_exprs = 0;
142         let mut num_exprs_unknown = 0;
143         let mut num_exprs_partially_unknown = 0;
144         let mut num_type_mismatches = 0;
145         for f in funcs {
146             let name = f.name(db);
147             let full_name = f
148                 .module(db)
149                 .path_to_root(db)
150                 .into_iter()
151                 .rev()
152                 .filter_map(|it| it.name(db))
153                 .chain(Some(f.name(db)))
154                 .join("::");
155             if let Some(only_name) = self.only.as_deref() {
156                 if name.to_string() != only_name && full_name != only_name {
157                     continue;
158                 }
159             }
160             let mut msg = format!("processing: {}", full_name);
161             if verbosity.is_verbose() {
162                 if let Some(src) = f.source(db) {
163                     let original_file = src.file_id.original_file(db);
164                     let path = vfs.file_path(original_file);
165                     let syntax_range = src.value.syntax().text_range();
166                     format_to!(msg, " ({} {:?})", path, syntax_range);
167                 }
168             }
169             if verbosity.is_spammy() {
170                 bar.println(msg.to_string());
171             }
172             bar.set_message(&msg);
173             let f_id = FunctionId::from(f);
174             let body = db.body(f_id.into());
175             let inference_result = db.infer(f_id.into());
176             let (previous_exprs, previous_unknown, previous_partially_unknown) =
177                 (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
178             for (expr_id, _) in body.exprs.iter() {
179                 let ty = &inference_result[expr_id];
180                 num_exprs += 1;
181                 if let Ty::Unknown = ty {
182                     num_exprs_unknown += 1;
183                 } else {
184                     let mut is_partially_unknown = false;
185                     ty.walk(&mut |ty| {
186                         if let Ty::Unknown = ty {
187                             is_partially_unknown = true;
188                         }
189                     });
190                     if is_partially_unknown {
191                         num_exprs_partially_unknown += 1;
192                     }
193                 }
194                 if self.only.is_some() && verbosity.is_spammy() {
195                     // in super-verbose mode for just one function, we print every single expression
196                     let (_, sm) = db.body_with_source_map(f_id.into());
197                     let src = sm.expr_syntax(expr_id);
198                     if let Ok(src) = src {
199                         let node = {
200                             let root = db.parse_or_expand(src.file_id).unwrap();
201                             src.value.to_node(&root)
202                         };
203                         let original_file = src.file_id.original_file(db);
204                         let line_index = host.analysis().file_line_index(original_file).unwrap();
205                         let text_range = node.syntax().text_range();
206                         let (start, end) = (
207                             line_index.line_col(text_range.start()),
208                             line_index.line_col(text_range.end()),
209                         );
210                         bar.println(format!(
211                             "{}:{}-{}:{}: {}",
212                             start.line + 1,
213                             start.col_utf16,
214                             end.line + 1,
215                             end.col_utf16,
216                             ty.display(db)
217                         ));
218                     } else {
219                         bar.println(format!("unknown location: {}", ty.display(db)));
220                     }
221                 }
222                 if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
223                     num_type_mismatches += 1;
224                     if verbosity.is_verbose() {
225                         let (_, sm) = db.body_with_source_map(f_id.into());
226                         let src = sm.expr_syntax(expr_id);
227                         if let Ok(src) = src {
228                             // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
229                             // But also, we should just turn the type mismatches into diagnostics and provide these
230                             let root = db.parse_or_expand(src.file_id).unwrap();
231                             let node = src.map(|e| e.to_node(&root).syntax().clone());
232                             let original_range = node.as_ref().original_file_range(db);
233                             let path = vfs.file_path(original_range.file_id);
234                             let line_index =
235                                 host.analysis().file_line_index(original_range.file_id).unwrap();
236                             let text_range = original_range.range;
237                             let (start, end) = (
238                                 line_index.line_col(text_range.start()),
239                                 line_index.line_col(text_range.end()),
240                             );
241                             bar.println(format!(
242                                 "{} {}:{}-{}:{}: Expected {}, got {}",
243                                 path,
244                                 start.line + 1,
245                                 start.col_utf16,
246                                 end.line + 1,
247                                 end.col_utf16,
248                                 mismatch.expected.display(db),
249                                 mismatch.actual.display(db)
250                             ));
251                         } else {
252                             bar.println(format!(
253                                 "{}: Expected {}, got {}",
254                                 name,
255                                 mismatch.expected.display(db),
256                                 mismatch.actual.display(db)
257                             ));
258                         }
259                     }
260                 }
261             }
262             if verbosity.is_spammy() {
263                 bar.println(format!(
264                     "In {}: {} exprs, {} unknown, {} partial",
265                     full_name,
266                     num_exprs - previous_exprs,
267                     num_exprs_unknown - previous_unknown,
268                     num_exprs_partially_unknown - previous_partially_unknown
269                 ));
270             }
271             bar.inc(1);
272         }
273         bar.finish_and_clear();
274         eprintln!(
275             "  exprs: {}, ??ty: {} ({}%), ?ty: {} ({}%), !ty: {}",
276             num_exprs,
277             num_exprs_unknown,
278             percentage(num_exprs_unknown, num_exprs),
279             num_exprs_partially_unknown,
280             percentage(num_exprs_partially_unknown, num_exprs),
281             num_type_mismatches
282         );
283         report_metric("unknown type", num_exprs_unknown, "#");
284         report_metric("type mismatches", num_type_mismatches, "#");
285
286         eprintln!("{:<20} {}", "Inference:", inference_sw.elapsed());
287
288         let total_span = analysis_sw.elapsed();
289         eprintln!("{:<20} {}", "Total:", total_span);
290         report_metric("total time", total_span.time.as_millis() as u64, "ms");
291         if let Some(instructions) = total_span.instructions {
292             report_metric("total instructions", instructions, "#instr");
293         }
294         if let Some(memory) = total_span.memory {
295             report_metric("total memory", memory.allocated.megabytes() as u64, "MB");
296         }
297
298         if self.memory_usage && verbosity.is_verbose() {
299             print_memory_usage(host, vfs);
300         }
301
302         Ok(())
303     }
304
305     fn stop_watch(&self) -> StopWatch {
306         StopWatch::start().memory(self.memory_usage)
307     }
308 }
309
310 fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
311     for i in 0..slice.len() {
312         randomize_first(rng, &mut slice[i..]);
313     }
314
315     fn randomize_first<T>(rng: &mut Rand32, slice: &mut [T]) {
316         assert!(!slice.is_empty());
317         let idx = rng.rand_range(0..slice.len() as u32) as usize;
318         slice.swap(0, idx);
319     }
320 }
321
322 fn percentage(n: u64, total: u64) -> u64 {
323     (n * 100).checked_div(total).unwrap_or(100)
324 }