aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_db
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_db')
-rw-r--r--crates/ra_ide_db/Cargo.toml32
-rw-r--r--crates/ra_ide_db/src/change.rs318
-rw-r--r--crates/ra_ide_db/src/defs.rs333
-rw-r--r--crates/ra_ide_db/src/imports_locator.rs65
-rw-r--r--crates/ra_ide_db/src/lib.rs139
-rw-r--r--crates/ra_ide_db/src/line_index.rs281
-rw-r--r--crates/ra_ide_db/src/search.rs323
-rw-r--r--crates/ra_ide_db/src/source_change.rs59
-rw-r--r--crates/ra_ide_db/src/symbol_index.rs430
-rw-r--r--crates/ra_ide_db/src/wasm_shims.rs19
10 files changed, 0 insertions, 1999 deletions
diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml
deleted file mode 100644
index 2716a38cc..000000000
--- a/crates/ra_ide_db/Cargo.toml
+++ /dev/null
@@ -1,32 +0,0 @@
1[package]
2edition = "2018"
3name = "ra_ide_db"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6license = "MIT OR Apache-2.0"
7
8[lib]
9doctest = false
10
11[features]
12wasm = []
13
14[dependencies]
15log = "0.4.8"
16rayon = "1.3.0"
17fst = { version = "0.4", default-features = false }
18rustc-hash = "1.1.0"
19once_cell = "1.3.1"
20either = "1.5.3"
21
22stdx = { path = "../stdx" }
23
24ra_syntax = { path = "../ra_syntax" }
25ra_text_edit = { path = "../ra_text_edit" }
26ra_db = { path = "../ra_db" }
27ra_prof = { path = "../ra_prof" }
28test_utils = { path = "../test_utils" }
29
30# ra_ide should depend only on the top-level `hir` package. if you need
31# something from some `hir_xxx` subpackage, reexport the API via `hir`.
32hir = { path = "../ra_hir", package = "ra_hir" }
diff --git a/crates/ra_ide_db/src/change.rs b/crates/ra_ide_db/src/change.rs
deleted file mode 100644
index b13df8b85..000000000
--- a/crates/ra_ide_db/src/change.rs
+++ /dev/null
@@ -1,318 +0,0 @@
1//! Defines a unit of change that can applied to a state of IDE to get the next
2//! state. Changes are transactional.
3
4use std::{fmt, sync::Arc, time};
5
6use ra_db::{
7 salsa::{Database, Durability, SweepStrategy},
8 CrateGraph, FileId, SourceDatabase, SourceDatabaseExt, SourceRoot, SourceRootId,
9};
10use ra_prof::{memory_usage, profile, Bytes};
11use rustc_hash::FxHashSet;
12
13use crate::{symbol_index::SymbolsDatabase, RootDatabase};
14
15#[derive(Default)]
16pub struct AnalysisChange {
17 roots: Option<Vec<SourceRoot>>,
18 files_changed: Vec<(FileId, Option<Arc<String>>)>,
19 crate_graph: Option<CrateGraph>,
20}
21
22impl fmt::Debug for AnalysisChange {
23 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
24 let mut d = fmt.debug_struct("AnalysisChange");
25 if let Some(roots) = &self.roots {
26 d.field("roots", roots);
27 }
28 if !self.files_changed.is_empty() {
29 d.field("files_changed", &self.files_changed.len());
30 }
31 if self.crate_graph.is_some() {
32 d.field("crate_graph", &self.crate_graph);
33 }
34 d.finish()
35 }
36}
37
38impl AnalysisChange {
39 pub fn new() -> AnalysisChange {
40 AnalysisChange::default()
41 }
42
43 pub fn set_roots(&mut self, roots: Vec<SourceRoot>) {
44 self.roots = Some(roots);
45 }
46
47 pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<String>>) {
48 self.files_changed.push((file_id, new_text))
49 }
50
51 pub fn set_crate_graph(&mut self, graph: CrateGraph) {
52 self.crate_graph = Some(graph);
53 }
54}
55
56#[derive(Debug)]
57struct AddFile {
58 file_id: FileId,
59 path: String,
60 text: Arc<String>,
61}
62
63#[derive(Debug)]
64struct RemoveFile {
65 file_id: FileId,
66 path: String,
67}
68
69#[derive(Default)]
70struct RootChange {
71 added: Vec<AddFile>,
72 removed: Vec<RemoveFile>,
73}
74
75impl fmt::Debug for RootChange {
76 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
77 fmt.debug_struct("AnalysisChange")
78 .field("added", &self.added.len())
79 .field("removed", &self.removed.len())
80 .finish()
81 }
82}
83
84const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100);
85
86impl RootDatabase {
87 pub fn request_cancellation(&mut self) {
88 let _p = profile("RootDatabase::request_cancellation");
89 self.salsa_runtime_mut().synthetic_write(Durability::LOW);
90 }
91
92 pub fn apply_change(&mut self, change: AnalysisChange) {
93 let _p = profile("RootDatabase::apply_change");
94 self.request_cancellation();
95 log::info!("apply_change {:?}", change);
96 if let Some(roots) = change.roots {
97 let mut local_roots = FxHashSet::default();
98 let mut library_roots = FxHashSet::default();
99 for (idx, root) in roots.into_iter().enumerate() {
100 let root_id = SourceRootId(idx as u32);
101 let durability = durability(&root);
102 if root.is_library {
103 library_roots.insert(root_id);
104 } else {
105 local_roots.insert(root_id);
106 }
107 for file_id in root.iter() {
108 self.set_file_source_root_with_durability(file_id, root_id, durability);
109 }
110 self.set_source_root_with_durability(root_id, Arc::new(root), durability);
111 }
112 self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
113 self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
114 }
115
116 for (file_id, text) in change.files_changed {
117 let source_root_id = self.file_source_root(file_id);
118 let source_root = self.source_root(source_root_id);
119 let durability = durability(&source_root);
120 // XXX: can't actually remove the file, just reset the text
121 let text = text.unwrap_or_default();
122 self.set_file_text_with_durability(file_id, text, durability)
123 }
124 if let Some(crate_graph) = change.crate_graph {
125 self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
126 }
127 }
128
129 pub fn maybe_collect_garbage(&mut self) {
130 if cfg!(feature = "wasm") {
131 return;
132 }
133
134 if self.last_gc_check.elapsed() > GC_COOLDOWN {
135 self.last_gc_check = crate::wasm_shims::Instant::now();
136 }
137 }
138
139 pub fn collect_garbage(&mut self) {
140 if cfg!(feature = "wasm") {
141 return;
142 }
143
144 let _p = profile("RootDatabase::collect_garbage");
145 self.last_gc = crate::wasm_shims::Instant::now();
146
147 let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
148
149 ra_db::ParseQuery.in_db(self).sweep(sweep);
150 hir::db::ParseMacroQuery.in_db(self).sweep(sweep);
151
152 // Macros do take significant space, but less then the syntax trees
153 // self.query(hir::db::MacroDefQuery).sweep(sweep);
154 // self.query(hir::db::MacroArgTextQuery).sweep(sweep);
155 // self.query(hir::db::MacroExpandQuery).sweep(sweep);
156
157 hir::db::AstIdMapQuery.in_db(self).sweep(sweep);
158
159 hir::db::BodyWithSourceMapQuery.in_db(self).sweep(sweep);
160
161 hir::db::ExprScopesQuery.in_db(self).sweep(sweep);
162 hir::db::InferQueryQuery.in_db(self).sweep(sweep);
163 hir::db::BodyQuery.in_db(self).sweep(sweep);
164 }
165
166 // Feature: Memory Usage
167 //
168 // Clears rust-analyzer's internal database and prints memory usage statistics.
169 //
170 // |===
171 // | Editor | Action Name
172 //
173 // | VS Code | **Rust Analyzer: Memory Usage (Clears Database)**
174 // |===
175 pub fn per_query_memory_usage(&mut self) -> Vec<(String, Bytes)> {
176 let mut acc: Vec<(String, Bytes)> = vec![];
177 let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
178 macro_rules! sweep_each_query {
179 ($($q:path)*) => {$(
180 let before = memory_usage().allocated;
181 $q.in_db(self).sweep(sweep);
182 let after = memory_usage().allocated;
183 let q: $q = Default::default();
184 let name = format!("{:?}", q);
185 acc.push((name, before - after));
186
187 let before = memory_usage().allocated;
188 $q.in_db(self).sweep(sweep.discard_everything());
189 let after = memory_usage().allocated;
190 let q: $q = Default::default();
191 let name = format!("{:?} (deps)", q);
192 acc.push((name, before - after));
193
194 let before = memory_usage().allocated;
195 $q.in_db(self).purge();
196 let after = memory_usage().allocated;
197 let q: $q = Default::default();
198 let name = format!("{:?} (purge)", q);
199 acc.push((name, before - after));
200 )*}
201 }
202 sweep_each_query![
203 // SourceDatabase
204 ra_db::ParseQuery
205 ra_db::CrateGraphQuery
206
207 // SourceDatabaseExt
208 ra_db::FileTextQuery
209 ra_db::FileSourceRootQuery
210 ra_db::SourceRootQuery
211 ra_db::SourceRootCratesQuery
212
213 // AstDatabase
214 hir::db::AstIdMapQuery
215 hir::db::MacroArgTextQuery
216 hir::db::MacroDefQuery
217 hir::db::ParseMacroQuery
218 hir::db::MacroExpandQuery
219
220 // DefDatabase
221 hir::db::ItemTreeQuery
222 hir::db::CrateDefMapQueryQuery
223 hir::db::StructDataQuery
224 hir::db::UnionDataQuery
225 hir::db::EnumDataQuery
226 hir::db::ImplDataQuery
227 hir::db::TraitDataQuery
228 hir::db::TypeAliasDataQuery
229 hir::db::FunctionDataQuery
230 hir::db::ConstDataQuery
231 hir::db::StaticDataQuery
232 hir::db::BodyWithSourceMapQuery
233 hir::db::BodyQuery
234 hir::db::ExprScopesQuery
235 hir::db::GenericParamsQuery
236 hir::db::AttrsQuery
237 hir::db::ModuleLangItemsQuery
238 hir::db::CrateLangItemsQuery
239 hir::db::LangItemQuery
240 hir::db::DocumentationQuery
241 hir::db::ImportMapQuery
242
243 // HirDatabase
244 hir::db::InferQueryQuery
245 hir::db::TyQuery
246 hir::db::ValueTyQuery
247 hir::db::ImplSelfTyQuery
248 hir::db::ImplTraitQuery
249 hir::db::FieldTypesQuery
250 hir::db::CallableItemSignatureQuery
251 hir::db::GenericPredicatesForParamQuery
252 hir::db::GenericPredicatesQuery
253 hir::db::GenericDefaultsQuery
254 hir::db::InherentImplsInCrateQuery
255 hir::db::TraitImplsInCrateQuery
256 hir::db::TraitImplsInDepsQuery
257 hir::db::AssociatedTyDataQuery
258 hir::db::AssociatedTyDataQuery
259 hir::db::TraitDatumQuery
260 hir::db::StructDatumQuery
261 hir::db::ImplDatumQuery
262 hir::db::FnDefDatumQuery
263 hir::db::ReturnTypeImplTraitsQuery
264 hir::db::InternCallableDefQuery
265 hir::db::InternTypeParamIdQuery
266 hir::db::InternImplTraitIdQuery
267 hir::db::InternClosureQuery
268 hir::db::AssociatedTyValueQuery
269 hir::db::TraitSolveQuery
270
271 // SymbolsDatabase
272 crate::symbol_index::FileSymbolsQuery
273 crate::symbol_index::LibrarySymbolsQuery
274 crate::symbol_index::LocalRootsQuery
275 crate::symbol_index::LibraryRootsQuery
276
277 // LineIndexDatabase
278 crate::LineIndexQuery
279 ];
280
281 // To collect interned data, we need to bump the revision counter by performing a synthetic
282 // write.
283 // We do this after collecting the non-interned queries to correctly attribute memory used
284 // by interned data.
285 self.salsa_runtime_mut().synthetic_write(Durability::HIGH);
286
287 sweep_each_query![
288 // AstDatabase
289 hir::db::InternMacroQuery
290 hir::db::InternEagerExpansionQuery
291
292 // InternDatabase
293 hir::db::InternFunctionQuery
294 hir::db::InternStructQuery
295 hir::db::InternUnionQuery
296 hir::db::InternEnumQuery
297 hir::db::InternConstQuery
298 hir::db::InternStaticQuery
299 hir::db::InternTraitQuery
300 hir::db::InternTypeAliasQuery
301 hir::db::InternImplQuery
302
303 // HirDatabase
304 hir::db::InternTypeParamIdQuery
305 ];
306
307 acc.sort_by_key(|it| std::cmp::Reverse(it.1));
308 acc
309 }
310}
311
312fn durability(source_root: &SourceRoot) -> Durability {
313 if source_root.is_library {
314 Durability::HIGH
315 } else {
316 Durability::LOW
317 }
318}
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs
deleted file mode 100644
index b51000b03..000000000
--- a/crates/ra_ide_db/src/defs.rs
+++ /dev/null
@@ -1,333 +0,0 @@
1//! `NameDefinition` keeps information about the element we want to search references for.
2//! The element is represented by `NameKind`. It's located inside some `container` and
3//! has a `visibility`, which defines a search scope.
4//! Note that the reference search is possible for not all of the classified items.
5
6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
7
8use hir::{
9 Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution,
10 Semantics, TypeParam, Visibility,
11};
12use ra_prof::profile;
13use ra_syntax::{
14 ast::{self, AstNode},
15 match_ast, SyntaxNode,
16};
17
18use crate::RootDatabase;
19
20// FIXME: a more precise name would probably be `Symbol`?
21#[derive(Debug, PartialEq, Eq, Copy, Clone)]
22pub enum Definition {
23 Macro(MacroDef),
24 Field(Field),
25 ModuleDef(ModuleDef),
26 SelfType(ImplDef),
27 Local(Local),
28 TypeParam(TypeParam),
29}
30
31impl Definition {
32 pub fn module(&self, db: &RootDatabase) -> Option<Module> {
33 match self {
34 Definition::Macro(it) => it.module(db),
35 Definition::Field(it) => Some(it.parent_def(db).module(db)),
36 Definition::ModuleDef(it) => it.module(db),
37 Definition::SelfType(it) => Some(it.module(db)),
38 Definition::Local(it) => Some(it.module(db)),
39 Definition::TypeParam(it) => Some(it.module(db)),
40 }
41 }
42
43 pub fn visibility(&self, db: &RootDatabase) -> Option<Visibility> {
44 match self {
45 Definition::Macro(_) => None,
46 Definition::Field(sf) => Some(sf.visibility(db)),
47 Definition::ModuleDef(def) => def.definition_visibility(db),
48 Definition::SelfType(_) => None,
49 Definition::Local(_) => None,
50 Definition::TypeParam(_) => None,
51 }
52 }
53
54 pub fn name(&self, db: &RootDatabase) -> Option<Name> {
55 let name = match self {
56 Definition::Macro(it) => it.name(db)?,
57 Definition::Field(it) => it.name(db),
58 Definition::ModuleDef(def) => match def {
59 hir::ModuleDef::Module(it) => it.name(db)?,
60 hir::ModuleDef::Function(it) => it.name(db),
61 hir::ModuleDef::Adt(def) => match def {
62 hir::Adt::Struct(it) => it.name(db),
63 hir::Adt::Union(it) => it.name(db),
64 hir::Adt::Enum(it) => it.name(db),
65 },
66 hir::ModuleDef::EnumVariant(it) => it.name(db),
67 hir::ModuleDef::Const(it) => it.name(db)?,
68 hir::ModuleDef::Static(it) => it.name(db)?,
69 hir::ModuleDef::Trait(it) => it.name(db),
70 hir::ModuleDef::TypeAlias(it) => it.name(db),
71 hir::ModuleDef::BuiltinType(_) => return None,
72 },
73 Definition::SelfType(_) => return None,
74 Definition::Local(it) => it.name(db)?,
75 Definition::TypeParam(it) => it.name(db),
76 };
77 Some(name)
78 }
79}
80
81#[derive(Debug)]
82pub enum NameClass {
83 Definition(Definition),
84 /// `None` in `if let None = Some(82) {}`
85 ConstReference(Definition),
86 FieldShorthand {
87 local: Local,
88 field: Definition,
89 },
90}
91
92impl NameClass {
93 pub fn into_definition(self) -> Option<Definition> {
94 match self {
95 NameClass::Definition(it) => Some(it),
96 NameClass::ConstReference(_) => None,
97 NameClass::FieldShorthand { local, field: _ } => Some(Definition::Local(local)),
98 }
99 }
100
101 pub fn definition(self) -> Definition {
102 match self {
103 NameClass::Definition(it) | NameClass::ConstReference(it) => it,
104 NameClass::FieldShorthand { local: _, field } => field,
105 }
106 }
107}
108
109pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> {
110 let _p = profile("classify_name");
111
112 let parent = name.syntax().parent()?;
113
114 if let Some(bind_pat) = ast::IdentPat::cast(parent.clone()) {
115 if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) {
116 return Some(NameClass::ConstReference(Definition::ModuleDef(def)));
117 }
118 }
119
120 match_ast! {
121 match parent {
122 ast::Rename(it) => {
123 let use_tree = it.syntax().parent().and_then(ast::UseTree::cast)?;
124 let path = use_tree.path()?;
125 let path_segment = path.segment()?;
126 let name_ref_class = path_segment
127 .name_ref()
128 // The rename might be from a `self` token, so fallback to the name higher
129 // in the use tree.
130 .or_else(||{
131 if path_segment.self_token().is_none() {
132 return None;
133 }
134
135 let use_tree = use_tree
136 .syntax()
137 .parent()
138 .as_ref()
139 // Skip over UseTreeList
140 .and_then(SyntaxNode::parent)
141 .and_then(ast::UseTree::cast)?;
142 let path = use_tree.path()?;
143 let path_segment = path.segment()?;
144 path_segment.name_ref()
145 })
146 .and_then(|name_ref| classify_name_ref(sema, &name_ref))?;
147
148 Some(NameClass::Definition(name_ref_class.definition()))
149 },
150 ast::IdentPat(it) => {
151 let local = sema.to_def(&it)?;
152
153 if let Some(record_field_pat) = it.syntax().parent().and_then(ast::RecordPatField::cast) {
154 if record_field_pat.name_ref().is_none() {
155 if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) {
156 let field = Definition::Field(field);
157 return Some(NameClass::FieldShorthand { local, field });
158 }
159 }
160 }
161
162 Some(NameClass::Definition(Definition::Local(local)))
163 },
164 ast::RecordField(it) => {
165 let field: hir::Field = sema.to_def(&it)?;
166 Some(NameClass::Definition(Definition::Field(field)))
167 },
168 ast::Module(it) => {
169 let def = sema.to_def(&it)?;
170 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
171 },
172 ast::Struct(it) => {
173 let def: hir::Struct = sema.to_def(&it)?;
174 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
175 },
176 ast::Union(it) => {
177 let def: hir::Union = sema.to_def(&it)?;
178 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
179 },
180 ast::Enum(it) => {
181 let def: hir::Enum = sema.to_def(&it)?;
182 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
183 },
184 ast::Trait(it) => {
185 let def: hir::Trait = sema.to_def(&it)?;
186 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
187 },
188 ast::Static(it) => {
189 let def: hir::Static = sema.to_def(&it)?;
190 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
191 },
192 ast::Variant(it) => {
193 let def: hir::EnumVariant = sema.to_def(&it)?;
194 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
195 },
196 ast::Fn(it) => {
197 let def: hir::Function = sema.to_def(&it)?;
198 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
199 },
200 ast::Const(it) => {
201 let def: hir::Const = sema.to_def(&it)?;
202 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
203 },
204 ast::TypeAlias(it) => {
205 let def: hir::TypeAlias = sema.to_def(&it)?;
206 Some(NameClass::Definition(Definition::ModuleDef(def.into())))
207 },
208 ast::MacroCall(it) => {
209 let def = sema.to_def(&it)?;
210 Some(NameClass::Definition(Definition::Macro(def)))
211 },
212 ast::TypeParam(it) => {
213 let def = sema.to_def(&it)?;
214 Some(NameClass::Definition(Definition::TypeParam(def)))
215 },
216 _ => None,
217 }
218 }
219}
220
221#[derive(Debug)]
222pub enum NameRefClass {
223 Definition(Definition),
224 FieldShorthand { local: Local, field: Definition },
225}
226
227impl NameRefClass {
228 pub fn definition(self) -> Definition {
229 match self {
230 NameRefClass::Definition(def) => def,
231 NameRefClass::FieldShorthand { local, field: _ } => Definition::Local(local),
232 }
233 }
234}
235
236// Note: we don't have unit-tests for this rather important function.
237// It is primarily exercised via goto definition tests in `ra_ide`.
238pub fn classify_name_ref(
239 sema: &Semantics<RootDatabase>,
240 name_ref: &ast::NameRef,
241) -> Option<NameRefClass> {
242 let _p = profile("classify_name_ref");
243
244 let parent = name_ref.syntax().parent()?;
245
246 if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
247 if let Some(func) = sema.resolve_method_call(&method_call) {
248 return Some(NameRefClass::Definition(Definition::ModuleDef(func.into())));
249 }
250 }
251
252 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
253 if let Some(field) = sema.resolve_field(&field_expr) {
254 return Some(NameRefClass::Definition(Definition::Field(field)));
255 }
256 }
257
258 if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) {
259 if let Some((field, local)) = sema.resolve_record_field(&record_field) {
260 let field = Definition::Field(field);
261 let res = match local {
262 None => NameRefClass::Definition(field),
263 Some(local) => NameRefClass::FieldShorthand { field, local },
264 };
265 return Some(res);
266 }
267 }
268
269 if let Some(record_field_pat) = ast::RecordPatField::cast(parent.clone()) {
270 if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) {
271 let field = Definition::Field(field);
272 return Some(NameRefClass::Definition(field));
273 }
274 }
275
276 if ast::AssocTypeArg::cast(parent.clone()).is_some() {
277 // `Trait<Assoc = Ty>`
278 // ^^^^^
279 let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
280 let resolved = sema.resolve_path(&path)?;
281 if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved {
282 if let Some(ty) = tr
283 .items(sema.db)
284 .iter()
285 .filter_map(|assoc| match assoc {
286 hir::AssocItem::TypeAlias(it) => Some(*it),
287 _ => None,
288 })
289 .find(|alias| alias.name(sema.db).to_string() == **name_ref.text())
290 {
291 return Some(NameRefClass::Definition(Definition::ModuleDef(
292 ModuleDef::TypeAlias(ty),
293 )));
294 }
295 }
296 }
297
298 if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) {
299 if let Some(path) = macro_call.path() {
300 if path.qualifier().is_none() {
301 // Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment
302 // paths are handled below (allowing `log<|>::info!` to resolve to the log crate).
303 if let Some(macro_def) = sema.resolve_macro_call(&macro_call) {
304 return Some(NameRefClass::Definition(Definition::Macro(macro_def)));
305 }
306 }
307 }
308 }
309
310 let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
311 let resolved = sema.resolve_path(&path)?;
312 Some(NameRefClass::Definition(resolved.into()))
313}
314
315impl From<PathResolution> for Definition {
316 fn from(path_resolution: PathResolution) -> Self {
317 match path_resolution {
318 PathResolution::Def(def) => Definition::ModuleDef(def),
319 PathResolution::AssocItem(item) => {
320 let def = match item {
321 hir::AssocItem::Function(it) => it.into(),
322 hir::AssocItem::Const(it) => it.into(),
323 hir::AssocItem::TypeAlias(it) => it.into(),
324 };
325 Definition::ModuleDef(def)
326 }
327 PathResolution::Local(local) => Definition::Local(local),
328 PathResolution::TypeParam(par) => Definition::TypeParam(par),
329 PathResolution::Macro(def) => Definition::Macro(def),
330 PathResolution::SelfType(impl_def) => Definition::SelfType(impl_def),
331 }
332 }
333}
diff --git a/crates/ra_ide_db/src/imports_locator.rs b/crates/ra_ide_db/src/imports_locator.rs
deleted file mode 100644
index 1fba71ff8..000000000
--- a/crates/ra_ide_db/src/imports_locator.rs
+++ /dev/null
@@ -1,65 +0,0 @@
1//! This module contains an import search funcionality that is provided to the ra_assists module.
2//! Later, this should be moved away to a separate crate that is accessible from the ra_assists module.
3
4use hir::{Crate, MacroDef, ModuleDef, Semantics};
5use ra_prof::profile;
6use ra_syntax::{ast, AstNode, SyntaxKind::NAME};
7
8use crate::{
9 defs::{classify_name, Definition},
10 symbol_index::{self, FileSymbol, Query},
11 RootDatabase,
12};
13use either::Either;
14use rustc_hash::FxHashSet;
15
16pub fn find_imports<'a>(
17 sema: &Semantics<'a, RootDatabase>,
18 krate: Crate,
19 name_to_import: &str,
20) -> Vec<Either<ModuleDef, MacroDef>> {
21 let _p = profile("search_for_imports");
22 let db = sema.db;
23
24 // Query dependencies first.
25 let mut candidates: FxHashSet<_> =
26 krate.query_external_importables(db, name_to_import).collect();
27
28 // Query the local crate using the symbol index.
29 let local_results = {
30 let mut query = Query::new(name_to_import.to_string());
31 query.exact();
32 query.limit(40);
33 symbol_index::crate_symbols(db, krate.into(), query)
34 };
35
36 candidates.extend(
37 local_results
38 .into_iter()
39 .filter_map(|import_candidate| get_name_definition(sema, &import_candidate))
40 .filter_map(|name_definition_to_import| match name_definition_to_import {
41 Definition::ModuleDef(module_def) => Some(Either::Left(module_def)),
42 Definition::Macro(macro_def) => Some(Either::Right(macro_def)),
43 _ => None,
44 }),
45 );
46
47 candidates.into_iter().collect()
48}
49
50fn get_name_definition<'a>(
51 sema: &Semantics<'a, RootDatabase>,
52 import_candidate: &FileSymbol,
53) -> Option<Definition> {
54 let _p = profile("get_name_definition");
55 let file_id = import_candidate.file_id;
56
57 let candidate_node = import_candidate.ptr.to_node(sema.parse(file_id).syntax());
58 let candidate_name_node = if candidate_node.kind() != NAME {
59 candidate_node.children().find(|it| it.kind() == NAME)?
60 } else {
61 candidate_node
62 };
63 let name = ast::Name::cast(candidate_name_node)?;
64 classify_name(sema, &name)?.into_definition()
65}
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
deleted file mode 100644
index 6900cac73..000000000
--- a/crates/ra_ide_db/src/lib.rs
+++ /dev/null
@@ -1,139 +0,0 @@
1//! This crate defines the core datastructure representing IDE state -- `RootDatabase`.
2//!
3//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search.
4
5pub mod line_index;
6pub mod symbol_index;
7pub mod change;
8pub mod defs;
9pub mod search;
10pub mod imports_locator;
11pub mod source_change;
12mod wasm_shims;
13
14use std::{fmt, sync::Arc};
15
16use hir::db::{AstDatabase, DefDatabase, HirDatabase};
17use ra_db::{
18 salsa::{self, Durability},
19 Canceled, CheckCanceled, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase,
20 Upcast,
21};
22use rustc_hash::FxHashSet;
23
24use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
25
26#[salsa::database(
27 ra_db::SourceDatabaseStorage,
28 ra_db::SourceDatabaseExtStorage,
29 LineIndexDatabaseStorage,
30 symbol_index::SymbolsDatabaseStorage,
31 hir::db::InternDatabaseStorage,
32 hir::db::AstDatabaseStorage,
33 hir::db::DefDatabaseStorage,
34 hir::db::HirDatabaseStorage
35)]
36pub struct RootDatabase {
37 storage: salsa::Storage<RootDatabase>,
38 pub last_gc: crate::wasm_shims::Instant,
39 pub last_gc_check: crate::wasm_shims::Instant,
40}
41
42impl fmt::Debug for RootDatabase {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.debug_struct("RootDatabase").finish()
45 }
46}
47
48impl Upcast<dyn AstDatabase> for RootDatabase {
49 fn upcast(&self) -> &(dyn AstDatabase + 'static) {
50 &*self
51 }
52}
53
54impl Upcast<dyn DefDatabase> for RootDatabase {
55 fn upcast(&self) -> &(dyn DefDatabase + 'static) {
56 &*self
57 }
58}
59
60impl Upcast<dyn HirDatabase> for RootDatabase {
61 fn upcast(&self) -> &(dyn HirDatabase + 'static) {
62 &*self
63 }
64}
65
66impl FileLoader for RootDatabase {
67 fn file_text(&self, file_id: FileId) -> Arc<String> {
68 FileLoaderDelegate(self).file_text(file_id)
69 }
70 fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
71 FileLoaderDelegate(self).resolve_path(anchor, path)
72 }
73 fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
74 FileLoaderDelegate(self).relevant_crates(file_id)
75 }
76}
77
78impl salsa::Database for RootDatabase {
79 fn on_propagated_panic(&self) -> ! {
80 Canceled::throw()
81 }
82 fn salsa_event(&self, event: salsa::Event) {
83 match event.kind {
84 salsa::EventKind::DidValidateMemoizedValue { .. }
85 | salsa::EventKind::WillExecute { .. } => {
86 self.check_canceled();
87 }
88 _ => (),
89 }
90 }
91}
92
93impl Default for RootDatabase {
94 fn default() -> RootDatabase {
95 RootDatabase::new(None)
96 }
97}
98
99impl RootDatabase {
100 pub fn new(lru_capacity: Option<usize>) -> RootDatabase {
101 let mut db = RootDatabase {
102 storage: salsa::Storage::default(),
103 last_gc: crate::wasm_shims::Instant::now(),
104 last_gc_check: crate::wasm_shims::Instant::now(),
105 };
106 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
107 db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
108 db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
109 db.update_lru_capacity(lru_capacity);
110 db
111 }
112
113 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
114 let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP);
115 ra_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
116 hir::db::ParseMacroQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
117 hir::db::MacroExpandQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
118 }
119}
120
121impl salsa::ParallelDatabase for RootDatabase {
122 fn snapshot(&self) -> salsa::Snapshot<RootDatabase> {
123 salsa::Snapshot::new(RootDatabase {
124 storage: self.storage.snapshot(),
125 last_gc: self.last_gc,
126 last_gc_check: self.last_gc_check,
127 })
128 }
129}
130
131#[salsa::query_group(LineIndexDatabaseStorage)]
132pub trait LineIndexDatabase: ra_db::SourceDatabase + CheckCanceled {
133 fn line_index(&self, file_id: FileId) -> Arc<LineIndex>;
134}
135
136fn line_index(db: &dyn LineIndexDatabase, file_id: FileId) -> Arc<LineIndex> {
137 let text = db.file_text(file_id);
138 Arc::new(LineIndex::new(&*text))
139}
diff --git a/crates/ra_ide_db/src/line_index.rs b/crates/ra_ide_db/src/line_index.rs
deleted file mode 100644
index 2ab662098..000000000
--- a/crates/ra_ide_db/src/line_index.rs
+++ /dev/null
@@ -1,281 +0,0 @@
1//! `LineIndex` maps flat `TextSize` offsets into `(Line, Column)`
2//! representation.
3use std::iter;
4
5use ra_syntax::{TextRange, TextSize};
6use rustc_hash::FxHashMap;
7use stdx::partition_point;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct LineIndex {
11 /// Offset the the beginning of each line, zero-based
12 pub(crate) newlines: Vec<TextSize>,
13 /// List of non-ASCII characters on each line
14 pub(crate) utf16_lines: FxHashMap<u32, Vec<Utf16Char>>,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub struct LineCol {
19 /// Zero-based
20 pub line: u32,
21 /// Zero-based
22 pub col_utf16: u32,
23}
24
25#[derive(Clone, Debug, Hash, PartialEq, Eq)]
26pub(crate) struct Utf16Char {
27 /// Start offset of a character inside a line, zero-based
28 pub(crate) start: TextSize,
29 /// End offset of a character inside a line, zero-based
30 pub(crate) end: TextSize,
31}
32
33impl Utf16Char {
34 /// Returns the length in 8-bit UTF-8 code units.
35 fn len(&self) -> TextSize {
36 self.end - self.start
37 }
38
39 /// Returns the length in 16-bit UTF-16 code units.
40 fn len_utf16(&self) -> usize {
41 if self.len() == TextSize::from(4) {
42 2
43 } else {
44 1
45 }
46 }
47}
48
49impl LineIndex {
50 pub fn new(text: &str) -> LineIndex {
51 let mut utf16_lines = FxHashMap::default();
52 let mut utf16_chars = Vec::new();
53
54 let mut newlines = vec![0.into()];
55 let mut curr_row = 0.into();
56 let mut curr_col = 0.into();
57 let mut line = 0;
58 for c in text.chars() {
59 let c_len = TextSize::of(c);
60 curr_row += c_len;
61 if c == '\n' {
62 newlines.push(curr_row);
63
64 // Save any utf-16 characters seen in the previous line
65 if !utf16_chars.is_empty() {
66 utf16_lines.insert(line, utf16_chars);
67 utf16_chars = Vec::new();
68 }
69
70 // Prepare for processing the next line
71 curr_col = 0.into();
72 line += 1;
73 continue;
74 }
75
76 if !c.is_ascii() {
77 utf16_chars.push(Utf16Char { start: curr_col, end: curr_col + c_len });
78 }
79
80 curr_col += c_len;
81 }
82
83 // Save any utf-16 characters seen in the last line
84 if !utf16_chars.is_empty() {
85 utf16_lines.insert(line, utf16_chars);
86 }
87
88 LineIndex { newlines, utf16_lines }
89 }
90
91 pub fn line_col(&self, offset: TextSize) -> LineCol {
92 let line = partition_point(&self.newlines, |&it| it <= offset) - 1;
93 let line_start_offset = self.newlines[line];
94 let col = offset - line_start_offset;
95
96 LineCol { line: line as u32, col_utf16: self.utf8_to_utf16_col(line as u32, col) as u32 }
97 }
98
99 pub fn offset(&self, line_col: LineCol) -> TextSize {
100 //FIXME: return Result
101 let col = self.utf16_to_utf8_col(line_col.line, line_col.col_utf16);
102 self.newlines[line_col.line as usize] + col
103 }
104
105 pub fn lines(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
106 let lo = partition_point(&self.newlines, |&it| it < range.start());
107 let hi = partition_point(&self.newlines, |&it| it <= range.end());
108 let all = iter::once(range.start())
109 .chain(self.newlines[lo..hi].iter().copied())
110 .chain(iter::once(range.end()));
111
112 all.clone()
113 .zip(all.skip(1))
114 .map(|(lo, hi)| TextRange::new(lo, hi))
115 .filter(|it| !it.is_empty())
116 }
117
118 fn utf8_to_utf16_col(&self, line: u32, col: TextSize) -> usize {
119 let mut res: usize = col.into();
120 if let Some(utf16_chars) = self.utf16_lines.get(&line) {
121 for c in utf16_chars {
122 if c.end <= col {
123 res -= usize::from(c.len()) - c.len_utf16();
124 } else {
125 // From here on, all utf16 characters come *after* the character we are mapping,
126 // so we don't need to take them into account
127 break;
128 }
129 }
130 }
131 res
132 }
133
134 fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize {
135 if let Some(utf16_chars) = self.utf16_lines.get(&line) {
136 for c in utf16_chars {
137 if col > u32::from(c.start) {
138 col += u32::from(c.len()) - c.len_utf16() as u32;
139 } else {
140 // From here on, all utf16 characters come *after* the character we are mapping,
141 // so we don't need to take them into account
142 break;
143 }
144 }
145 }
146
147 col.into()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_line_index() {
157 let text = "hello\nworld";
158 let index = LineIndex::new(text);
159 assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 });
160 assert_eq!(index.line_col(1.into()), LineCol { line: 0, col_utf16: 1 });
161 assert_eq!(index.line_col(5.into()), LineCol { line: 0, col_utf16: 5 });
162 assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 0 });
163 assert_eq!(index.line_col(7.into()), LineCol { line: 1, col_utf16: 1 });
164 assert_eq!(index.line_col(8.into()), LineCol { line: 1, col_utf16: 2 });
165 assert_eq!(index.line_col(10.into()), LineCol { line: 1, col_utf16: 4 });
166 assert_eq!(index.line_col(11.into()), LineCol { line: 1, col_utf16: 5 });
167 assert_eq!(index.line_col(12.into()), LineCol { line: 1, col_utf16: 6 });
168
169 let text = "\nhello\nworld";
170 let index = LineIndex::new(text);
171 assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 });
172 assert_eq!(index.line_col(1.into()), LineCol { line: 1, col_utf16: 0 });
173 assert_eq!(index.line_col(2.into()), LineCol { line: 1, col_utf16: 1 });
174 assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 5 });
175 assert_eq!(index.line_col(7.into()), LineCol { line: 2, col_utf16: 0 });
176 }
177
178 #[test]
179 fn test_char_len() {
180 assert_eq!('メ'.len_utf8(), 3);
181 assert_eq!('メ'.len_utf16(), 1);
182 }
183
184 #[test]
185 fn test_empty_index() {
186 let col_index = LineIndex::new(
187 "
188const C: char = 'x';
189",
190 );
191 assert_eq!(col_index.utf16_lines.len(), 0);
192 }
193
194 #[test]
195 fn test_single_char() {
196 let col_index = LineIndex::new(
197 "
198const C: char = 'メ';
199",
200 );
201
202 assert_eq!(col_index.utf16_lines.len(), 1);
203 assert_eq!(col_index.utf16_lines[&1].len(), 1);
204 assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() });
205
206 // UTF-8 to UTF-16, no changes
207 assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15);
208
209 // UTF-8 to UTF-16
210 assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20);
211
212 // UTF-16 to UTF-8, no changes
213 assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15));
214
215 // UTF-16 to UTF-8
216 assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21));
217
218 let col_index = LineIndex::new("a𐐏b");
219 assert_eq!(col_index.utf16_to_utf8_col(0, 3), TextSize::from(5));
220 }
221
222 #[test]
223 fn test_string() {
224 let col_index = LineIndex::new(
225 "
226const C: char = \"メ メ\";
227",
228 );
229
230 assert_eq!(col_index.utf16_lines.len(), 1);
231 assert_eq!(col_index.utf16_lines[&1].len(), 2);
232 assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() });
233 assert_eq!(col_index.utf16_lines[&1][1], Utf16Char { start: 21.into(), end: 24.into() });
234
235 // UTF-8 to UTF-16
236 assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15);
237
238 assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19);
239 assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21);
240
241 assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15);
242
243 // UTF-16 to UTF-8
244 assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15));
245
246 // メ UTF-8: 0xE3 0x83 0xA1, UTF-16: 0x30E1
247 assert_eq!(col_index.utf16_to_utf8_col(1, 17), TextSize::from(17)); // first メ at 17..20
248 assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20)); // space
249 assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); // second メ at 21..24
250
251 assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextSize::from(15));
252 }
253
254 #[test]
255 fn test_splitlines() {
256 fn r(lo: u32, hi: u32) -> TextRange {
257 TextRange::new(lo.into(), hi.into())
258 }
259
260 let text = "a\nbb\nccc\n";
261 let line_index = LineIndex::new(text);
262
263 let actual = line_index.lines(r(0, 9)).collect::<Vec<_>>();
264 let expected = vec![r(0, 2), r(2, 5), r(5, 9)];
265 assert_eq!(actual, expected);
266
267 let text = "";
268 let line_index = LineIndex::new(text);
269
270 let actual = line_index.lines(r(0, 0)).collect::<Vec<_>>();
271 let expected = vec![];
272 assert_eq!(actual, expected);
273
274 let text = "\n";
275 let line_index = LineIndex::new(text);
276
277 let actual = line_index.lines(r(0, 1)).collect::<Vec<_>>();
278 let expected = vec![r(0, 1)];
279 assert_eq!(actual, expected)
280 }
281}
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs
deleted file mode 100644
index 0b862b449..000000000
--- a/crates/ra_ide_db/src/search.rs
+++ /dev/null
@@ -1,323 +0,0 @@
1//! Implementation of find-usages functionality.
2//!
3//! It is based on the standard ide trick: first, we run a fast text search to
4//! get a super-set of matches. Then, we we confirm each match using precise
5//! name resolution.
6
7use std::{convert::TryInto, mem};
8
9use hir::{DefWithBody, HasSource, Module, ModuleSource, Semantics, Visibility};
10use once_cell::unsync::Lazy;
11use ra_db::{FileId, FileRange, SourceDatabaseExt};
12use ra_prof::profile;
13use ra_syntax::{ast, match_ast, AstNode, TextRange, TextSize};
14use rustc_hash::FxHashMap;
15
16use crate::{
17 defs::{classify_name_ref, Definition, NameRefClass},
18 RootDatabase,
19};
20
21#[derive(Debug, Clone)]
22pub struct Reference {
23 pub file_range: FileRange,
24 pub kind: ReferenceKind,
25 pub access: Option<ReferenceAccess>,
26}
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum ReferenceKind {
30 FieldShorthandForField,
31 FieldShorthandForLocal,
32 StructLiteral,
33 Other,
34}
35
36#[derive(Debug, Copy, Clone, PartialEq)]
37pub enum ReferenceAccess {
38 Read,
39 Write,
40}
41
42/// Generally, `search_scope` returns files that might contain references for the element.
43/// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
44/// In some cases, the location of the references is known to within a `TextRange`,
45/// e.g. for things like local variables.
46pub struct SearchScope {
47 entries: FxHashMap<FileId, Option<TextRange>>,
48}
49
50impl SearchScope {
51 fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope {
52 SearchScope { entries }
53 }
54
55 pub fn empty() -> SearchScope {
56 SearchScope::new(FxHashMap::default())
57 }
58
59 pub fn single_file(file: FileId) -> SearchScope {
60 SearchScope::new(std::iter::once((file, None)).collect())
61 }
62
63 pub fn files(files: &[FileId]) -> SearchScope {
64 SearchScope::new(files.iter().map(|f| (*f, None)).collect())
65 }
66
67 pub fn intersection(&self, other: &SearchScope) -> SearchScope {
68 let (mut small, mut large) = (&self.entries, &other.entries);
69 if small.len() > large.len() {
70 mem::swap(&mut small, &mut large)
71 }
72
73 let res = small
74 .iter()
75 .filter_map(|(file_id, r1)| {
76 let r2 = large.get(file_id)?;
77 let r = intersect_ranges(*r1, *r2)?;
78 Some((*file_id, r))
79 })
80 .collect();
81
82 return SearchScope::new(res);
83
84 fn intersect_ranges(
85 r1: Option<TextRange>,
86 r2: Option<TextRange>,
87 ) -> Option<Option<TextRange>> {
88 match (r1, r2) {
89 (None, r) | (r, None) => Some(r),
90 (Some(r1), Some(r2)) => {
91 let r = r1.intersect(r2)?;
92 Some(Some(r))
93 }
94 }
95 }
96 }
97}
98
99impl IntoIterator for SearchScope {
100 type Item = (FileId, Option<TextRange>);
101 type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>;
102
103 fn into_iter(self) -> Self::IntoIter {
104 self.entries.into_iter()
105 }
106}
107
108impl Definition {
109 fn search_scope(&self, db: &RootDatabase) -> SearchScope {
110 let _p = profile("search_scope");
111 let module = match self.module(db) {
112 Some(it) => it,
113 None => return SearchScope::empty(),
114 };
115 let module_src = module.definition_source(db);
116 let file_id = module_src.file_id.original_file(db);
117
118 if let Definition::Local(var) = self {
119 let range = match var.parent(db) {
120 DefWithBody::Function(f) => f.source(db).value.syntax().text_range(),
121 DefWithBody::Const(c) => c.source(db).value.syntax().text_range(),
122 DefWithBody::Static(s) => s.source(db).value.syntax().text_range(),
123 };
124 let mut res = FxHashMap::default();
125 res.insert(file_id, Some(range));
126 return SearchScope::new(res);
127 }
128
129 let vis = self.visibility(db);
130
131 if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) {
132 let module: Module = module.into();
133 let mut res = FxHashMap::default();
134
135 let mut to_visit = vec![module];
136 let mut is_first = true;
137 while let Some(module) = to_visit.pop() {
138 let src = module.definition_source(db);
139 let file_id = src.file_id.original_file(db);
140 match src.value {
141 ModuleSource::Module(m) => {
142 if is_first {
143 let range = Some(m.syntax().text_range());
144 res.insert(file_id, range);
145 } else {
146 // We have already added the enclosing file to the search scope,
147 // so do nothing.
148 }
149 }
150 ModuleSource::SourceFile(_) => {
151 res.insert(file_id, None);
152 }
153 };
154 is_first = false;
155 to_visit.extend(module.children(db));
156 }
157
158 return SearchScope::new(res);
159 }
160
161 if let Some(Visibility::Public) = vis {
162 let source_root_id = db.file_source_root(file_id);
163 let source_root = db.source_root(source_root_id);
164 let mut res = source_root.iter().map(|id| (id, None)).collect::<FxHashMap<_, _>>();
165
166 let krate = module.krate();
167 for rev_dep in krate.reverse_dependencies(db) {
168 let root_file = rev_dep.root_file(db);
169 let source_root_id = db.file_source_root(root_file);
170 let source_root = db.source_root(source_root_id);
171 res.extend(source_root.iter().map(|id| (id, None)));
172 }
173 return SearchScope::new(res);
174 }
175
176 let mut res = FxHashMap::default();
177 let range = match module_src.value {
178 ModuleSource::Module(m) => Some(m.syntax().text_range()),
179 ModuleSource::SourceFile(_) => None,
180 };
181 res.insert(file_id, range);
182 SearchScope::new(res)
183 }
184
185 pub fn find_usages(
186 &self,
187 sema: &Semantics<RootDatabase>,
188 search_scope: Option<SearchScope>,
189 ) -> Vec<Reference> {
190 let _p = profile("Definition::find_usages");
191
192 let search_scope = {
193 let base = self.search_scope(sema.db);
194 match search_scope {
195 None => base,
196 Some(scope) => base.intersection(&scope),
197 }
198 };
199
200 let name = match self.name(sema.db) {
201 None => return Vec::new(),
202 Some(it) => it.to_string(),
203 };
204
205 let pat = name.as_str();
206 let mut refs = vec![];
207
208 for (file_id, search_range) in search_scope {
209 let text = sema.db.file_text(file_id);
210 let search_range =
211 search_range.unwrap_or(TextRange::up_to(TextSize::of(text.as_str())));
212
213 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
214
215 for (idx, _) in text.match_indices(pat) {
216 let offset: TextSize = idx.try_into().unwrap();
217 if !search_range.contains_inclusive(offset) {
218 continue;
219 }
220
221 let name_ref: ast::NameRef =
222 if let Some(name_ref) = sema.find_node_at_offset_with_descend(&tree, offset) {
223 name_ref
224 } else {
225 continue;
226 };
227
228 match classify_name_ref(&sema, &name_ref) {
229 Some(NameRefClass::Definition(def)) if &def == self => {
230 let kind = if is_record_lit_name_ref(&name_ref)
231 || is_call_expr_name_ref(&name_ref)
232 {
233 ReferenceKind::StructLiteral
234 } else {
235 ReferenceKind::Other
236 };
237
238 let file_range = sema.original_range(name_ref.syntax());
239 refs.push(Reference {
240 file_range,
241 kind,
242 access: reference_access(&def, &name_ref),
243 });
244 }
245 Some(NameRefClass::FieldShorthand { local, field }) => {
246 match self {
247 Definition::Field(_) if &field == self => refs.push(Reference {
248 file_range: sema.original_range(name_ref.syntax()),
249 kind: ReferenceKind::FieldShorthandForField,
250 access: reference_access(&field, &name_ref),
251 }),
252 Definition::Local(l) if &local == l => refs.push(Reference {
253 file_range: sema.original_range(name_ref.syntax()),
254 kind: ReferenceKind::FieldShorthandForLocal,
255 access: reference_access(&Definition::Local(local), &name_ref),
256 }),
257
258 _ => {} // not a usage
259 };
260 }
261 _ => {} // not a usage
262 }
263 }
264 }
265 refs
266 }
267}
268
269fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
270 // Only Locals and Fields have accesses for now.
271 match def {
272 Definition::Local(_) | Definition::Field(_) => {}
273 _ => return None,
274 };
275
276 let mode = name_ref.syntax().ancestors().find_map(|node| {
277 match_ast! {
278 match (node) {
279 ast::BinExpr(expr) => {
280 if expr.op_kind()?.is_assignment() {
281 // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
282 // FIXME: This is not terribly accurate.
283 if let Some(lhs) = expr.lhs() {
284 if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() {
285 return Some(ReferenceAccess::Write);
286 }
287 }
288 }
289 Some(ReferenceAccess::Read)
290 },
291 _ => None
292 }
293 }
294 });
295
296 // Default Locals and Fields to read
297 mode.or(Some(ReferenceAccess::Read))
298}
299
300fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool {
301 name_ref
302 .syntax()
303 .ancestors()
304 .find_map(ast::CallExpr::cast)
305 .and_then(|c| match c.expr()? {
306 ast::Expr::PathExpr(p) => {
307 Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref))
308 }
309 _ => None,
310 })
311 .unwrap_or(false)
312}
313
314fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool {
315 name_ref
316 .syntax()
317 .ancestors()
318 .find_map(ast::RecordExpr::cast)
319 .and_then(|l| l.path())
320 .and_then(|p| p.segment())
321 .map(|p| p.name_ref().as_ref() == Some(name_ref))
322 .unwrap_or(false)
323}
diff --git a/crates/ra_ide_db/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs
deleted file mode 100644
index abb83f421..000000000
--- a/crates/ra_ide_db/src/source_change.rs
+++ /dev/null
@@ -1,59 +0,0 @@
1//! This modules defines type to represent changes to the source code, that flow
2//! from the server to the client.
3//!
4//! It can be viewed as a dual for `AnalysisChange`.
5
6use ra_db::FileId;
7use ra_text_edit::TextEdit;
8
9#[derive(Default, Debug, Clone)]
10pub struct SourceChange {
11 pub source_file_edits: Vec<SourceFileEdit>,
12 pub file_system_edits: Vec<FileSystemEdit>,
13 pub is_snippet: bool,
14}
15
16impl SourceChange {
17 /// Creates a new SourceChange with the given label
18 /// from the edits.
19 pub fn from_edits(
20 source_file_edits: Vec<SourceFileEdit>,
21 file_system_edits: Vec<FileSystemEdit>,
22 ) -> Self {
23 SourceChange { source_file_edits, file_system_edits, is_snippet: false }
24 }
25}
26
27#[derive(Debug, Clone)]
28pub struct SourceFileEdit {
29 pub file_id: FileId,
30 pub edit: TextEdit,
31}
32
33impl From<SourceFileEdit> for SourceChange {
34 fn from(edit: SourceFileEdit) -> SourceChange {
35 vec![edit].into()
36 }
37}
38
39impl From<Vec<SourceFileEdit>> for SourceChange {
40 fn from(source_file_edits: Vec<SourceFileEdit>) -> SourceChange {
41 SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
42 }
43}
44
45#[derive(Debug, Clone)]
46pub enum FileSystemEdit {
47 CreateFile { anchor: FileId, dst: String },
48 MoveFile { src: FileId, anchor: FileId, dst: String },
49}
50
51impl From<FileSystemEdit> for SourceChange {
52 fn from(edit: FileSystemEdit) -> SourceChange {
53 SourceChange {
54 source_file_edits: Vec::new(),
55 file_system_edits: vec![edit],
56 is_snippet: false,
57 }
58 }
59}
diff --git a/crates/ra_ide_db/src/symbol_index.rs b/crates/ra_ide_db/src/symbol_index.rs
deleted file mode 100644
index 35a2c5be3..000000000
--- a/crates/ra_ide_db/src/symbol_index.rs
+++ /dev/null
@@ -1,430 +0,0 @@
1//! This module handles fuzzy-searching of functions, structs and other symbols
2//! by name across the whole workspace and dependencies.
3//!
4//! It works by building an incrementally-updated text-search index of all
5//! symbols. The backbone of the index is the **awesome** `fst` crate by
6//! @BurntSushi.
7//!
8//! In a nutshell, you give a set of strings to `fst`, and it builds a
9//! finite state machine describing this set of strings. The strings which
10//! could fuzzy-match a pattern can also be described by a finite state machine.
11//! What is freaking cool is that you can now traverse both state machines in
12//! lock-step to enumerate the strings which are both in the input set and
13//! fuzz-match the query. Or, more formally, given two languages described by
14//! FSTs, one can build a product FST which describes the intersection of the
15//! languages.
16//!
17//! `fst` does not support cheap updating of the index, but it supports unioning
18//! of state machines. So, to account for changing source code, we build an FST
19//! for each library (which is assumed to never change) and an FST for each Rust
20//! file in the current workspace, and run a query against the union of all
21//! those FSTs.
22
23use std::{
24 cmp::Ordering,
25 fmt,
26 hash::{Hash, Hasher},
27 mem,
28 sync::Arc,
29};
30
31use fst::{self, Streamer};
32use hir::db::DefDatabase;
33use ra_db::{
34 salsa::{self, ParallelDatabase},
35 CrateId, FileId, SourceDatabaseExt, SourceRootId,
36};
37use ra_prof::profile;
38use ra_syntax::{
39 ast::{self, NameOwner},
40 match_ast, AstNode, Parse, SmolStr, SourceFile,
41 SyntaxKind::{self, *},
42 SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent,
43};
44use rayon::prelude::*;
45use rustc_hash::{FxHashMap, FxHashSet};
46
47use crate::RootDatabase;
48
49#[derive(Debug)]
50pub struct Query {
51 query: String,
52 lowercased: String,
53 only_types: bool,
54 libs: bool,
55 exact: bool,
56 limit: usize,
57}
58
59impl Query {
60 pub fn new(query: String) -> Query {
61 let lowercased = query.to_lowercase();
62 Query {
63 query,
64 lowercased,
65 only_types: false,
66 libs: false,
67 exact: false,
68 limit: usize::max_value(),
69 }
70 }
71
72 pub fn only_types(&mut self) {
73 self.only_types = true;
74 }
75
76 pub fn libs(&mut self) {
77 self.libs = true;
78 }
79
80 pub fn exact(&mut self) {
81 self.exact = true;
82 }
83
84 pub fn limit(&mut self, limit: usize) {
85 self.limit = limit
86 }
87}
88
89#[salsa::query_group(SymbolsDatabaseStorage)]
90pub trait SymbolsDatabase: hir::db::HirDatabase + SourceDatabaseExt {
91 fn file_symbols(&self, file_id: FileId) -> Arc<SymbolIndex>;
92 fn library_symbols(&self) -> Arc<FxHashMap<SourceRootId, SymbolIndex>>;
93 /// The set of "local" (that is, from the current workspace) roots.
94 /// Files in local roots are assumed to change frequently.
95 #[salsa::input]
96 fn local_roots(&self) -> Arc<FxHashSet<SourceRootId>>;
97 /// The set of roots for crates.io libraries.
98 /// Files in libraries are assumed to never change.
99 #[salsa::input]
100 fn library_roots(&self) -> Arc<FxHashSet<SourceRootId>>;
101}
102
103fn library_symbols(db: &dyn SymbolsDatabase) -> Arc<FxHashMap<SourceRootId, SymbolIndex>> {
104 let _p = profile("library_symbols");
105
106 let roots = db.library_roots();
107 let res = roots
108 .iter()
109 .map(|&root_id| {
110 let root = db.source_root(root_id);
111 let files = root
112 .iter()
113 .map(|it| (it, SourceDatabaseExt::file_text(db, it)))
114 .collect::<Vec<_>>();
115 let symbol_index = SymbolIndex::for_files(
116 files.into_par_iter().map(|(file, text)| (file, SourceFile::parse(&text))),
117 );
118 (root_id, symbol_index)
119 })
120 .collect();
121 Arc::new(res)
122}
123
124fn file_symbols(db: &dyn SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex> {
125 db.check_canceled();
126 let parse = db.parse(file_id);
127
128 let symbols = source_file_to_file_symbols(&parse.tree(), file_id);
129
130 // FIXME: add macros here
131
132 Arc::new(SymbolIndex::new(symbols))
133}
134
135/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
136struct Snap<DB>(DB);
137impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
138 fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
139 Snap(self.0.snapshot())
140 }
141}
142
143// Feature: Workspace Symbol
144//
145// Uses fuzzy-search to find types, modules and functions by name across your
146// project and dependencies. This is **the** most useful feature, which improves code
147// navigation tremendously. It mostly works on top of the built-in LSP
148// functionality, however `#` and `*` symbols can be used to narrow down the
149// search. Specifically,
150//
151// - `Foo` searches for `Foo` type in the current workspace
152// - `foo#` searches for `foo` function in the current workspace
153// - `Foo*` searches for `Foo` type among dependencies, including `stdlib`
154// - `foo#*` searches for `foo` function among dependencies
155//
156// That is, `#` switches from "types" to all symbols, `*` switches from the current
157// workspace to dependencies.
158//
159// |===
160// | Editor | Shortcut
161//
162// | VS Code | kbd:[Ctrl+T]
163// |===
164pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
165 let _p = ra_prof::profile("world_symbols").detail(|| query.query.clone());
166
167 let tmp1;
168 let tmp2;
169 let buf: Vec<&SymbolIndex> = if query.libs {
170 tmp1 = db.library_symbols();
171 tmp1.values().collect()
172 } else {
173 let mut files = Vec::new();
174 for &root in db.local_roots().iter() {
175 let sr = db.source_root(root);
176 files.extend(sr.iter())
177 }
178
179 let snap = Snap(db.snapshot());
180 tmp2 = files
181 .par_iter()
182 .map_with(snap, |db, &file_id| db.0.file_symbols(file_id))
183 .collect::<Vec<_>>();
184 tmp2.iter().map(|it| &**it).collect()
185 };
186 query.search(&buf)
187}
188
189pub fn crate_symbols(db: &RootDatabase, krate: CrateId, query: Query) -> Vec<FileSymbol> {
190 // FIXME(#4842): This now depends on CrateDefMap, why not build the entire symbol index from
191 // that instead?
192
193 let def_map = db.crate_def_map(krate);
194 let mut files = Vec::new();
195 let mut modules = vec![def_map.root];
196 while let Some(module) = modules.pop() {
197 let data = &def_map[module];
198 files.extend(data.origin.file_id());
199 modules.extend(data.children.values());
200 }
201
202 let snap = Snap(db.snapshot());
203
204 let buf = files
205 .par_iter()
206 .map_with(snap, |db, &file_id| db.0.file_symbols(file_id))
207 .collect::<Vec<_>>();
208 let buf = buf.iter().map(|it| &**it).collect::<Vec<_>>();
209
210 query.search(&buf)
211}
212
213pub fn index_resolve(db: &RootDatabase, name_ref: &ast::NameRef) -> Vec<FileSymbol> {
214 let name = name_ref.text();
215 let mut query = Query::new(name.to_string());
216 query.exact();
217 query.limit(4);
218 world_symbols(db, query)
219}
220
221#[derive(Default)]
222pub struct SymbolIndex {
223 symbols: Vec<FileSymbol>,
224 map: fst::Map<Vec<u8>>,
225}
226
227impl fmt::Debug for SymbolIndex {
228 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229 f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish()
230 }
231}
232
233impl PartialEq for SymbolIndex {
234 fn eq(&self, other: &SymbolIndex) -> bool {
235 self.symbols == other.symbols
236 }
237}
238
239impl Eq for SymbolIndex {}
240
241impl Hash for SymbolIndex {
242 fn hash<H: Hasher>(&self, hasher: &mut H) {
243 self.symbols.hash(hasher)
244 }
245}
246
247impl SymbolIndex {
248 fn new(mut symbols: Vec<FileSymbol>) -> SymbolIndex {
249 fn cmp(lhs: &FileSymbol, rhs: &FileSymbol) -> Ordering {
250 let lhs_chars = lhs.name.chars().map(|c| c.to_ascii_lowercase());
251 let rhs_chars = rhs.name.chars().map(|c| c.to_ascii_lowercase());
252 lhs_chars.cmp(rhs_chars)
253 }
254
255 symbols.par_sort_by(cmp);
256
257 let mut builder = fst::MapBuilder::memory();
258
259 let mut last_batch_start = 0;
260
261 for idx in 0..symbols.len() {
262 if let Some(next_symbol) = symbols.get(idx + 1) {
263 if cmp(&symbols[last_batch_start], next_symbol) == Ordering::Equal {
264 continue;
265 }
266 }
267
268 let start = last_batch_start;
269 let end = idx + 1;
270 last_batch_start = end;
271
272 let key = symbols[start].name.as_str().to_ascii_lowercase();
273 let value = SymbolIndex::range_to_map_value(start, end);
274
275 builder.insert(key, value).unwrap();
276 }
277
278 let map = fst::Map::new(builder.into_inner().unwrap()).unwrap();
279 SymbolIndex { symbols, map }
280 }
281
282 pub fn len(&self) -> usize {
283 self.symbols.len()
284 }
285
286 pub fn memory_size(&self) -> usize {
287 self.map.as_fst().size() + self.symbols.len() * mem::size_of::<FileSymbol>()
288 }
289
290 pub(crate) fn for_files(
291 files: impl ParallelIterator<Item = (FileId, Parse<ast::SourceFile>)>,
292 ) -> SymbolIndex {
293 let symbols = files
294 .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id))
295 .collect::<Vec<_>>();
296 SymbolIndex::new(symbols)
297 }
298
299 fn range_to_map_value(start: usize, end: usize) -> u64 {
300 debug_assert![start <= (std::u32::MAX as usize)];
301 debug_assert![end <= (std::u32::MAX as usize)];
302
303 ((start as u64) << 32) | end as u64
304 }
305
306 fn map_value_to_range(value: u64) -> (usize, usize) {
307 let end = value as u32 as usize;
308 let start = (value >> 32) as usize;
309 (start, end)
310 }
311}
312
313impl Query {
314 pub(crate) fn search(self, indices: &[&SymbolIndex]) -> Vec<FileSymbol> {
315 let mut op = fst::map::OpBuilder::new();
316 for file_symbols in indices.iter() {
317 let automaton = fst::automaton::Subsequence::new(&self.lowercased);
318 op = op.add(file_symbols.map.search(automaton))
319 }
320 let mut stream = op.union();
321 let mut res = Vec::new();
322 while let Some((_, indexed_values)) = stream.next() {
323 for indexed_value in indexed_values {
324 let symbol_index = &indices[indexed_value.index];
325 let (start, end) = SymbolIndex::map_value_to_range(indexed_value.value);
326
327 for symbol in &symbol_index.symbols[start..end] {
328 if self.only_types && !is_type(symbol.kind) {
329 continue;
330 }
331 if self.exact && symbol.name != self.query {
332 continue;
333 }
334
335 res.push(symbol.clone());
336 if res.len() >= self.limit {
337 return res;
338 }
339 }
340 }
341 }
342 res
343 }
344}
345
346fn is_type(kind: SyntaxKind) -> bool {
347 matches!(kind, STRUCT | ENUM | TRAIT | TYPE_ALIAS)
348}
349
350/// The actual data that is stored in the index. It should be as compact as
351/// possible.
352#[derive(Debug, Clone, PartialEq, Eq, Hash)]
353pub struct FileSymbol {
354 pub file_id: FileId,
355 pub name: SmolStr,
356 pub kind: SyntaxKind,
357 pub range: TextRange,
358 pub ptr: SyntaxNodePtr,
359 pub name_range: Option<TextRange>,
360 pub container_name: Option<SmolStr>,
361}
362
363fn source_file_to_file_symbols(source_file: &SourceFile, file_id: FileId) -> Vec<FileSymbol> {
364 let mut symbols = Vec::new();
365 let mut stack = Vec::new();
366
367 for event in source_file.syntax().preorder() {
368 match event {
369 WalkEvent::Enter(node) => {
370 if let Some(mut symbol) = to_file_symbol(&node, file_id) {
371 symbol.container_name = stack.last().cloned();
372
373 stack.push(symbol.name.clone());
374 symbols.push(symbol);
375 }
376 }
377
378 WalkEvent::Leave(node) => {
379 if to_symbol(&node).is_some() {
380 stack.pop();
381 }
382 }
383 }
384 }
385
386 symbols
387}
388
389fn to_symbol(node: &SyntaxNode) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> {
390 fn decl<N: NameOwner>(node: N) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> {
391 let name = node.name()?;
392 let name_range = name.syntax().text_range();
393 let name = name.text().clone();
394 let ptr = SyntaxNodePtr::new(node.syntax());
395
396 Some((name, ptr, name_range))
397 }
398 match_ast! {
399 match node {
400 ast::Fn(it) => decl(it),
401 ast::Struct(it) => decl(it),
402 ast::Enum(it) => decl(it),
403 ast::Trait(it) => decl(it),
404 ast::Module(it) => decl(it),
405 ast::TypeAlias(it) => decl(it),
406 ast::Const(it) => decl(it),
407 ast::Static(it) => decl(it),
408 ast::MacroCall(it) => {
409 if it.is_macro_rules().is_some() {
410 decl(it)
411 } else {
412 None
413 }
414 },
415 _ => None,
416 }
417 }
418}
419
420fn to_file_symbol(node: &SyntaxNode, file_id: FileId) -> Option<FileSymbol> {
421 to_symbol(node).map(move |(name, ptr, name_range)| FileSymbol {
422 name,
423 kind: node.kind(),
424 range: node.text_range(),
425 ptr,
426 file_id,
427 name_range: Some(name_range),
428 container_name: None,
429 })
430}
diff --git a/crates/ra_ide_db/src/wasm_shims.rs b/crates/ra_ide_db/src/wasm_shims.rs
deleted file mode 100644
index 7af9f9d9b..000000000
--- a/crates/ra_ide_db/src/wasm_shims.rs
+++ /dev/null
@@ -1,19 +0,0 @@
1//! A version of `std::time::Instant` that doesn't panic in WASM.
2
3#[cfg(not(feature = "wasm"))]
4pub use std::time::Instant;
5
6#[cfg(feature = "wasm")]
7#[derive(Clone, Copy, Debug)]
8pub struct Instant;
9
10#[cfg(feature = "wasm")]
11impl Instant {
12 pub fn now() -> Self {
13 Self
14 }
15
16 pub fn elapsed(&self) -> std::time::Duration {
17 std::time::Duration::new(0, 0)
18 }
19}