Skip to content

Commit 7178fea

Browse files
committed
feat: add shared semantic index for references and modules
1 parent f65a6f0 commit 7178fea

5 files changed

Lines changed: 498 additions & 164 deletions

File tree

crates/ide/src/db/workspace_symbol_index_db.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ use vfs::FileId;
77

88
use crate::{
99
db::root_db::RootDb,
10-
workspace_symbols::{SymbolIndex, WorkspaceSymbol},
10+
workspace_symbols::{ModuleIndex, SemanticIndex, SymbolIndex, WorkspaceSymbol},
1111
};
1212

1313
#[salsa::query_group(WorkspaceSymbolIndexDbStorage)]
1414
pub trait WorkspaceSymbolIndexDb: SourceRootDb + HirDb {
1515
fn file_workspace_symbols(&self, file_id: FileId) -> Arc<[WorkspaceSymbol]>;
1616
fn source_root_symbol_index(&self, source_root_id: SourceRootId) -> Arc<SymbolIndex>;
17+
fn source_root_module_index(&self, source_root_id: SourceRootId) -> Arc<ModuleIndex>;
1718
}
1819

1920
fn file_workspace_symbols(
@@ -30,9 +31,30 @@ fn source_root_symbol_index(
3031
Arc::new(SymbolIndex::for_source_root(db, source_root_id))
3132
}
3233

34+
fn source_root_module_index(
35+
db: &dyn WorkspaceSymbolIndexDb,
36+
source_root_id: SourceRootId,
37+
) -> Arc<ModuleIndex> {
38+
Arc::new(ModuleIndex::for_source_root(db, source_root_id))
39+
}
40+
3341
pub(crate) fn source_root_symbol_index_for_root(
3442
db: &RootDb,
3543
source_root_id: SourceRootId,
3644
) -> Arc<SymbolIndex> {
3745
WorkspaceSymbolIndexDb::source_root_symbol_index(db, source_root_id)
3846
}
47+
48+
pub(crate) fn source_root_module_index_for_root(
49+
db: &RootDb,
50+
source_root_id: SourceRootId,
51+
) -> Arc<ModuleIndex> {
52+
WorkspaceSymbolIndexDb::source_root_module_index(db, source_root_id)
53+
}
54+
55+
pub(crate) fn source_root_semantic_index_for_root(
56+
db: &RootDb,
57+
source_root_id: SourceRootId,
58+
) -> Arc<SemanticIndex> {
59+
Arc::new(SemanticIndex::for_source_root(db, source_root_id))
60+
}

crates/ide/src/module_resolution.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use std::cmp::Ordering;
22

33
use hir::{
4-
base_db::{source_db::SourceRootDb, source_root::SourceRootRole},
4+
base_db::{
5+
source_db::{SourceDb, SourceRootDb},
6+
source_root::SourceRootRole,
7+
},
58
container::InModule,
69
db::HirDb,
710
hir_def::{
@@ -11,7 +14,7 @@ use hir::{
1114
lower_ident_opt,
1215
module::{ModuleId, instantiation::Instantiation},
1316
},
14-
scope::{ModuleEntry, ScopeResolution},
17+
scope::ModuleEntry,
1518
semantics::pathres::PathResolution,
1619
};
1720
use syntax::{
@@ -21,7 +24,7 @@ use syntax::{
2124
use utils::get::GetRef;
2225
use vfs::{FileId, VfsPath};
2326

24-
use crate::db::root_db::RootDb;
27+
use crate::db::{root_db::RootDb, workspace_symbol_index_db::source_root_module_index_for_root};
2528

2629
#[derive(Debug, Clone, PartialEq, Eq)]
2730
pub(crate) enum ModuleResolution {
@@ -154,13 +157,34 @@ fn resolve_module_name_with_policy(
154157
name: &Ident,
155158
policy: ModuleResolutionPolicy,
156159
) -> ModuleResolution {
157-
match db.unit_scope().resolve_module(name) {
158-
ScopeResolution::Unique(module_id) => ModuleResolution::Unique(module_id),
159-
ScopeResolution::Unresolved => ModuleResolution::Unresolved,
160-
ScopeResolution::Ambiguous(candidates) => {
161-
policy.resolve_ambiguous(db, candidates.into_vec())
162-
}
160+
let candidates = module_candidates(db, name);
161+
match candidates.as_slice() {
162+
[module_id] => ModuleResolution::Unique(*module_id),
163+
[] => ModuleResolution::Unresolved,
164+
_ => policy.resolve_ambiguous(db, candidates),
165+
}
166+
}
167+
168+
fn module_candidates(db: &RootDb, name: &Ident) -> Vec<ModuleId> {
169+
let mut source_root_ids =
170+
db.files().iter().map(|&file_id| db.source_root_id(file_id)).collect::<Vec<_>>();
171+
source_root_ids.sort_unstable();
172+
source_root_ids.dedup();
173+
174+
let mut candidates = Vec::new();
175+
for source_root_id in source_root_ids {
176+
let module_index = source_root_module_index_for_root(db, source_root_id);
177+
candidates.extend(
178+
module_index
179+
.module_definitions(name)
180+
.iter()
181+
.map(|module| (module.file_id, module.name_range.start(), module.module_id)),
182+
);
163183
}
184+
185+
candidates.sort_by_key(|(file_id, name_start, _)| (file_id.0, *name_start));
186+
candidates.dedup_by_key(|(_, _, module_id)| *module_id);
187+
candidates.into_iter().map(|(_, _, module_id)| module_id).collect()
164188
}
165189

166190
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Lines changed: 43 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,26 @@
1-
use std::cell::LazyCell;
2-
31
use hir::{
4-
base_db::{intern::Lookup, salsa::Database, source_db::SourceDb},
2+
base_db::{
3+
intern::Lookup,
4+
salsa::Database,
5+
source_db::{SourceDb, SourceRootDb},
6+
source_root::SourceRootId,
7+
},
58
container::{ContainerId, InFile},
69
semantics::Semantics,
710
source_map::IsSrc,
811
};
9-
use itertools::Itertools;
10-
use memchr::memmem::Finder;
1112
use nohash_hasher::IntMap;
1213
use rustc_hash::FxHashMap;
13-
use smallvec::SmallVec;
14-
use syntax::{
15-
SyntaxNode, SyntaxTokenWithParent, has_text_range::HasTextRange, ptr::SyntaxTokenPtr,
16-
token::TokenKindExt,
17-
};
18-
use triomphe::Arc;
19-
use utils::{
20-
get::Get,
21-
line_index::{TextRange, TextSize},
22-
};
14+
use syntax::{SyntaxTokenWithParent, ptr::SyntaxTokenPtr};
15+
use utils::{get::Get, line_index::TextRange};
2316
use vfs::FileId;
2417

2518
use super::{ReferenceCategory, ReferencesConfig};
2619
use crate::{
2720
ScopeVisibility,
28-
db::root_db::RootDb,
29-
definitions::{Definition, DefinitionClass},
30-
source_targets::SourceTargetRequestCache,
21+
db::{root_db::RootDb, workspace_symbol_index_db::source_root_semantic_index_for_root},
22+
definitions::Definition,
23+
workspace_symbols::SemanticReference,
3124
};
3225

3326
/// A search scope is a set of files and ranges within those files that should
@@ -136,6 +129,20 @@ impl SearchScope {
136129
pub(crate) fn range_for_file(&self, file_id: FileId) -> Option<Option<TextRange>> {
137130
self.0.get(&file_id).copied()
138131
}
132+
133+
pub(crate) fn contains(&self, file_id: FileId, range: TextRange) -> bool {
134+
self.range_for_file(file_id).is_some_and(|file_range| {
135+
file_range.is_none_or(|file_range| file_range.intersect(range).is_some())
136+
})
137+
}
138+
139+
fn source_root_ids(&self, db: &RootDb) -> Vec<SourceRootId> {
140+
let mut root_ids =
141+
self.0.keys().map(|file_id| db.source_root_id(*file_id)).collect::<Vec<_>>();
142+
root_ids.sort_unstable();
143+
root_ids.dedup();
144+
root_ids
145+
}
139146
}
140147

141148
pub(crate) struct ReferencesCtx<'a, 'b> {
@@ -147,19 +154,17 @@ pub(crate) struct ReferencesCtx<'a, 'b> {
147154
#[derive(Debug, Clone, Copy)]
148155
pub(crate) struct ReferenceToken {
149156
ptr: SyntaxTokenPtr,
157+
range: TextRange,
150158
category: ReferenceCategory,
151159
}
152160

153161
impl ReferenceToken {
154-
pub fn new(token: SyntaxTokenWithParent) -> Self {
155-
Self {
156-
ptr: SyntaxTokenPtr::from_token(token),
157-
category: ReferenceCategory::from_tok(token),
158-
}
162+
pub(crate) fn from_semantic_reference(reference: &SemanticReference) -> Self {
163+
Self { ptr: reference.ptr, range: reference.range, category: reference.category }
159164
}
160165

161166
pub fn range(&self) -> TextRange {
162-
self.ptr.range()
167+
self.range
163168
}
164169

165170
pub fn category(&self) -> ReferenceCategory {
@@ -184,142 +189,26 @@ impl<'a, 'b> ReferencesCtx<'a, 'b> {
184189
}
185190

186191
pub(crate) fn search(&self) -> IntMap<FileId, Vec<ReferenceToken>> {
187-
let sema = self.sema;
188-
let db = sema.db;
192+
let db = self.sema.db;
189193
let mut res: IntMap<_, Vec<_>> = IntMap::default();
190194

191-
let Some(name) = self.def.origins().into_iter().find_map(|def| def.name(db)) else {
192-
return res;
193-
};
194-
debug_assert! {{
195-
let names = self
196-
.def
197-
.origins()
198-
.into_iter()
199-
.filter_map(|def| def.name(sema.db))
200-
.collect_vec();
201-
!names.is_empty() && names.iter().all(|namei| namei == &name)
202-
}};
203-
204-
let def_ranges: SmallVec<[_; 6]> =
205-
self.def.origins().into_iter().filter_map(|def| def.name_range(db)).collect();
206-
207-
let finder = &Finder::new(&name);
208-
let mut source_target_cache = SourceTargetRequestCache::default();
209-
for (text, file_id, range) in self.scope_files() {
195+
for source_root_id in self.scope.source_root_ids(db) {
210196
self.sema.db.unwind_if_cancelled();
197+
let index = source_root_semantic_index_for_root(db, source_root_id);
198+
let Some(group) = index.references_for_definition(self.def) else {
199+
continue;
200+
};
211201

212-
let parsed_file = LazyCell::new(|| sema.parse_file(file_id));
213-
Self::match_text(&text, finder, range)
214-
.flat_map(|offset| {
215-
let Some(root) = (*parsed_file).root() else {
216-
return Vec::new();
217-
};
218-
Self::filter_tokens(
219-
sema.db,
220-
root,
221-
file_id,
222-
&def_ranges,
223-
offset,
224-
&mut source_target_cache,
225-
)
226-
})
227-
.filter(|tp| self.classify_and_filter(sema, file_id.into(), tp))
228-
.for_each(|token| {
229-
res.entry(file_id)
230-
.or_insert_with(|| Vec::with_capacity(Self::FILE_REF_CAPACITY))
231-
.push(ReferenceToken::new(token))
232-
});
202+
for reference in group.references.iter() {
203+
if !self.scope.contains(reference.file_id, reference.range) {
204+
continue;
205+
}
206+
res.entry(reference.file_id)
207+
.or_insert_with(|| Vec::with_capacity(Self::FILE_REF_CAPACITY))
208+
.push(ReferenceToken::from_semantic_reference(reference));
209+
}
233210
}
234211

235212
res
236213
}
237-
238-
fn scope_files(&self) -> impl Iterator<Item = (Arc<str>, FileId, TextRange)> + '_ {
239-
self.scope.0.iter().map(|(file_id, range)| {
240-
let text = self.sema.db.file_text(*file_id);
241-
let range = range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text)));
242-
(text, *file_id, range)
243-
})
244-
}
245-
246-
fn match_text<'c>(
247-
text: &'c str,
248-
finder: &'c Finder,
249-
search_range: TextRange,
250-
) -> impl Iterator<Item = TextSize> + 'c {
251-
finder.find_iter(text.as_bytes()).filter_map(move |idx| {
252-
let offset = TextSize::from(idx as u32);
253-
if !search_range.contains_inclusive(offset) {
254-
return None;
255-
}
256-
257-
// If this is not a word boundary, that means this is only part of an ident.
258-
if text[..idx].chars().next_back().is_some_and(|ch| ch.is_alphabetic() || ch == '_')
259-
|| text[idx + finder.needle().len()..]
260-
.chars()
261-
.next()
262-
.is_some_and(|ch| ch.is_alphanumeric() || ch == '_')
263-
{
264-
return None;
265-
}
266-
267-
Some(offset)
268-
})
269-
}
270-
271-
fn filter_tokens<'tree>(
272-
db: &RootDb,
273-
node: SyntaxNode<'tree>,
274-
file_id: FileId,
275-
names: &[InFile<TextRange>],
276-
offset: TextSize,
277-
source_target_cache: &mut SourceTargetRequestCache,
278-
) -> Vec<SyntaxTokenWithParent<'tree>> {
279-
let Some(target) = crate::source_targets::source_target_at_offset_with_cache(
280-
db,
281-
file_id,
282-
node,
283-
offset,
284-
super::token_precedence,
285-
source_target_cache,
286-
)
287-
.and_then(|resolution| resolution.resolved()) else {
288-
return Vec::new();
289-
};
290-
291-
target
292-
.into_tokens()
293-
.into_iter()
294-
.filter(|tok| tok.kind().name_like())
295-
.filter(|tok| {
296-
let Some(tok_range) = tok.text_range() else {
297-
return false;
298-
};
299-
300-
!names.iter().any(|InFile { value: range, file_id: name_file_id }| {
301-
tok_range == *range && *name_file_id == file_id.into()
302-
})
303-
})
304-
.collect()
305-
}
306-
307-
fn classify_and_filter<'tree>(
308-
&self,
309-
sema: &Semantics<'_, RootDb>,
310-
file_id: hir::file::HirFileId,
311-
tp: &SyntaxTokenWithParent<'tree>,
312-
) -> bool {
313-
let Some(def) = DefinitionClass::resolve(sema, file_id, *tp) else {
314-
return false;
315-
};
316-
317-
match def {
318-
DefinitionClass::Definition(def) => def == *self.def,
319-
DefinitionClass::PortConnShorthand { local, port } => {
320-
local == *self.def || port == *self.def
321-
}
322-
DefinitionClass::Ambiguous(_) => false,
323-
}
324-
}
325214
}

0 commit comments

Comments
 (0)