aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-02-10 11:42:42 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-02-10 11:42:42 +0000
commit8e4be2708635818aa3e210f0e39fb871cc433004 (patch)
tree569110cbb504c0516b136c414610b0f2edbe5044 /crates
parent01b15c9fc2ce128149872ffe02de022bdb157286 (diff)
parent2e9194a621ccb33872d6189ecc30a83c17e6e33a (diff)
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 :sweat_smile: So I'll need to add something simpler (maybe just looking at a few modules).~ Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_batch/Cargo.toml20
-rw-r--r--crates/ra_batch/src/lib.rs151
-rw-r--r--crates/ra_cli/Cargo.toml6
-rw-r--r--crates/ra_cli/src/analysis_stats.rs100
-rw-r--r--crates/ra_cli/src/main.rs11
-rw-r--r--crates/ra_db/src/input.rs1
-rw-r--r--crates/ra_hir/src/code_model_api.rs34
-rw-r--r--crates/ra_hir/src/expr.rs8
-rw-r--r--crates/ra_hir/src/ty.rs53
-rw-r--r--crates/ra_lsp_server/src/server_world.rs4
-rw-r--r--crates/ra_vfs/src/lib.rs5
11 files changed, 387 insertions, 6 deletions
diff --git a/crates/ra_batch/Cargo.toml b/crates/ra_batch/Cargo.toml
new file mode 100644
index 000000000..30a0749c7
--- /dev/null
+++ b/crates/ra_batch/Cargo.toml
@@ -0,0 +1,20 @@
1[package]
2edition = "2018"
3name = "ra_batch"
4version = "0.1.0"
5authors = ["Aleksey Kladov <[email protected]>"]
6
7[dependencies]
8log = "0.4.5"
9rustc-hash = "1.0"
10
11failure = "0.1.4"
12
13ra_syntax = { path = "../ra_syntax" }
14ra_db = { path = "../ra_db" }
15ra_hir = { path = "../ra_hir" }
16ra_vfs = { path = "../ra_vfs" }
17ra_project_model = { path = "../ra_project_model" }
18
19[dev-dependencies]
20test_utils = { path = "../test_utils" }
diff --git a/crates/ra_batch/src/lib.rs b/crates/ra_batch/src/lib.rs
new file mode 100644
index 000000000..837fff4dc
--- /dev/null
+++ b/crates/ra_batch/src/lib.rs
@@ -0,0 +1,151 @@
1use std::sync::Arc;
2use std::path::Path;
3use std::collections::HashSet;
4
5use rustc_hash::FxHashMap;
6
7use ra_db::{
8 CrateGraph, FileId, SourceRoot, SourceRootId, SourceDatabase, salsa,
9};
10use ra_hir::{db, HirInterner};
11use ra_project_model::ProjectWorkspace;
12use ra_vfs::{Vfs, VfsChange};
13
14type 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)]
22pub struct BatchDatabase {
23 runtime: salsa::Runtime<BatchDatabase>,
24 interner: Arc<HirInterner>,
25}
26
27impl salsa::Database for BatchDatabase {
28 fn salsa_runtime(&self) -> &salsa::Runtime<BatchDatabase> {
29 &self.runtime
30 }
31}
32
33impl AsRef<HirInterner> for BatchDatabase {
34 fn as_ref(&self) -> &HirInterner {
35 &self.interner
36 }
37}
38
39fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId {
40 FileId(f.0.into())
41}
42fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
43 SourceRootId(r.0.into())
44}
45
46impl 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)]
130mod 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}
diff --git a/crates/ra_cli/Cargo.toml b/crates/ra_cli/Cargo.toml
index eb1722d5e..641ac5cbd 100644
--- a/crates/ra_cli/Cargo.toml
+++ b/crates/ra_cli/Cargo.toml
@@ -9,6 +9,12 @@ publish = false
9clap = "2.32.0" 9clap = "2.32.0"
10failure = "0.1.4" 10failure = "0.1.4"
11join_to_string = "0.1.1" 11join_to_string = "0.1.1"
12flexi_logger = "0.10.0"
13indicatif = "0.11.0"
14
12ra_syntax = { path = "../ra_syntax" } 15ra_syntax = { path = "../ra_syntax" }
13ra_ide_api_light = { path = "../ra_ide_api_light" } 16ra_ide_api_light = { path = "../ra_ide_api_light" }
14tools = { path = "../tools" } 17tools = { path = "../tools" }
18ra_batch = { path = "../ra_batch" }
19ra_hir = { path = "../ra_hir" }
20ra_db = { path = "../ra_db" }
diff --git a/crates/ra_cli/src/analysis_stats.rs b/crates/ra_cli/src/analysis_stats.rs
new file mode 100644
index 000000000..a46ac974d
--- /dev/null
+++ b/crates/ra_cli/src/analysis_stats.rs
@@ -0,0 +1,100 @@
1use std::collections::HashSet;
2
3use ra_db::SourceDatabase;
4use ra_batch::BatchDatabase;
5use ra_hir::{Crate, ModuleDef, Ty, ImplItem};
6use ra_syntax::AstNode;
7
8use crate::Result;
9
10pub 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}
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs
index a4debeb48..72e6ae4d5 100644
--- a/crates/ra_cli/src/main.rs
+++ b/crates/ra_cli/src/main.rs
@@ -1,3 +1,5 @@
1mod analysis_stats;
2
1use std::{fs, io::Read, path::Path, time::Instant}; 3use std::{fs, io::Read, path::Path, time::Instant};
2 4
3use clap::{App, Arg, SubCommand}; 5use clap::{App, Arg, SubCommand};
@@ -5,10 +7,12 @@ use join_to_string::join;
5use ra_ide_api_light::{extend_selection, file_structure, syntax_tree}; 7use ra_ide_api_light::{extend_selection, file_structure, syntax_tree};
6use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode}; 8use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode};
7use tools::collect_tests; 9use tools::collect_tests;
10use flexi_logger::Logger;
8 11
9type Result<T> = ::std::result::Result<T, failure::Error>; 12type Result<T> = ::std::result::Result<T, failure::Error>;
10 13
11fn main() -> Result<()> { 14fn main() -> Result<()> {
15 Logger::with_env().start()?;
12 let matches = App::new("ra-cli") 16 let matches = App::new("ra-cli")
13 .setting(clap::AppSettings::SubcommandRequiredElseHelp) 17 .setting(clap::AppSettings::SubcommandRequiredElseHelp)
14 .subcommand( 18 .subcommand(
@@ -23,6 +27,9 @@ fn main() -> Result<()> {
23 .arg(Arg::with_name("start")) 27 .arg(Arg::with_name("start"))
24 .arg(Arg::with_name("end")), 28 .arg(Arg::with_name("end")),
25 ) 29 )
30 .subcommand(
31 SubCommand::with_name("analysis-stats").arg(Arg::with_name("verbose").short("v")),
32 )
26 .get_matches(); 33 .get_matches();
27 match matches.subcommand() { 34 match matches.subcommand() {
28 ("parse", Some(matches)) => { 35 ("parse", Some(matches)) => {
@@ -56,6 +63,10 @@ fn main() -> Result<()> {
56 let sels = selections(&file, start, end); 63 let sels = selections(&file, start, end);
57 println!("{}", sels) 64 println!("{}", sels)
58 } 65 }
66 ("analysis-stats", Some(matches)) => {
67 let verbose = matches.is_present("verbose");
68 analysis_stats::run(verbose)?;
69 }
59 _ => unreachable!(), 70 _ => unreachable!(),
60 } 71 }
61 Ok(()) 72 Ok(())
diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs
index 405634fe0..8decc65c5 100644
--- a/crates/ra_db/src/input.rs
+++ b/crates/ra_db/src/input.rs
@@ -112,6 +112,7 @@ impl CrateGraph {
112 self.arena[&crate_id].file_id 112 self.arena[&crate_id].file_id
113 } 113 }
114 114
115 // TODO: this only finds one crate with the given root; we could have multiple
115 pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> { 116 pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> {
116 let (&crate_id, _) = self.arena.iter().find(|(_crate_id, data)| data.file_id == file_id)?; 117 let (&crate_id, _) = self.arena.iter().find(|(_crate_id, data)| data.file_id == file_id)?;
117 Some(crate_id) 118 Some(crate_id)
diff --git a/crates/ra_hir/src/code_model_api.rs b/crates/ra_hir/src/code_model_api.rs
index cafc5279d..19f103855 100644
--- a/crates/ra_hir/src/code_model_api.rs
+++ b/crates/ra_hir/src/code_model_api.rs
@@ -1,7 +1,7 @@
1use std::sync::Arc; 1use std::sync::Arc;
2 2
3use relative_path::RelativePathBuf; 3use relative_path::RelativePathBuf;
4use ra_db::{CrateId, FileId}; 4use ra_db::{CrateId, FileId, SourceRootId};
5use ra_syntax::{ast::self, TreeArc, SyntaxNode}; 5use ra_syntax::{ast::self, TreeArc, SyntaxNode};
6 6
7use crate::{ 7use crate::{
@@ -16,7 +16,7 @@ use crate::{
16 docs::{Documentation, Docs, docs_from_ast}, 16 docs::{Documentation, Docs, docs_from_ast},
17 module_tree::ModuleId, 17 module_tree::ModuleId,
18 ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId}, 18 ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId},
19 impl_block::ImplId, 19 impl_block::{ImplId, ImplBlock},
20 resolve::Resolver, 20 resolve::Resolver,
21}; 21};
22 22
@@ -44,6 +44,15 @@ impl Crate {
44 pub fn root_module(&self, db: &impl PersistentHirDatabase) -> Option<Module> { 44 pub fn root_module(&self, db: &impl PersistentHirDatabase) -> Option<Module> {
45 self.root_module_impl(db) 45 self.root_module_impl(db)
46 } 46 }
47
48 // TODO: should this be in source_binder?
49 pub fn source_root_crates(
50 db: &impl PersistentHirDatabase,
51 source_root: SourceRootId,
52 ) -> Vec<Crate> {
53 let crate_ids = db.source_root_crates(source_root);
54 crate_ids.iter().map(|&crate_id| Crate { crate_id }).collect()
55 }
47} 56}
48 57
49#[derive(Debug)] 58#[derive(Debug)]
@@ -168,6 +177,27 @@ impl Module {
168 let item_map = db.item_map(self.krate); 177 let item_map = db.item_map(self.krate);
169 Resolver::default().push_module_scope(item_map, *self) 178 Resolver::default().push_module_scope(item_map, *self)
170 } 179 }
180
181 pub fn declarations(self, db: &impl HirDatabase) -> Vec<ModuleDef> {
182 let (lowered_module, _) = db.lower_module(self);
183 lowered_module
184 .declarations
185 .values()
186 .cloned()
187 .flat_map(|per_ns| {
188 per_ns.take_types().into_iter().chain(per_ns.take_values().into_iter())
189 })
190 .collect()
191 }
192
193 pub fn impl_blocks(self, db: &impl HirDatabase) -> Vec<ImplBlock> {
194 let module_impl_blocks = db.impls_in_module(self);
195 module_impl_blocks
196 .impls
197 .iter()
198 .map(|(impl_id, _)| ImplBlock::from_id(module_impl_blocks.clone(), impl_id))
199 .collect()
200 }
171} 201}
172 202
173impl Docs for Module { 203impl Docs for Module {
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
index b30e11abb..4e73590d0 100644
--- a/crates/ra_hir/src/expr.rs
+++ b/crates/ra_hir/src/expr.rs
@@ -70,6 +70,14 @@ impl Body {
70 self.owner 70 self.owner
71 } 71 }
72 72
73 pub fn exprs(&self) -> impl Iterator<Item = (ExprId, &Expr)> {
74 self.exprs.iter()
75 }
76
77 pub fn pats(&self) -> impl Iterator<Item = (PatId, &Pat)> {
78 self.pats.iter()
79 }
80
73 pub fn syntax_mapping(&self, db: &impl HirDatabase) -> Arc<BodySyntaxMapping> { 81 pub fn syntax_mapping(&self, db: &impl HirDatabase) -> Arc<BodySyntaxMapping> {
74 db.body_syntax_mapping(self.owner) 82 db.body_syntax_mapping(self.owner)
75 } 83 }
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
index 7203a8a10..2dc1de41a 100644
--- a/crates/ra_hir/src/ty.rs
+++ b/crates/ra_hir/src/ty.rs
@@ -449,6 +449,49 @@ impl Ty {
449 Ty::Tuple(Arc::new([])) 449 Ty::Tuple(Arc::new([]))
450 } 450 }
451 451
452 pub fn walk(&self, f: &mut impl FnMut(&Ty)) {
453 f(self);
454 match self {
455 Ty::Slice(t) | Ty::Array(t) => t.walk(f),
456 Ty::RawPtr(t, _) => t.walk(f),
457 Ty::Ref(t, _) => t.walk(f),
458 Ty::Tuple(ts) => {
459 for t in ts.iter() {
460 t.walk(f);
461 }
462 }
463 Ty::FnPtr(sig) => {
464 for input in &sig.input {
465 input.walk(f);
466 }
467 sig.output.walk(f);
468 }
469 Ty::FnDef { substs, sig, .. } => {
470 for input in &sig.input {
471 input.walk(f);
472 }
473 sig.output.walk(f);
474 for t in substs.0.iter() {
475 t.walk(f);
476 }
477 }
478 Ty::Adt { substs, .. } => {
479 for t in substs.0.iter() {
480 t.walk(f);
481 }
482 }
483 Ty::Bool
484 | Ty::Char
485 | Ty::Int(_)
486 | Ty::Float(_)
487 | Ty::Str
488 | Ty::Never
489 | Ty::Param { .. }
490 | Ty::Infer(_)
491 | Ty::Unknown => {}
492 }
493 }
494
452 fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { 495 fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) {
453 f(self); 496 f(self);
454 match self { 497 match self {
@@ -491,7 +534,15 @@ impl Ty {
491 } 534 }
492 substs.0 = v.into(); 535 substs.0 = v.into();
493 } 536 }
494 _ => {} 537 Ty::Bool
538 | Ty::Char
539 | Ty::Int(_)
540 | Ty::Float(_)
541 | Ty::Str
542 | Ty::Never
543 | Ty::Param { .. }
544 | Ty::Infer(_)
545 | Ty::Unknown => {}
495 } 546 }
496 } 547 }
497 548
diff --git a/crates/ra_lsp_server/src/server_world.rs b/crates/ra_lsp_server/src/server_world.rs
index f97d240fa..4a68c019f 100644
--- a/crates/ra_lsp_server/src/server_world.rs
+++ b/crates/ra_lsp_server/src/server_world.rs
@@ -47,10 +47,8 @@ impl ServerWorldState {
47 roots.push(krate.root_dir(&ws.sysroot).to_path_buf()) 47 roots.push(krate.root_dir(&ws.sysroot).to_path_buf())
48 } 48 }
49 } 49 }
50 roots.sort();
51 roots.dedup();
52 let roots_to_scan = roots.len();
53 let (mut vfs, roots) = Vfs::new(roots); 50 let (mut vfs, roots) = Vfs::new(roots);
51 let roots_to_scan = roots.len();
54 for r in roots { 52 for r in roots {
55 let is_local = vfs.root2path(r).starts_with(&root); 53 let is_local = vfs.root2path(r).starts_with(&root);
56 change.add_root(SourceRootId(r.0.into()), is_local); 54 change.add_root(SourceRootId(r.0.into()), is_local);
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
index 6b4eb6842..3805be570 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -94,6 +94,7 @@ impl Roots {
94 let mut roots = Arena::default(); 94 let mut roots = Arena::default();
95 // A hack to make nesting work. 95 // A hack to make nesting work.
96 paths.sort_by_key(|it| Reverse(it.as_os_str().len())); 96 paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
97 paths.dedup();
97 for (i, path) in paths.iter().enumerate() { 98 for (i, path) in paths.iter().enumerate() {
98 let nested_roots = paths[..i] 99 let nested_roots = paths[..i]
99 .iter() 100 .iter()
@@ -181,6 +182,10 @@ impl Vfs {
181 None 182 None
182 } 183 }
183 184
185 pub fn num_roots(&self) -> usize {
186 self.roots.len()
187 }
188
184 pub fn load(&mut self, path: &Path) -> Option<VfsFile> { 189 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
185 if let Some((root, rel_path, file)) = self.find_root(path) { 190 if let Some((root, rel_path, file)) = self.find_root(path) {
186 return if let Some(file) = file { 191 return if let Some(file) = file {