1use std::mem;
8use std::{cell::LazyCell, cmp::Reverse};
9
10use base_db::{SourceDatabase, all_crates};
11use either::Either;
12use hir::{
13 Adt, AsAssocItem, DefWithBody, EditionedFileId, ExpressionStoreOwner, FileRange,
14 FileRangeWrapper, HasAttrs, HasContainer, HasSource, InFile, InFileWrapper, InRealFile,
15 InlineAsmOperand, ItemContainer, ModuleSource, PathResolution, Semantics, Visibility,
16};
17use memchr::memmem::Finder;
18use parser::SyntaxKind;
19use rustc_hash::{FxHashMap, FxHashSet};
20use salsa::Database;
21use syntax::{
22 AstNode, AstToken, SmolStr, SyntaxElement, SyntaxNode, TextRange, TextSize, ToSmolStr,
23 ast::{self, HasName, Rename},
24 match_ast,
25};
26use triomphe::Arc;
27
28use crate::{
29 RootDatabase,
30 defs::{Definition, NameClass, NameRefClass},
31 traits::{as_trait_assoc_def, convert_to_def_in_trait},
32};
33
34#[derive(Debug, Default, Clone)]
35pub struct UsageSearchResult {
36 pub references: FxHashMap<EditionedFileId, Vec<FileReference>>,
37}
38
39impl UsageSearchResult {
40 pub fn is_empty(&self) -> bool {
41 self.references.is_empty()
42 }
43
44 pub fn len(&self) -> usize {
45 self.references.len()
46 }
47
48 pub fn iter(&self) -> impl Iterator<Item = (EditionedFileId, &[FileReference])> + '_ {
49 self.references.iter().map(|(&file_id, refs)| (file_id, &**refs))
50 }
51
52 pub fn file_ranges(&self) -> impl Iterator<Item = FileRange> + '_ {
53 self.references.iter().flat_map(|(&file_id, refs)| {
54 refs.iter().map(move |&FileReference { range, .. }| FileRange { file_id, range })
55 })
56 }
57}
58
59impl IntoIterator for UsageSearchResult {
60 type Item = (EditionedFileId, Vec<FileReference>);
61 type IntoIter = <FxHashMap<EditionedFileId, Vec<FileReference>> as IntoIterator>::IntoIter;
62
63 fn into_iter(self) -> Self::IntoIter {
64 self.references.into_iter()
65 }
66}
67
68#[derive(Debug, Clone)]
69pub struct FileReference {
70 pub range: TextRange,
72 pub name: FileReferenceNode,
74 pub category: ReferenceCategory,
75}
76
77#[derive(Debug, Clone)]
78pub enum FileReferenceNode {
79 Name(ast::Name),
80 NameRef(ast::NameRef),
81 Lifetime(ast::Lifetime),
82 FormatStringEntry(ast::String, TextRange),
83}
84
85impl FileReferenceNode {
86 pub fn text_range(&self) -> TextRange {
87 match self {
88 FileReferenceNode::Name(it) => it.syntax().text_range(),
89 FileReferenceNode::NameRef(it) => it.syntax().text_range(),
90 FileReferenceNode::Lifetime(it) => it.syntax().text_range(),
91 FileReferenceNode::FormatStringEntry(_, range) => *range,
92 }
93 }
94 pub fn syntax(&self) -> SyntaxElement {
95 match self {
96 FileReferenceNode::Name(it) => it.syntax().clone().into(),
97 FileReferenceNode::NameRef(it) => it.syntax().clone().into(),
98 FileReferenceNode::Lifetime(it) => it.syntax().clone().into(),
99 FileReferenceNode::FormatStringEntry(it, _) => it.syntax().clone().into(),
100 }
101 }
102 pub fn into_name_like(self) -> Option<ast::NameLike> {
103 match self {
104 FileReferenceNode::Name(it) => Some(ast::NameLike::Name(it)),
105 FileReferenceNode::NameRef(it) => Some(ast::NameLike::NameRef(it)),
106 FileReferenceNode::Lifetime(it) => Some(ast::NameLike::Lifetime(it)),
107 FileReferenceNode::FormatStringEntry(_, _) => None,
108 }
109 }
110 pub fn as_name_ref(&self) -> Option<&ast::NameRef> {
111 match self {
112 FileReferenceNode::NameRef(name_ref) => Some(name_ref),
113 _ => None,
114 }
115 }
116 pub fn as_lifetime(&self) -> Option<&ast::Lifetime> {
117 match self {
118 FileReferenceNode::Lifetime(lifetime) => Some(lifetime),
119 _ => None,
120 }
121 }
122 pub fn text(&self) -> syntax::TokenText<'_> {
123 match self {
124 FileReferenceNode::NameRef(name_ref) => name_ref.text(),
125 FileReferenceNode::Name(name) => name.text(),
126 FileReferenceNode::Lifetime(lifetime) => lifetime.text(),
127 FileReferenceNode::FormatStringEntry(it, range) => {
128 syntax::TokenText::borrowed(&it.text()[*range - it.syntax().text_range().start()])
129 }
130 }
131 }
132}
133
134bitflags::bitflags! {
135 #[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
136 pub struct ReferenceCategory: u8 {
137 const WRITE = 1 << 0;
140 const READ = 1 << 1;
141 const IMPORT = 1 << 2;
142 const TEST = 1 << 3;
143 }
144}
145
146#[derive(Clone, Debug)]
151pub struct SearchScope {
152 entries: FxHashMap<EditionedFileId, Option<TextRange>>,
153}
154
155impl SearchScope {
156 fn new(entries: FxHashMap<EditionedFileId, Option<TextRange>>) -> SearchScope {
157 SearchScope { entries }
158 }
159
160 fn crate_graph(db: &RootDatabase) -> SearchScope {
162 let mut entries = FxHashMap::default();
163
164 let all_crates = all_crates(db);
165 for &krate in all_crates.iter() {
166 let crate_data = krate.data(db);
167 let source_root = db.file_source_root(crate_data.root_file_id).source_root_id(db);
168 let source_root = db.source_root(source_root).source_root(db);
169 entries.extend(
170 source_root
171 .iter()
172 .map(|id| (EditionedFileId::new(db, id, crate_data.edition), None)),
173 );
174 }
175 SearchScope { entries }
176 }
177
178 fn reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope {
180 let mut entries = FxHashMap::default();
181 for rev_dep in of.transitive_reverse_dependencies(db) {
182 let root_file = rev_dep.root_file(db);
183
184 let source_root = db.file_source_root(root_file).source_root_id(db);
185 let source_root = db.source_root(source_root).source_root(db);
186 entries.extend(
187 source_root
188 .iter()
189 .map(|id| (EditionedFileId::new(db, id, rev_dep.edition(db)), None)),
190 );
191 }
192 SearchScope { entries }
193 }
194
195 fn krate(db: &RootDatabase, of: hir::Crate) -> SearchScope {
197 let root_file = of.root_file(db);
198
199 let source_root_id = db.file_source_root(root_file).source_root_id(db);
200 let source_root = db.source_root(source_root_id).source_root(db);
201 SearchScope {
202 entries: source_root
203 .iter()
204 .map(|id| (EditionedFileId::new(db, id, of.edition(db)), None))
205 .collect(),
206 }
207 }
208
209 pub fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
211 let mut entries = FxHashMap::default();
212
213 let (file_id, range) = {
214 let InFile { file_id, value } = module.definition_source_range(db);
215 if let Some(InRealFile { file_id, value: call_source }) = file_id.original_call_node(db)
216 {
217 (file_id, Some(call_source.text_range()))
218 } else {
219 (file_id.original_file(db), Some(value))
220 }
221 };
222 entries.entry(file_id).or_insert(range);
223
224 let mut to_visit: Vec<_> = module.children(db).collect();
225 while let Some(module) = to_visit.pop() {
226 if let Some(file_id) = module.as_source_file_id(db) {
227 entries.insert(file_id, None);
228 }
229 to_visit.extend(module.children(db));
230 }
231 SearchScope { entries }
232 }
233
234 pub fn empty() -> SearchScope {
236 SearchScope::new(FxHashMap::default())
237 }
238
239 pub fn single_file(file: EditionedFileId) -> SearchScope {
241 SearchScope::new(std::iter::once((file, None)).collect())
242 }
243
244 pub fn file_range(range: FileRange) -> SearchScope {
246 SearchScope::new(std::iter::once((range.file_id, Some(range.range))).collect())
247 }
248
249 pub fn files(files: &[EditionedFileId]) -> SearchScope {
251 SearchScope::new(files.iter().map(|f| (*f, None)).collect())
252 }
253
254 pub fn intersection(&self, other: &SearchScope) -> SearchScope {
255 let (mut small, mut large) = (&self.entries, &other.entries);
256 if small.len() > large.len() {
257 mem::swap(&mut small, &mut large)
258 }
259
260 let intersect_ranges =
261 |r1: Option<TextRange>, r2: Option<TextRange>| -> Option<Option<TextRange>> {
262 match (r1, r2) {
263 (None, r) | (r, None) => Some(r),
264 (Some(r1), Some(r2)) => r1.intersect(r2).map(Some),
265 }
266 };
267 let res = small
268 .iter()
269 .filter_map(|(&file_id, &r1)| {
270 let &r2 = large.get(&file_id)?;
271 let r = intersect_ranges(r1, r2)?;
272 Some((file_id, r))
273 })
274 .collect();
275
276 SearchScope::new(res)
277 }
278}
279
280impl IntoIterator for SearchScope {
281 type Item = (EditionedFileId, Option<TextRange>);
282 type IntoIter = std::collections::hash_map::IntoIter<EditionedFileId, Option<TextRange>>;
283
284 fn into_iter(self) -> Self::IntoIter {
285 self.entries.into_iter()
286 }
287}
288
289impl Definition {
290 fn search_scope(&self, db: &RootDatabase) -> SearchScope {
291 let _p = tracing::info_span!("search_scope").entered();
292
293 if let Definition::BuiltinType(_) = self {
294 return SearchScope::crate_graph(db);
295 }
296
297 if let &Definition::Module(module) = self
299 && module.is_crate_root(db)
300 {
301 return SearchScope::reverse_dependencies(db, module.krate(db));
302 }
303
304 let module = match self.module(db) {
305 Some(it) => it,
306 None => return SearchScope::empty(),
307 };
308 let InFile { file_id, value: module_source } = module.definition_source(db);
309 let file_id = file_id.original_file(db);
310
311 if let Definition::Local(var) = self {
312 let def = match var.parent(db) {
313 ExpressionStoreOwner::Body(def) => match def {
314 DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
315 DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
316 DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
317 DefWithBody::EnumVariant(v) => v.source(db).map(|src| src.syntax().cloned()),
318 },
319 ExpressionStoreOwner::Signature(def) => match def {
320 hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()),
321 hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()),
322 hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()),
323 hir::GenericDef::TypeAlias(it) => {
324 it.source(db).map(|src| src.syntax().cloned())
325 }
326 hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()),
327 hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()),
328 hir::GenericDef::Static(it) => it.source(db).map(|src| src.syntax().cloned()),
329 },
330 ExpressionStoreOwner::VariantFields(it) => {
331 it.source(db).map(|src| src.syntax().cloned())
332 }
333 };
334 return match def {
335 Some(def) => SearchScope::file_range(
336 def.as_ref().original_file_range_with_macro_call_input(db),
337 ),
338 None => SearchScope::single_file(file_id),
339 };
340 }
341
342 if let Definition::InlineAsmOperand(op) = self {
343 let def = match op.parent(db) {
344 ExpressionStoreOwner::Body(def) => match def {
345 DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
346 DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
347 DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
348 DefWithBody::EnumVariant(v) => v.source(db).map(|src| src.syntax().cloned()),
349 },
350 ExpressionStoreOwner::Signature(def) => match def {
351 hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()),
352 hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()),
353 hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()),
354 hir::GenericDef::TypeAlias(it) => {
355 it.source(db).map(|src| src.syntax().cloned())
356 }
357 hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()),
358 hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()),
359 hir::GenericDef::Static(it) => it.source(db).map(|src| src.syntax().cloned()),
360 },
361 ExpressionStoreOwner::VariantFields(it) => {
362 it.source(db).map(|src| src.syntax().cloned())
363 }
364 };
365 return match def {
366 Some(def) => SearchScope::file_range(
367 def.as_ref().original_file_range_with_macro_call_input(db),
368 ),
369 None => SearchScope::single_file(file_id),
370 };
371 }
372
373 if let Definition::SelfType(impl_) = self {
374 return match impl_.source(db).map(|src| src.syntax().cloned()) {
375 Some(def) => SearchScope::file_range(
376 def.as_ref().original_file_range_with_macro_call_input(db),
377 ),
378 None => SearchScope::single_file(file_id),
379 };
380 }
381
382 if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self {
383 let def = match param.parent(db) {
384 hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()),
385 hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()),
386 hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()),
387 hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()),
388 hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()),
389 hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()),
390 hir::GenericDef::Static(it) => it.source(db).map(|src| src.syntax().cloned()),
391 };
392 return match def {
393 Some(def) => SearchScope::file_range(
394 def.as_ref().original_file_range_with_macro_call_input(db),
395 ),
396 None => SearchScope::single_file(file_id),
397 };
398 }
399
400 if let Definition::Macro(macro_def) = self {
401 return match macro_def.kind(db) {
402 hir::MacroKind::Declarative => {
403 if macro_def.attrs(db).is_macro_export() {
404 SearchScope::reverse_dependencies(db, module.krate(db))
405 } else {
406 SearchScope::krate(db, module.krate(db))
407 }
408 }
409 hir::MacroKind::AttrBuiltIn
410 | hir::MacroKind::DeriveBuiltIn
411 | hir::MacroKind::DeclarativeBuiltIn => SearchScope::crate_graph(db),
412 hir::MacroKind::Derive | hir::MacroKind::Attr | hir::MacroKind::ProcMacro => {
413 SearchScope::reverse_dependencies(db, module.krate(db))
414 }
415 };
416 }
417
418 if let Definition::DeriveHelper(_) = self {
419 return SearchScope::reverse_dependencies(db, module.krate(db));
420 }
421
422 if let Some(vis) = self.visibility(db) {
423 return match vis {
424 Visibility::Module(module, _) => {
425 SearchScope::module_and_children(db, module.into())
426 }
427 Visibility::PubCrate(krate) => SearchScope::krate(db, krate.into()),
428 Visibility::Public => SearchScope::reverse_dependencies(db, module.krate(db)),
429 };
430 }
431
432 let range = match module_source {
433 ModuleSource::Module(m) => Some(m.syntax().text_range()),
434 ModuleSource::BlockExpr(b) => Some(b.syntax().text_range()),
435 ModuleSource::SourceFile(_) => None,
436 };
437 match range {
438 Some(range) => SearchScope::file_range(FileRange { file_id, range }),
439 None => SearchScope::single_file(file_id),
440 }
441 }
442
443 pub fn usages<'a>(self, sema: &'a Semantics<'_, RootDatabase>) -> FindUsages<'a> {
444 FindUsages {
445 def: self,
446 rename: None,
447 assoc_item_container: self.as_assoc_item(sema.db).map(|a| a.container(sema.db)),
448 sema,
449 scope: None,
450 include_self_kw_refs: None,
451 search_self_mod: false,
452 included_categories: ReferenceCategory::all(),
453 exclude_library_files: false,
454 }
455 }
456}
457
458#[derive(Clone)]
459pub struct FindUsages<'a> {
460 def: Definition,
461 rename: Option<&'a Rename>,
462 sema: &'a Semantics<'a, RootDatabase>,
463 scope: Option<&'a SearchScope>,
464 assoc_item_container: Option<hir::AssocItemContainer>,
466 include_self_kw_refs: Option<hir::Type<'a>>,
468 search_self_mod: bool,
470 included_categories: ReferenceCategory,
472 exclude_library_files: bool,
474}
475
476impl<'a> FindUsages<'a> {
477 pub fn include_self_refs(mut self) -> Self {
479 self.include_self_kw_refs = def_to_ty(self.sema, &self.def);
480 self.search_self_mod = true;
481 self
482 }
483
484 pub fn in_scope(self, scope: &'a SearchScope) -> Self {
486 self.set_scope(Some(scope))
487 }
488
489 pub fn set_scope(mut self, scope: Option<&'a SearchScope>) -> Self {
491 assert!(self.scope.is_none());
492 self.scope = scope;
493 self
494 }
495
496 pub fn with_rename(mut self, rename: Option<&'a Rename>) -> Self {
500 self.rename = rename;
501 self
502 }
503
504 pub fn set_included_categories(mut self, categories: ReferenceCategory) -> Self {
505 self.included_categories = categories;
506 self
507 }
508
509 pub fn set_exclude_library_files(mut self, exclude_library_files: bool) -> Self {
510 self.exclude_library_files = exclude_library_files;
511 self
512 }
513
514 pub fn at_least_one(&self) -> bool {
515 let mut found = false;
516 self.search(&mut |_, _| {
517 found = true;
518 true
519 });
520 found
521 }
522
523 pub fn all(self) -> UsageSearchResult {
524 let mut res = UsageSearchResult::default();
525 self.search(&mut |file_id, reference| {
526 res.references.entry(file_id).or_default().push(reference);
527 false
528 });
529 res
530 }
531
532 fn scope_files<'b>(
533 db: &'b RootDatabase,
534 scope: &'b SearchScope,
535 exclude_library_files: bool,
536 ) -> impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)> + 'b {
537 scope
538 .entries
539 .iter()
540 .filter(move |(file_id, _)| {
541 !exclude_library_files || !is_library_file(db, file_id.file_id(db))
542 })
543 .map(|(&file_id, &search_range)| {
544 let text = db.file_text(file_id.file_id(db)).text(db);
545 let search_range =
546 search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&**text)));
547
548 (text.clone(), file_id, search_range)
549 })
550 }
551
552 fn match_indices<'b>(
553 text: &'b str,
554 finder: &'b Finder<'b>,
555 search_range: TextRange,
556 ) -> impl Iterator<Item = TextSize> + 'b {
557 finder.find_iter(text.as_bytes()).filter_map(move |idx| {
558 let offset: TextSize = idx.try_into().unwrap();
559 if !search_range.contains_inclusive(offset) {
560 return None;
561 }
562 if text[..idx]
566 .chars()
567 .next_back()
568 .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_'))
569 || text[idx + finder.needle().len()..]
570 .chars()
571 .next()
572 .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_' | '0'..='9'))
573 {
574 return None;
575 }
576 Some(offset)
577 })
578 }
579
580 fn find_nodes<'b>(
581 sema: &'b Semantics<'_, RootDatabase>,
582 name: &str,
583 file_id: EditionedFileId,
584 node: &syntax::SyntaxNode,
585 offset: TextSize,
586 ) -> impl Iterator<Item = SyntaxNode> + 'b {
587 node.token_at_offset(offset)
588 .find(|it| {
589 it.text().trim_start_matches('\'').trim_start_matches("r#") == name
591 })
592 .into_iter()
593 .flat_map(move |token| {
594 if sema.is_inside_macro_call(InFile::new(file_id.into(), &token)) {
595 sema.descend_into_macros_exact(token)
596 } else {
597 <_>::from([token])
598 }
599 .into_iter()
600 .filter_map(|it| it.parent())
601 })
602 }
603
604 fn short_associated_function_fast_search(
620 &self,
621 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
622 search_scope: &SearchScope,
623 name: &str,
624 ) -> bool {
625 if self.scope.is_some() {
626 return false;
627 }
628
629 let _p = tracing::info_span!("short_associated_function_fast_search").entered();
630
631 let container = (|| {
632 let Definition::Function(function) = self.def else {
633 return None;
634 };
635 if function.has_self_param(self.sema.db) {
636 return None;
637 }
638 match function.container(self.sema.db) {
639 ItemContainer::Impl(impl_) => {
642 let has_trait = impl_.trait_(self.sema.db).is_some();
643 if has_trait {
644 return None;
645 }
646 let adt = impl_.self_ty(self.sema.db).as_adt()?;
647 Some(adt)
648 }
649 _ => None,
650 }
651 })();
652 let Some(container) = container else {
653 return false;
654 };
655
656 fn has_any_name(node: &SyntaxNode, mut predicate: impl FnMut(&str) -> bool) -> bool {
657 node.descendants().any(|node| {
658 match_ast! {
659 match node {
660 ast::Name(it) => predicate(it.text().trim_start_matches("r#")),
661 ast::NameRef(it) => predicate(it.text().trim_start_matches("r#")),
662 _ => false
663 }
664 }
665 })
666 }
667
668 fn collect_possible_aliases(
673 sema: &Semantics<'_, RootDatabase>,
674 container: Adt,
675 exclude_library_files: bool,
676 ) -> Option<(FxHashSet<SmolStr>, Vec<FileRangeWrapper<EditionedFileId>>)> {
677 fn insert_type_alias(
678 db: &RootDatabase,
679 to_process: &mut Vec<(SmolStr, SearchScope)>,
680 alias_name: &str,
681 def: Definition,
682 ) {
683 let alias = alias_name.trim_start_matches("r#").to_smolstr();
684 tracing::debug!("found alias: {alias}");
685 to_process.push((alias, def.search_scope(db)));
686 }
687
688 let _p = tracing::info_span!("collect_possible_aliases").entered();
689
690 let db = sema.db;
691 let container_name = container.name(db).as_str().to_smolstr();
692 let search_scope = Definition::from(container).search_scope(db);
693 let mut seen = FxHashSet::default();
694 let mut completed = FxHashSet::default();
695 let mut to_process = vec![(container_name, search_scope)];
696 let mut is_possibly_self = Vec::new();
697 let mut total_files_searched = 0;
698
699 while let Some((current_to_process, current_to_process_search_scope)) = to_process.pop()
700 {
701 let is_alias = |alias: &ast::TypeAlias| {
702 let def = sema.to_def(alias)?;
703 let ty = def.ty(db);
704 let is_alias = ty.as_adt()? == container;
705 is_alias.then_some(def)
706 };
707
708 let finder = Finder::new(current_to_process.as_bytes());
709 for (file_text, file_id, search_range) in FindUsages::scope_files(
710 db,
711 ¤t_to_process_search_scope,
712 exclude_library_files,
713 ) {
714 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
715
716 for offset in FindUsages::match_indices(&file_text, &finder, search_range) {
717 let usages = FindUsages::find_nodes(
718 sema,
719 ¤t_to_process,
720 file_id,
721 &tree,
722 offset,
723 )
724 .filter(|it| matches!(it.kind(), SyntaxKind::NAME | SyntaxKind::NAME_REF));
725 for usage in usages {
726 if let Some(alias) = usage.parent().and_then(|it| {
727 let path = ast::PathSegment::cast(it)?.parent_path();
728 let use_tree = ast::UseTree::cast(path.syntax().parent()?)?;
729 use_tree.rename()?.name()
730 }) {
731 if seen.insert(InFileWrapper::new(
732 file_id,
733 alias.syntax().text_range(),
734 )) {
735 tracing::debug!("found alias: {alias}");
736 cov_mark::hit!(container_use_rename);
737 to_process.push((
739 alias.text().to_smolstr(),
740 current_to_process_search_scope.clone(),
741 ));
742 }
743 } else if let Some(alias) =
744 usage.ancestors().find_map(ast::TypeAlias::cast)
745 && let Some(name) = alias.name()
746 && seen
747 .insert(InFileWrapper::new(file_id, name.syntax().text_range()))
748 {
749 if let Some(def) = is_alias(&alias) {
750 cov_mark::hit!(container_type_alias);
751 insert_type_alias(
752 sema.db,
753 &mut to_process,
754 name.text().as_str(),
755 def.into(),
756 );
757 } else {
758 cov_mark::hit!(same_name_different_def_type_alias);
759 }
760 }
761
762 let impl_ = 'impl_: {
764 for ancestor in usage.ancestors() {
765 if let Some(parent) = ancestor.parent()
766 && let Some(parent) = ast::Impl::cast(parent)
767 {
768 if matches!(
770 ancestor.kind(),
771 SyntaxKind::ASSOC_ITEM_LIST
772 | SyntaxKind::WHERE_CLAUSE
773 | SyntaxKind::GENERIC_PARAM_LIST
774 ) {
775 break;
776 }
777 if parent
778 .trait_()
779 .is_some_and(|trait_| *trait_.syntax() == ancestor)
780 {
781 break;
782 }
783
784 break 'impl_ Some(parent);
786 }
787 }
788 None
789 };
790 (|| {
791 let impl_ = impl_?;
792 is_possibly_self.push(sema.original_range(impl_.syntax()));
793 let assoc_items = impl_.assoc_item_list()?;
794 let type_aliases = assoc_items
795 .syntax()
796 .descendants()
797 .filter_map(ast::TypeAlias::cast);
798 for type_alias in type_aliases {
799 let Some(ty) = type_alias.ty() else { continue };
800 let Some(name) = type_alias.name() else { continue };
801 let contains_self = ty
802 .syntax()
803 .descendants_with_tokens()
804 .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW);
805 if !contains_self {
806 continue;
807 }
808 if seen.insert(InFileWrapper::new(
809 file_id,
810 name.syntax().text_range(),
811 )) {
812 if let Some(def) = is_alias(&type_alias) {
813 cov_mark::hit!(self_type_alias);
814 insert_type_alias(
815 sema.db,
816 &mut to_process,
817 name.text().as_str(),
818 def.into(),
819 );
820 } else {
821 cov_mark::hit!(same_name_different_def_type_alias);
822 }
823 }
824 }
825 Some(())
826 })();
827 }
828 }
829 }
830
831 completed.insert(current_to_process);
832
833 total_files_searched += current_to_process_search_scope.entries.len();
834 if total_files_searched > 20_000 && completed.len() > 100 {
836 tracing::info!(aliases_count = %completed.len(), "too much aliases; leaving fast path");
841 return None;
842 }
843 }
844
845 is_possibly_self.sort_unstable_by_key(|position| {
847 (position.file_id, position.range.start(), Reverse(position.range.end()))
848 });
849 is_possibly_self.dedup_by(|pos2, pos1| {
850 pos1.file_id == pos2.file_id
851 && pos1.range.start() <= pos2.range.start()
852 && pos1.range.end() >= pos2.range.end()
853 });
854
855 tracing::info!(aliases_count = %completed.len(), "aliases search completed");
856
857 Some((completed, is_possibly_self))
858 }
859
860 fn search(
861 this: &FindUsages<'_>,
862 finder: &Finder<'_>,
863 name: &str,
864 files: impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)>,
865 mut container_predicate: impl FnMut(
866 &SyntaxNode,
867 InFileWrapper<EditionedFileId, TextRange>,
868 ) -> bool,
869 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
870 ) {
871 for (file_text, file_id, search_range) in files {
872 let tree = LazyCell::new(move || this.sema.parse(file_id).syntax().clone());
873
874 for offset in FindUsages::match_indices(&file_text, finder, search_range) {
875 let usages = FindUsages::find_nodes(this.sema, name, file_id, &tree, offset)
876 .filter_map(ast::NameRef::cast);
877 for usage in usages {
878 let found_usage = usage
879 .syntax()
880 .parent()
881 .and_then(ast::PathSegment::cast)
882 .map(|path_segment| {
883 container_predicate(
884 path_segment.parent_path().syntax(),
885 InFileWrapper::new(file_id, usage.syntax().text_range()),
886 )
887 })
888 .unwrap_or(false);
889 if found_usage {
890 this.found_name_ref(&usage, sink);
891 }
892 }
893 }
894 }
895 }
896
897 let Some((container_possible_aliases, is_possibly_self)) =
898 collect_possible_aliases(self.sema, container, self.exclude_library_files)
899 else {
900 return false;
901 };
902
903 cov_mark::hit!(short_associated_function_fast_search);
904
905 let finder = Finder::new(name.as_bytes());
908 let mut self_positions = FxHashSet::default();
910 tracing::info_span!("Self_search").in_scope(|| {
911 search(
912 self,
913 &finder,
914 name,
915 is_possibly_self.into_iter().map(|position| {
916 (position.file_text(self.sema.db).clone(), position.file_id, position.range)
917 }),
918 |path, name_position| {
919 let has_self = path
920 .descendants_with_tokens()
921 .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW);
922 if has_self {
923 self_positions.insert(name_position);
924 }
925 has_self
926 },
927 sink,
928 )
929 });
930 tracing::info_span!("aliases_search").in_scope(|| {
931 search(
932 self,
933 &finder,
934 name,
935 FindUsages::scope_files(self.sema.db, search_scope, self.exclude_library_files),
936 |path, name_position| {
937 has_any_name(path, |name| container_possible_aliases.contains(name))
938 && !self_positions.contains(&name_position)
939 },
940 sink,
941 )
942 });
943
944 true
945 }
946
947 pub fn search(&self, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool) {
948 let _p = tracing::info_span!("FindUsages:search").entered();
949 let sema = self.sema;
950
951 let search_scope = {
952 let base =
954 as_trait_assoc_def(sema.db, self.def).unwrap_or(self.def).search_scope(sema.db);
955 match &self.scope {
956 None => base,
957 Some(scope) => base.intersection(scope),
958 }
959 };
960 if search_scope.entries.is_empty() {
961 return;
962 }
963
964 let name = match (self.rename, self.def) {
965 (Some(rename), _) => {
966 if rename.underscore_token().is_some() {
967 None
968 } else {
969 rename.name().map(|n| n.to_smolstr())
970 }
971 }
972 (_, Definition::Module(module)) if module.is_crate_root(self.sema.db) => {
974 module
979 .krate(self.sema.db)
980 .display_name(self.sema.db)
981 .map(|crate_name| crate_name.crate_name().symbol().as_str().into())
982 }
983 _ => {
984 let self_kw_refs = || {
985 self.include_self_kw_refs.as_ref().and_then(|ty| {
986 ty.as_adt()
987 .map(|adt| adt.name(self.sema.db))
988 .or_else(|| ty.as_builtin().map(|builtin| builtin.name()))
989 })
990 };
991 self.def
995 .name(sema.db)
996 .or_else(self_kw_refs)
997 .map(|it| it.as_str().trim_start_matches('\'').to_smolstr())
998 }
999 };
1000 let name = match &name {
1001 Some(s) => s.as_str(),
1002 None => return,
1003 };
1004
1005 if name.len() <= 7 && self.short_associated_function_fast_search(sink, &search_scope, name)
1007 {
1008 return;
1009 }
1010
1011 let finder = &Finder::new(name);
1012 let include_self_kw_refs =
1013 self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
1014 for (text, file_id, search_range) in
1015 Self::scope_files(sema.db, &search_scope, self.exclude_library_files)
1016 {
1017 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
1018
1019 for offset in Self::match_indices(&text, finder, search_range) {
1021 let ret = tree.token_at_offset(offset).any(|token| {
1022 if let Some((range, _frange, string_token, Some(nameres))) =
1023 sema.check_for_format_args_template(token.clone(), offset)
1024 {
1025 return self.found_format_args_ref(
1026 file_id,
1027 range,
1028 string_token,
1029 nameres,
1030 sink,
1031 );
1032 }
1033 false
1034 });
1035 if ret {
1036 return;
1037 }
1038
1039 for name in Self::find_nodes(sema, name, file_id, &tree, offset)
1040 .filter_map(ast::NameLike::cast)
1041 {
1042 if match name {
1043 ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
1044 ast::NameLike::Name(name) => self.found_name(&name, sink),
1045 ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
1046 } {
1047 return;
1048 }
1049 }
1050 }
1051 if let Some((self_ty, finder)) = &include_self_kw_refs {
1053 for offset in Self::match_indices(&text, finder, search_range) {
1054 for name_ref in Self::find_nodes(sema, "Self", file_id, &tree, offset)
1055 .filter_map(ast::NameRef::cast)
1056 {
1057 if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
1058 return;
1059 }
1060 }
1061 }
1062 }
1063 }
1064
1065 if let Definition::Module(module) = self.def {
1067 let scope =
1068 search_scope.intersection(&SearchScope::module_and_children(self.sema.db, module));
1069
1070 let is_crate_root = module.is_crate_root(self.sema.db).then(|| Finder::new("crate"));
1071 let finder = &Finder::new("super");
1072
1073 for (text, file_id, search_range) in
1074 Self::scope_files(sema.db, &scope, self.exclude_library_files)
1075 {
1076 self.sema.db.unwind_if_revision_cancelled();
1077
1078 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
1079
1080 for offset in Self::match_indices(&text, finder, search_range) {
1081 for name_ref in Self::find_nodes(sema, "super", file_id, &tree, offset)
1082 .filter_map(ast::NameRef::cast)
1083 {
1084 if self.found_name_ref(&name_ref, sink) {
1085 return;
1086 }
1087 }
1088 }
1089 if let Some(finder) = &is_crate_root {
1090 for offset in Self::match_indices(&text, finder, search_range) {
1091 for name_ref in Self::find_nodes(sema, "crate", file_id, &tree, offset)
1092 .filter_map(ast::NameRef::cast)
1093 {
1094 if self.found_name_ref(&name_ref, sink) {
1095 return;
1096 }
1097 }
1098 }
1099 }
1100 }
1101 }
1102
1103 match self.def {
1105 Definition::Module(module) if self.search_self_mod => {
1106 let src = module.definition_source(sema.db);
1107 let file_id = src.file_id.original_file(sema.db);
1108 let (file_id, search_range) = match src.value {
1109 ModuleSource::Module(m) => (file_id, Some(m.syntax().text_range())),
1110 ModuleSource::BlockExpr(b) => (file_id, Some(b.syntax().text_range())),
1111 ModuleSource::SourceFile(_) => (file_id, None),
1112 };
1113
1114 let search_range = if let Some(&range) = search_scope.entries.get(&file_id) {
1115 match (range, search_range) {
1116 (None, range) | (range, None) => range,
1117 (Some(range), Some(search_range)) => match range.intersect(search_range) {
1118 Some(range) => Some(range),
1119 None => return,
1120 },
1121 }
1122 } else {
1123 return;
1124 };
1125
1126 let file_text = sema.db.file_text(file_id.file_id(self.sema.db));
1127 let text = file_text.text(sema.db);
1128 let search_range =
1129 search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&**text)));
1130
1131 let tree = LazyCell::new(|| sema.parse(file_id).syntax().clone());
1132 let finder = &Finder::new("self");
1133
1134 for offset in Self::match_indices(text, finder, search_range) {
1135 for name_ref in Self::find_nodes(sema, "self", file_id, &tree, offset)
1136 .filter_map(ast::NameRef::cast)
1137 {
1138 if self.found_self_module_name_ref(&name_ref, sink) {
1139 return;
1140 }
1141 }
1142 }
1143 }
1144 _ => {}
1145 }
1146 }
1147
1148 fn found_self_ty_name_ref(
1149 &self,
1150 self_ty: &hir::Type<'_>,
1151 name_ref: &ast::NameRef,
1152 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1153 ) -> bool {
1154 if self.is_excluded_name_ref(name_ref) {
1155 return false;
1156 }
1157
1158 let ty_eq = |ty: hir::Type<'_>| match (ty.as_adt(), self_ty.as_adt()) {
1160 (Some(ty), Some(self_ty)) => ty == self_ty,
1161 (None, None) => ty == *self_ty,
1162 _ => false,
1163 };
1164
1165 match NameRefClass::classify(self.sema, name_ref) {
1166 Some(NameRefClass::Definition(Definition::SelfType(impl_), _))
1167 if ty_eq(impl_.self_ty(self.sema.db)) =>
1168 {
1169 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1170 let reference = FileReference {
1171 range,
1172 name: FileReferenceNode::NameRef(name_ref.clone()),
1173 category: ReferenceCategory::empty(),
1174 };
1175 sink(file_id, reference)
1176 }
1177 _ => false,
1178 }
1179 }
1180
1181 fn found_self_module_name_ref(
1182 &self,
1183 name_ref: &ast::NameRef,
1184 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1185 ) -> bool {
1186 if self.is_excluded_name_ref(name_ref) {
1187 return false;
1188 }
1189
1190 match NameRefClass::classify(self.sema, name_ref) {
1191 Some(NameRefClass::Definition(def @ Definition::Module(_), _)) if def == self.def => {
1192 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1193 let category = if is_name_ref_in_import(name_ref) {
1194 ReferenceCategory::IMPORT
1195 } else {
1196 ReferenceCategory::empty()
1197 };
1198 let reference = FileReference {
1199 range,
1200 name: FileReferenceNode::NameRef(name_ref.clone()),
1201 category,
1202 };
1203 sink(file_id, reference)
1204 }
1205 _ => false,
1206 }
1207 }
1208
1209 fn found_format_args_ref(
1210 &self,
1211 file_id: EditionedFileId,
1212 range: TextRange,
1213 token: ast::String,
1214 res: Either<PathResolution, InlineAsmOperand>,
1215 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1216 ) -> bool {
1217 let def = res.either(Definition::from, Definition::from);
1218 if def == self.def {
1219 let reference = FileReference {
1220 range,
1221 name: FileReferenceNode::FormatStringEntry(token, range),
1222 category: ReferenceCategory::READ,
1223 };
1224 sink(file_id, reference)
1225 } else {
1226 false
1227 }
1228 }
1229
1230 fn found_lifetime(
1231 &self,
1232 lifetime: &ast::Lifetime,
1233 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1234 ) -> bool {
1235 match NameRefClass::classify_lifetime(self.sema, lifetime) {
1236 Some(NameRefClass::Definition(def, _)) if def == self.def => {
1237 let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
1238 let reference = FileReference {
1239 range,
1240 name: FileReferenceNode::Lifetime(lifetime.clone()),
1241 category: ReferenceCategory::empty(),
1242 };
1243 sink(file_id, reference)
1244 }
1245 _ => false,
1246 }
1247 }
1248
1249 fn found_name_ref(
1250 &self,
1251 name_ref: &ast::NameRef,
1252 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1253 ) -> bool {
1254 if self.is_excluded_name_ref(name_ref) {
1255 return false;
1256 }
1257
1258 match NameRefClass::classify(self.sema, name_ref) {
1259 Some(NameRefClass::Definition(def, _))
1260 if self.def == def
1261 || matches!(self.assoc_item_container, Some(hir::AssocItemContainer::Trait(_)))
1263 && convert_to_def_in_trait(self.sema.db, def) == self.def =>
1264 {
1265 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1266 let reference = FileReference {
1267 range,
1268 name: FileReferenceNode::NameRef(name_ref.clone()),
1269 category: ReferenceCategory::new(self.sema, &def, name_ref),
1270 };
1271 sink(file_id, reference)
1272 }
1273 Some(NameRefClass::Definition(def, _))
1276 if self.assoc_item_container.is_some()
1277 && matches!(self.def, Definition::TypeAlias(_))
1278 && convert_to_def_in_trait(self.sema.db, def)
1279 == convert_to_def_in_trait(self.sema.db, self.def) =>
1280 {
1281 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1282 let reference = FileReference {
1283 range,
1284 name: FileReferenceNode::NameRef(name_ref.clone()),
1285 category: ReferenceCategory::new(self.sema, &def, name_ref),
1286 };
1287 sink(file_id, reference)
1288 }
1289 Some(NameRefClass::Definition(def, _))
1290 if self.include_self_kw_refs.is_some()
1291 && self.include_self_kw_refs == def_to_ty(self.sema, &def) =>
1292 {
1293 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1294 let reference = FileReference {
1295 range,
1296 name: FileReferenceNode::NameRef(name_ref.clone()),
1297 category: ReferenceCategory::new(self.sema, &def, name_ref),
1298 };
1299 sink(file_id, reference)
1300 }
1301 Some(NameRefClass::FieldShorthand {
1302 local_ref: local,
1303 field_ref: field,
1304 adt_subst: _,
1305 }) => {
1306 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1307
1308 let field = Definition::Field(field);
1309 let local = Definition::Local(local);
1310 let access = match self.def {
1311 Definition::Field(_) if field == self.def => {
1312 ReferenceCategory::new(self.sema, &field, name_ref)
1313 }
1314 Definition::Local(_) if local == self.def => {
1315 ReferenceCategory::new(self.sema, &local, name_ref)
1316 }
1317 _ => return false,
1318 };
1319 let reference = FileReference {
1320 range,
1321 name: FileReferenceNode::NameRef(name_ref.clone()),
1322 category: access,
1323 };
1324 sink(file_id, reference)
1325 }
1326 _ => false,
1327 }
1328 }
1329
1330 fn is_excluded_name_ref(&self, name_ref: &ast::NameRef) -> bool {
1331 (!self.included_categories.contains(ReferenceCategory::TEST)
1332 && is_name_ref_in_test(self.sema, name_ref))
1333 || (!self.included_categories.contains(ReferenceCategory::IMPORT)
1334 && is_name_ref_in_import(name_ref))
1335 }
1336
1337 fn found_name(
1338 &self,
1339 name: &ast::Name,
1340 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1341 ) -> bool {
1342 match NameClass::classify(self.sema, name) {
1343 Some(NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ })
1344 if matches!(
1345 self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def
1346 ) =>
1347 {
1348 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1349 let reference = FileReference {
1350 range,
1351 name: FileReferenceNode::Name(name.clone()),
1352 category: ReferenceCategory::READ,
1354 };
1355 sink(file_id, reference)
1356 }
1357 Some(NameClass::ConstReference(def)) if self.def == def => {
1358 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1359 let reference = FileReference {
1360 range,
1361 name: FileReferenceNode::Name(name.clone()),
1362 category: ReferenceCategory::empty(),
1363 };
1364 sink(file_id, reference)
1365 }
1366 Some(NameClass::Definition(def)) if def != self.def => {
1367 match (&self.assoc_item_container, self.def) {
1368 (Some(_), Definition::TypeAlias(_))
1371 if convert_to_def_in_trait(self.sema.db, def)
1372 != convert_to_def_in_trait(self.sema.db, self.def) =>
1373 {
1374 return false;
1375 }
1376 (Some(_), Definition::TypeAlias(_)) => {}
1377 (Some(hir::AssocItemContainer::Trait(_)), _)
1380 if convert_to_def_in_trait(self.sema.db, def) == self.def => {}
1381 _ => return false,
1382 }
1383 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1384 let reference = FileReference {
1385 range,
1386 name: FileReferenceNode::Name(name.clone()),
1387 category: ReferenceCategory::empty(),
1388 };
1389 sink(file_id, reference)
1390 }
1391 _ => false,
1392 }
1393 }
1394}
1395
1396fn def_to_ty<'db>(sema: &Semantics<'db, RootDatabase>, def: &Definition) -> Option<hir::Type<'db>> {
1397 match def {
1398 Definition::Adt(adt) => Some(adt.ty(sema.db)),
1399 Definition::TypeAlias(it) => Some(it.ty(sema.db)),
1400 Definition::BuiltinType(it) => Some(it.ty(sema.db)),
1401 Definition::SelfType(it) => Some(it.self_ty(sema.db)),
1402 _ => None,
1403 }
1404}
1405
1406impl ReferenceCategory {
1407 fn new(
1408 sema: &Semantics<'_, RootDatabase>,
1409 def: &Definition,
1410 r: &ast::NameRef,
1411 ) -> ReferenceCategory {
1412 let mut result = ReferenceCategory::empty();
1413 if is_name_ref_in_test(sema, r) {
1414 result |= ReferenceCategory::TEST;
1415 }
1416
1417 if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
1419 if is_name_ref_in_import(r) {
1420 result |= ReferenceCategory::IMPORT;
1421 }
1422 return result;
1423 }
1424
1425 let mode = r.syntax().ancestors().find_map(|node| {
1426 match_ast! {
1427 match node {
1428 ast::BinExpr(expr) => {
1429 if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
1430 if let Some(lhs) = expr.lhs()
1433 && lhs.syntax().text_range().contains_range(r.syntax().text_range()) {
1434 return Some(ReferenceCategory::WRITE)
1435 }
1436 }
1437 Some(ReferenceCategory::READ)
1438 },
1439 _ => None,
1440 }
1441 }
1442 }).unwrap_or(ReferenceCategory::READ);
1443
1444 result | mode
1445 }
1446}
1447
1448fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool {
1449 name_ref
1450 .syntax()
1451 .parent()
1452 .and_then(ast::PathSegment::cast)
1453 .and_then(|it| it.parent_path().top_path().syntax().parent())
1454 .is_some_and(|it| it.kind() == SyntaxKind::USE_TREE)
1455}
1456
1457fn is_name_ref_in_test(sema: &Semantics<'_, RootDatabase>, name_ref: &ast::NameRef) -> bool {
1458 sema.ancestors_with_macros(name_ref.syntax().clone()).any(|node| match ast::Fn::cast(node) {
1459 Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)),
1460 None => false,
1461 })
1462}
1463
1464fn is_library_file(db: &RootDatabase, file_id: span::FileId) -> bool {
1465 let source_root = db.file_source_root(file_id).source_root_id(db);
1466 db.source_root(source_root).source_root(db).is_library
1467}