Add an ra_cli command that analyses all crates in the current workspace

... and prints various stats about how many expressions have a type etc.
This commit is contained in:
Florian Diebold 2019-02-09 18:27:11 +01:00
parent 43e52ac9e2
commit 6964a88e8c
9 changed files with 227 additions and 4 deletions

27
Cargo.lock generated
View file

@ -486,6 +486,18 @@ name = "indexmap"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "indicatif"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"console 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "inotify"
version = "0.6.1"
@ -780,6 +792,14 @@ dependencies = [
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "number_prefix"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "owning_ref"
version = "0.4.0"
@ -930,7 +950,12 @@ version = "0.1.0"
dependencies = [
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"flexi_logger 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
"indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_batch 0.1.0",
"ra_db 0.1.0",
"ra_hir 0.1.0",
"ra_ide_api_light 0.1.0",
"ra_syntax 0.1.0",
"tools 0.1.0",
@ -1949,6 +1974,7 @@ dependencies = [
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
"checksum im 12.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0627d417829c1d763d602687634869f254fc79f7e22dea6c824dab993db857e4"
"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d"
"checksum indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2c60da1c9abea75996b70a931bba6c750730399005b61ccd853cee50ef3d0d0c"
"checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718"
"checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
"checksum insta 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcdfb5ab565a1fc5c397722d5a9503f2095696ef07ef1a222d85a0fd6666c6aa"
@ -1982,6 +2008,7 @@ dependencies = [
"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
"checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
"checksum number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dbf9993e59c894e3c08aa1c2712914e9e6bf1fcbfc6bef283e2183df345a4fee"
"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"

View file

@ -60,7 +60,11 @@ impl BatchDatabase {
match change {
VfsChange::AddRoot { root, files } => {
let source_root_id = vfs_root_to_id(root);
log::debug!("loaded source root {:?} with path {:?}", source_root_id, vfs.root2path(root));
log::debug!(
"loaded source root {:?} with path {:?}",
source_root_id,
vfs.root2path(root)
);
let mut file_map = FxHashMap::default();
for (vfs_file, path, text) in files {
let file_id = vfs_file_to_id(vfs_file);
@ -111,7 +115,8 @@ impl BatchDatabase {
let crate_graph = ws.to_crate_graph(&mut load);
log::debug!("crate graph: {:?}", crate_graph);
let local_roots = roots.into_iter()
let local_roots = roots
.into_iter()
.filter(|r| vfs.root2path(*r).starts_with(&root))
.map(vfs_root_to_id)
.collect();

View file

@ -9,6 +9,12 @@ publish = false
clap = "2.32.0"
failure = "0.1.4"
join_to_string = "0.1.1"
flexi_logger = "0.10.0"
indicatif = "0.11.0"
ra_syntax = { path = "../ra_syntax" }
ra_ide_api_light = { path = "../ra_ide_api_light" }
tools = { path = "../tools" }
ra_batch = { path = "../ra_batch" }
ra_hir = { path = "../ra_hir" }
ra_db = { path = "../ra_db" }

View file

@ -0,0 +1,100 @@
use std::collections::HashSet;
use ra_db::SourceDatabase;
use ra_batch::BatchDatabase;
use ra_hir::{Crate, ModuleDef, Ty, ImplItem};
use ra_syntax::AstNode;
use crate::Result;
pub fn run(verbose: bool) -> Result<()> {
let (db, roots) = BatchDatabase::load_cargo(".")?;
println!("Database loaded, {} roots", roots.len());
let mut num_crates = 0;
let mut visited_modules = HashSet::new();
let mut visit_queue = Vec::new();
for root in roots {
for krate in Crate::source_root_crates(&db, root) {
num_crates += 1;
let module = krate.root_module(&db).expect("crate in source root without root module");
visit_queue.push(module);
}
}
println!("Crates in this dir: {}", num_crates);
let mut num_decls = 0;
let mut funcs = Vec::new();
while let Some(module) = visit_queue.pop() {
if visited_modules.insert(module) {
visit_queue.extend(module.children(&db));
for decl in module.declarations(&db) {
num_decls += 1;
match decl {
ModuleDef::Function(f) => funcs.push(f),
_ => {}
}
}
for impl_block in module.impl_blocks(&db) {
for item in impl_block.items() {
num_decls += 1;
match item {
ImplItem::Method(f) => funcs.push(*f),
_ => {}
}
}
}
}
}
println!("Total modules found: {}", visited_modules.len());
println!("Total declarations: {}", num_decls);
println!("Total functions: {}", funcs.len());
let bar = indicatif::ProgressBar::new(funcs.len() as u64);
bar.tick();
let mut num_exprs = 0;
let mut num_exprs_unknown = 0;
let mut num_exprs_partially_unknown = 0;
for f in funcs {
if verbose {
let (file_id, source) = f.source(&db);
let original_file = file_id.original_file(&db);
let path = db.file_relative_path(original_file);
let syntax_range = source.syntax().range();
let name = f.name(&db);
println!("{} ({:?} {})", name, path, syntax_range);
}
let body = f.body(&db);
let inference_result = f.infer(&db);
for (expr_id, _) in body.exprs() {
let ty = &inference_result[expr_id];
num_exprs += 1;
if let Ty::Unknown = ty {
num_exprs_unknown += 1;
} else {
let mut is_partially_unknown = false;
ty.walk(&mut |ty| {
if let Ty::Unknown = ty {
is_partially_unknown = true;
}
});
if is_partially_unknown {
num_exprs_partially_unknown += 1;
}
}
}
bar.inc(1);
}
bar.finish_and_clear();
println!("Total expressions: {}", num_exprs);
println!(
"Expressions of unknown type: {} ({}%)",
num_exprs_unknown,
(num_exprs_unknown * 100 / num_exprs)
);
println!(
"Expressions of partially unknown type: {} ({}%)",
num_exprs_partially_unknown,
(num_exprs_partially_unknown * 100 / num_exprs)
);
Ok(())
}

View file

@ -1,3 +1,5 @@
mod analysis_stats;
use std::{fs, io::Read, path::Path, time::Instant};
use clap::{App, Arg, SubCommand};
@ -5,10 +7,12 @@ use join_to_string::join;
use ra_ide_api_light::{extend_selection, file_structure, syntax_tree};
use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode};
use tools::collect_tests;
use flexi_logger::Logger;
type Result<T> = ::std::result::Result<T, failure::Error>;
fn main() -> Result<()> {
Logger::with_env().start()?;
let matches = App::new("ra-cli")
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
.subcommand(
@ -23,6 +27,9 @@ fn main() -> Result<()> {
.arg(Arg::with_name("start"))
.arg(Arg::with_name("end")),
)
.subcommand(
SubCommand::with_name("analysis-stats").arg(Arg::with_name("verbose").short("v")),
)
.get_matches();
match matches.subcommand() {
("parse", Some(matches)) => {
@ -56,6 +63,10 @@ fn main() -> Result<()> {
let sels = selections(&file, start, end);
println!("{}", sels)
}
("analysis-stats", Some(matches)) => {
let verbose = matches.is_present("verbose");
analysis_stats::run(verbose)?;
}
_ => unreachable!(),
}
Ok(())

View file

@ -112,6 +112,7 @@ impl CrateGraph {
self.arena[&crate_id].file_id
}
// TODO: this only finds one crate with the given root; we could have multiple
pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> {
let (&crate_id, _) = self.arena.iter().find(|(_crate_id, data)| data.file_id == file_id)?;
Some(crate_id)

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use relative_path::RelativePathBuf;
use ra_db::{CrateId, FileId};
use ra_db::{CrateId, FileId, SourceRootId};
use ra_syntax::{ast::self, TreeArc, SyntaxNode};
use crate::{
@ -16,7 +16,7 @@ use crate::{
docs::{Documentation, Docs, docs_from_ast},
module_tree::ModuleId,
ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId},
impl_block::ImplId,
impl_block::{ImplId, ImplBlock},
resolve::Resolver,
};
@ -44,6 +44,15 @@ impl Crate {
pub fn root_module(&self, db: &impl PersistentHirDatabase) -> Option<Module> {
self.root_module_impl(db)
}
// TODO: should this be in source_binder?
pub fn source_root_crates(
db: &impl PersistentHirDatabase,
source_root: SourceRootId,
) -> Vec<Crate> {
let crate_ids = db.source_root_crates(source_root);
crate_ids.iter().map(|&crate_id| Crate { crate_id }).collect()
}
}
#[derive(Debug)]
@ -168,6 +177,27 @@ impl Module {
let item_map = db.item_map(self.krate);
Resolver::default().push_module_scope(item_map, *self)
}
pub fn declarations(self, db: &impl HirDatabase) -> Vec<ModuleDef> {
let (lowered_module, _) = db.lower_module(self);
lowered_module
.declarations
.values()
.cloned()
.flat_map(|per_ns| {
per_ns.take_types().into_iter().chain(per_ns.take_values().into_iter())
})
.collect()
}
pub fn impl_blocks(self, db: &impl HirDatabase) -> Vec<ImplBlock> {
let module_impl_blocks = db.impls_in_module(self);
module_impl_blocks
.impls
.iter()
.map(|(impl_id, _)| ImplBlock::from_id(module_impl_blocks.clone(), impl_id))
.collect()
}
}
impl Docs for Module {

View file

@ -70,6 +70,14 @@ impl Body {
self.owner
}
pub fn exprs(&self) -> impl Iterator<Item = (ExprId, &Expr)> {
self.exprs.iter()
}
pub fn pats(&self) -> impl Iterator<Item = (PatId, &Pat)> {
self.pats.iter()
}
pub fn syntax_mapping(&self, db: &impl HirDatabase) -> Arc<BodySyntaxMapping> {
db.body_syntax_mapping(self.owner)
}

View file

@ -449,6 +449,41 @@ impl Ty {
Ty::Tuple(Arc::new([]))
}
pub fn walk(&self, f: &mut impl FnMut(&Ty)) {
f(self);
match self {
Ty::Slice(t) | Ty::Array(t) => t.walk(f),
Ty::RawPtr(t, _) => t.walk(f),
Ty::Ref(t, _) => t.walk(f),
Ty::Tuple(ts) => {
for t in ts.iter() {
t.walk(f);
}
}
Ty::FnPtr(sig) => {
for input in &sig.input {
input.walk(f);
}
sig.output.walk(f);
}
Ty::FnDef { substs, sig, .. } => {
for input in &sig.input {
input.walk(f);
}
sig.output.walk(f);
for t in substs.0.iter() {
t.walk(f);
}
}
Ty::Adt { substs, .. } => {
for t in substs.0.iter() {
t.walk(f);
}
}
_ => {}
}
}
fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) {
f(self);
match self {