Skip to content

Commit 8e4be27

Browse files
bors[bot]flodiebold
andcommitted
Merge #774
774: Batch crate & command r=matklad a=flodiebold This adds a new crate, `ra_batch`, which is intended for scenarios where you're loading a workspace once and then running some analyses using the HIR API. Also, it adds a command to `ra_cli` which uses that to type-check all crates in a workspace and print some statistics: E.g. in rust-analyzer: ``` > $ time target/release/ra_cli analysis-stats Database loaded, 21 roots Crates in this dir: 28 Total modules found: 231 Total declarations: 3694 Total functions: 2408 Total expressions: 47017 Expressions of unknown type: 19826 (42%) Expressions of partially unknown type: 4482 (9%) target/release/ra_cli analysis-stats 3,23s user 0,60s system 100% cpu 3,821 total ``` Or in rust-lang/rust: ``` > $ time ../opensource/rust-analyzer/target/release/ra_cli analysis-stats Database loaded, 77 roots Crates in this dir: 130 Total modules found: 1820 Total declarations: 35038 Total functions: 25914 Total expressions: 753678 Expressions of unknown type: 337975 (44%) Expressions of partially unknown type: 92314 (12%) ../opensource/rust-analyzer/target/release/ra_cli analysis-stats 13,45s user 2,08s system 100% cpu 15,477 total ``` ~This still needs a test. Type-checking all of rust-analyzer sadly takes almost a minute when compiled in debug mode 😅 So I'll need to add something simpler (maybe just looking at a few modules).~ Co-authored-by: Florian Diebold <flodiebold@gmail.com>
2 parents 01b15c9 + 2e9194a commit 8e4be27

File tree

12 files changed

+429
-6
lines changed

12 files changed

+429
-6
lines changed

Cargo.lock

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_batch/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
edition = "2018"
3+
name = "ra_batch"
4+
version = "0.1.0"
5+
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
6+
7+
[dependencies]
8+
log = "0.4.5"
9+
rustc-hash = "1.0"
10+
11+
failure = "0.1.4"
12+
13+
ra_syntax = { path = "../ra_syntax" }
14+
ra_db = { path = "../ra_db" }
15+
ra_hir = { path = "../ra_hir" }
16+
ra_vfs = { path = "../ra_vfs" }
17+
ra_project_model = { path = "../ra_project_model" }
18+
19+
[dev-dependencies]
20+
test_utils = { path = "../test_utils" }

crates/ra_batch/src/lib.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use std::sync::Arc;
2+
use std::path::Path;
3+
use std::collections::HashSet;
4+
5+
use rustc_hash::FxHashMap;
6+
7+
use ra_db::{
8+
CrateGraph, FileId, SourceRoot, SourceRootId, SourceDatabase, salsa,
9+
};
10+
use ra_hir::{db, HirInterner};
11+
use ra_project_model::ProjectWorkspace;
12+
use ra_vfs::{Vfs, VfsChange};
13+
14+
type Result<T> = std::result::Result<T, failure::Error>;
15+
16+
#[salsa::database(
17+
ra_db::SourceDatabaseStorage,
18+
db::HirDatabaseStorage,
19+
db::PersistentHirDatabaseStorage
20+
)]
21+
#[derive(Debug)]
22+
pub struct BatchDatabase {
23+
runtime: salsa::Runtime<BatchDatabase>,
24+
interner: Arc<HirInterner>,
25+
}
26+
27+
impl salsa::Database for BatchDatabase {
28+
fn salsa_runtime(&self) -> &salsa::Runtime<BatchDatabase> {
29+
&self.runtime
30+
}
31+
}
32+
33+
impl AsRef<HirInterner> for BatchDatabase {
34+
fn as_ref(&self) -> &HirInterner {
35+
&self.interner
36+
}
37+
}
38+
39+
fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId {
40+
FileId(f.0.into())
41+
}
42+
fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
43+
SourceRootId(r.0.into())
44+
}
45+
46+
impl BatchDatabase {
47+
pub fn load(crate_graph: CrateGraph, vfs: &mut Vfs) -> BatchDatabase {
48+
let mut db =
49+
BatchDatabase { runtime: salsa::Runtime::default(), interner: Default::default() };
50+
db.set_crate_graph(Arc::new(crate_graph));
51+
52+
// wait until Vfs has loaded all roots
53+
let receiver = vfs.task_receiver().clone();
54+
let mut roots_loaded = HashSet::new();
55+
for task in receiver {
56+
vfs.handle_task(task);
57+
let mut done = false;
58+
for change in vfs.commit_changes() {
59+
match change {
60+
VfsChange::AddRoot { root, files } => {
61+
let source_root_id = vfs_root_to_id(root);
62+
log::debug!(
63+
"loaded source root {:?} with path {:?}",
64+
source_root_id,
65+
vfs.root2path(root)
66+
);
67+
let mut file_map = FxHashMap::default();
68+
for (vfs_file, path, text) in files {
69+
let file_id = vfs_file_to_id(vfs_file);
70+
db.set_file_text(file_id, text);
71+
db.set_file_relative_path(file_id, path.clone());
72+
db.set_file_source_root(file_id, source_root_id);
73+
file_map.insert(path, file_id);
74+
}
75+
let source_root = SourceRoot { files: file_map };
76+
db.set_source_root(source_root_id, Arc::new(source_root));
77+
roots_loaded.insert(source_root_id);
78+
if roots_loaded.len() == vfs.num_roots() {
79+
done = true;
80+
}
81+
}
82+
VfsChange::AddFile { .. }
83+
| VfsChange::RemoveFile { .. }
84+
| VfsChange::ChangeFile { .. } => {
85+
// We just need the first scan, so just ignore these
86+
}
87+
}
88+
}
89+
if done {
90+
break;
91+
}
92+
}
93+
94+
db
95+
}
96+
97+
pub fn load_cargo(root: impl AsRef<Path>) -> Result<(BatchDatabase, Vec<SourceRootId>)> {
98+
let root = root.as_ref().canonicalize()?;
99+
let ws = ProjectWorkspace::discover(root.as_ref())?;
100+
let mut roots = Vec::new();
101+
roots.push(root.clone());
102+
for pkg in ws.cargo.packages() {
103+
roots.push(pkg.root(&ws.cargo).to_path_buf());
104+
}
105+
for krate in ws.sysroot.crates() {
106+
roots.push(krate.root_dir(&ws.sysroot).to_path_buf())
107+
}
108+
let (mut vfs, roots) = Vfs::new(roots);
109+
let mut load = |path: &Path| {
110+
let vfs_file = vfs.load(path);
111+
log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
112+
vfs_file.map(vfs_file_to_id)
113+
};
114+
let crate_graph = ws.to_crate_graph(&mut load);
115+
log::debug!("crate graph: {:?}", crate_graph);
116+
117+
let local_roots = roots
118+
.into_iter()
119+
.filter(|r| vfs.root2path(*r).starts_with(&root))
120+
.map(vfs_root_to_id)
121+
.collect();
122+
123+
let db = BatchDatabase::load(crate_graph, &mut vfs);
124+
let _ = vfs.shutdown();
125+
Ok((db, local_roots))
126+
}
127+
}
128+
129+
#[cfg(test)]
130+
mod tests {
131+
use ra_hir::Crate;
132+
use super::*;
133+
134+
#[test]
135+
fn test_loading_rust_analyzer() {
136+
let mut path = std::env::current_exe().unwrap();
137+
while !path.join("Cargo.toml").is_file() {
138+
path = path.parent().unwrap().to_owned();
139+
}
140+
let (db, roots) = BatchDatabase::load_cargo(path).unwrap();
141+
let mut num_crates = 0;
142+
for root in roots {
143+
for _krate in Crate::source_root_crates(&db, root) {
144+
num_crates += 1;
145+
}
146+
}
147+
148+
// RA has quite a few crates, but the exact count doesn't matter
149+
assert!(num_crates > 20);
150+
}
151+
}

crates/ra_cli/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ publish = false
99
clap = "2.32.0"
1010
failure = "0.1.4"
1111
join_to_string = "0.1.1"
12+
flexi_logger = "0.10.0"
13+
indicatif = "0.11.0"
14+
1215
ra_syntax = { path = "../ra_syntax" }
1316
ra_ide_api_light = { path = "../ra_ide_api_light" }
1417
tools = { path = "../tools" }
18+
ra_batch = { path = "../ra_batch" }
19+
ra_hir = { path = "../ra_hir" }
20+
ra_db = { path = "../ra_db" }

crates/ra_cli/src/analysis_stats.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::collections::HashSet;
2+
3+
use ra_db::SourceDatabase;
4+
use ra_batch::BatchDatabase;
5+
use ra_hir::{Crate, ModuleDef, Ty, ImplItem};
6+
use ra_syntax::AstNode;
7+
8+
use crate::Result;
9+
10+
pub fn run(verbose: bool) -> Result<()> {
11+
let (db, roots) = BatchDatabase::load_cargo(".")?;
12+
println!("Database loaded, {} roots", roots.len());
13+
let mut num_crates = 0;
14+
let mut visited_modules = HashSet::new();
15+
let mut visit_queue = Vec::new();
16+
for root in roots {
17+
for krate in Crate::source_root_crates(&db, root) {
18+
num_crates += 1;
19+
let module = krate.root_module(&db).expect("crate in source root without root module");
20+
visit_queue.push(module);
21+
}
22+
}
23+
println!("Crates in this dir: {}", num_crates);
24+
let mut num_decls = 0;
25+
let mut funcs = Vec::new();
26+
while let Some(module) = visit_queue.pop() {
27+
if visited_modules.insert(module) {
28+
visit_queue.extend(module.children(&db));
29+
30+
for decl in module.declarations(&db) {
31+
num_decls += 1;
32+
match decl {
33+
ModuleDef::Function(f) => funcs.push(f),
34+
_ => {}
35+
}
36+
}
37+
38+
for impl_block in module.impl_blocks(&db) {
39+
for item in impl_block.items() {
40+
num_decls += 1;
41+
match item {
42+
ImplItem::Method(f) => funcs.push(*f),
43+
_ => {}
44+
}
45+
}
46+
}
47+
}
48+
}
49+
println!("Total modules found: {}", visited_modules.len());
50+
println!("Total declarations: {}", num_decls);
51+
println!("Total functions: {}", funcs.len());
52+
let bar = indicatif::ProgressBar::new(funcs.len() as u64);
53+
bar.tick();
54+
let mut num_exprs = 0;
55+
let mut num_exprs_unknown = 0;
56+
let mut num_exprs_partially_unknown = 0;
57+
for f in funcs {
58+
if verbose {
59+
let (file_id, source) = f.source(&db);
60+
let original_file = file_id.original_file(&db);
61+
let path = db.file_relative_path(original_file);
62+
let syntax_range = source.syntax().range();
63+
let name = f.name(&db);
64+
println!("{} ({:?} {})", name, path, syntax_range);
65+
}
66+
let body = f.body(&db);
67+
let inference_result = f.infer(&db);
68+
for (expr_id, _) in body.exprs() {
69+
let ty = &inference_result[expr_id];
70+
num_exprs += 1;
71+
if let Ty::Unknown = ty {
72+
num_exprs_unknown += 1;
73+
} else {
74+
let mut is_partially_unknown = false;
75+
ty.walk(&mut |ty| {
76+
if let Ty::Unknown = ty {
77+
is_partially_unknown = true;
78+
}
79+
});
80+
if is_partially_unknown {
81+
num_exprs_partially_unknown += 1;
82+
}
83+
}
84+
}
85+
bar.inc(1);
86+
}
87+
bar.finish_and_clear();
88+
println!("Total expressions: {}", num_exprs);
89+
println!(
90+
"Expressions of unknown type: {} ({}%)",
91+
num_exprs_unknown,
92+
(num_exprs_unknown * 100 / num_exprs)
93+
);
94+
println!(
95+
"Expressions of partially unknown type: {} ({}%)",
96+
num_exprs_partially_unknown,
97+
(num_exprs_partially_unknown * 100 / num_exprs)
98+
);
99+
Ok(())
100+
}

0 commit comments

Comments
 (0)