1 //! Implementation of find-usages functionality.
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
7 use std
::{mem, sync::Arc}
;
9 use base_db
::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}
;
11 AsAssocItem
, DefWithBody
, HasAttrs
, HasSource
, InFile
, ModuleSource
, Semantics
, Visibility
,
13 use memchr
::memmem
::Finder
;
14 use once_cell
::unsync
::Lazy
;
15 use parser
::SyntaxKind
;
16 use stdx
::hash
::NoHashHashMap
;
17 use syntax
::{ast, match_ast, AstNode, TextRange, TextSize}
;
20 defs
::{Definition, NameClass, NameRefClass}
,
21 traits
::{as_trait_assoc_def, convert_to_def_in_trait}
,
25 #[derive(Debug, Default, Clone)]
26 pub struct UsageSearchResult
{
27 pub references
: NoHashHashMap
<FileId
, Vec
<FileReference
>>,
30 impl UsageSearchResult
{
31 pub fn is_empty(&self) -> bool
{
32 self.references
.is_empty()
35 pub fn len(&self) -> usize {
39 pub fn iter(&self) -> impl Iterator
<Item
= (&FileId
, &[FileReference
])> + '_
{
40 self.references
.iter().map(|(file_id
, refs
)| (file_id
, &**refs
))
43 pub fn file_ranges(&self) -> impl Iterator
<Item
= FileRange
> + '_
{
44 self.references
.iter().flat_map(|(&file_id
, refs
)| {
45 refs
.iter().map(move |&FileReference { range, .. }
| FileRange { file_id, range }
)
50 impl IntoIterator
for UsageSearchResult
{
51 type Item
= (FileId
, Vec
<FileReference
>);
52 type IntoIter
= <NoHashHashMap
<FileId
, Vec
<FileReference
>> as IntoIterator
>::IntoIter
;
54 fn into_iter(self) -> Self::IntoIter
{
55 self.references
.into_iter()
59 #[derive(Debug, Clone)]
60 pub struct FileReference
{
61 /// The range of the reference in the original file
63 /// The node of the reference in the (macro-)file
64 pub name
: ast
::NameLike
,
65 pub category
: Option
<ReferenceCategory
>,
68 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
69 pub enum ReferenceCategory
{
70 // FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
75 // FIXME: Some day should be able to search in doc comments. Would probably
76 // need to switch from enum to bitflags then?
80 /// Generally, `search_scope` returns files that might contain references for the element.
81 /// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
82 /// In some cases, the location of the references is known to within a `TextRange`,
83 /// e.g. for things like local variables.
84 #[derive(Clone, Debug)]
85 pub struct SearchScope
{
86 entries
: NoHashHashMap
<FileId
, Option
<TextRange
>>,
90 fn new(entries
: NoHashHashMap
<FileId
, Option
<TextRange
>>) -> SearchScope
{
91 SearchScope { entries }
94 /// Build a search scope spanning the entire crate graph of files.
95 fn crate_graph(db
: &RootDatabase
) -> SearchScope
{
96 let mut entries
= NoHashHashMap
::default();
98 let graph
= db
.crate_graph();
99 for krate
in graph
.iter() {
100 let root_file
= graph
[krate
].root_file_id
;
101 let source_root_id
= db
.file_source_root(root_file
);
102 let source_root
= db
.source_root(source_root_id
);
103 entries
.extend(source_root
.iter().map(|id
| (id
, None
)));
105 SearchScope { entries }
108 /// Build a search scope spanning all the reverse dependencies of the given crate.
109 fn reverse_dependencies(db
: &RootDatabase
, of
: hir
::Crate
) -> SearchScope
{
110 let mut entries
= NoHashHashMap
::default();
111 for rev_dep
in of
.transitive_reverse_dependencies(db
) {
112 let root_file
= rev_dep
.root_file(db
);
113 let source_root_id
= db
.file_source_root(root_file
);
114 let source_root
= db
.source_root(source_root_id
);
115 entries
.extend(source_root
.iter().map(|id
| (id
, None
)));
117 SearchScope { entries }
120 /// Build a search scope spanning the given crate.
121 fn krate(db
: &RootDatabase
, of
: hir
::Crate
) -> SearchScope
{
122 let root_file
= of
.root_file(db
);
123 let source_root_id
= db
.file_source_root(root_file
);
124 let source_root
= db
.source_root(source_root_id
);
125 SearchScope { entries: source_root.iter().map(|id| (id, None)).collect() }
128 /// Build a search scope spanning the given module and all its submodules.
129 fn module_and_children(db
: &RootDatabase
, module
: hir
::Module
) -> SearchScope
{
130 let mut entries
= NoHashHashMap
::default();
132 let (file_id
, range
) = {
133 let InFile { file_id, value }
= module
.definition_source(db
);
134 if let Some((file_id
, call_source
)) = file_id
.original_call_node(db
) {
135 (file_id
, Some(call_source
.text_range()))
138 file_id
.original_file(db
),
140 ModuleSource
::SourceFile(_
) => None
,
141 ModuleSource
::Module(it
) => Some(it
.syntax().text_range()),
142 ModuleSource
::BlockExpr(it
) => Some(it
.syntax().text_range()),
147 entries
.insert(file_id
, range
);
149 let mut to_visit
: Vec
<_
> = module
.children(db
).collect();
150 while let Some(module
) = to_visit
.pop() {
151 if let InFile { file_id, value: ModuleSource::SourceFile(_) }
=
152 module
.definition_source(db
)
154 entries
.insert(file_id
.original_file(db
), None
);
156 to_visit
.extend(module
.children(db
));
158 SearchScope { entries }
161 /// Build an empty search scope.
162 pub fn empty() -> SearchScope
{
163 SearchScope
::new(NoHashHashMap
::default())
166 /// Build a empty search scope spanning the given file.
167 pub fn single_file(file
: FileId
) -> SearchScope
{
168 SearchScope
::new(std
::iter
::once((file
, None
)).collect())
171 /// Build a empty search scope spanning the text range of the given file.
172 pub fn file_range(range
: FileRange
) -> SearchScope
{
173 SearchScope
::new(std
::iter
::once((range
.file_id
, Some(range
.range
))).collect())
176 /// Build a empty search scope spanning the given files.
177 pub fn files(files
: &[FileId
]) -> SearchScope
{
178 SearchScope
::new(files
.iter().map(|f
| (*f
, None
)).collect())
181 pub fn intersection(&self, other
: &SearchScope
) -> SearchScope
{
182 let (mut small
, mut large
) = (&self.entries
, &other
.entries
);
183 if small
.len() > large
.len() {
184 mem
::swap(&mut small
, &mut large
)
187 let intersect_ranges
=
188 |r1
: Option
<TextRange
>, r2
: Option
<TextRange
>| -> Option
<Option
<TextRange
>> {
190 (None
, r
) | (r
, None
) => Some(r
),
191 (Some(r1
), Some(r2
)) => r1
.intersect(r2
).map(Some
),
196 .filter_map(|(&file_id
, &r1
)| {
197 let &r2
= large
.get(&file_id
)?
;
198 let r
= intersect_ranges(r1
, r2
)?
;
203 SearchScope
::new(res
)
207 impl IntoIterator
for SearchScope
{
208 type Item
= (FileId
, Option
<TextRange
>);
209 type IntoIter
= std
::collections
::hash_map
::IntoIter
<FileId
, Option
<TextRange
>>;
211 fn into_iter(self) -> Self::IntoIter
{
212 self.entries
.into_iter()
217 fn search_scope(&self, db
: &RootDatabase
) -> SearchScope
{
218 let _p
= profile
::span("search_scope");
220 if let Definition
::BuiltinType(_
) = self {
221 return SearchScope
::crate_graph(db
);
225 // FIXME: We don't do searches for crates currently, as a crate does not actually have a single name
226 if let &Definition
::Module(module
) = self {
227 if module
.is_crate_root(db
) {
228 return SearchScope
::reverse_dependencies(db
, module
.krate());
232 let module
= match self.module(db
) {
234 None
=> return SearchScope
::empty(),
236 let InFile { file_id, value: module_source }
= module
.definition_source(db
);
237 let file_id
= file_id
.original_file(db
);
239 if let Definition
::Local(var
) = self {
240 let def
= match var
.parent(db
) {
241 DefWithBody
::Function(f
) => f
.source(db
).map(|src
| src
.syntax().cloned()),
242 DefWithBody
::Const(c
) => c
.source(db
).map(|src
| src
.syntax().cloned()),
243 DefWithBody
::Static(s
) => s
.source(db
).map(|src
| src
.syntax().cloned()),
244 DefWithBody
::Variant(v
) => v
.source(db
).map(|src
| src
.syntax().cloned()),
247 Some(def
) => SearchScope
::file_range(def
.as_ref().original_file_range_full(db
)),
248 None
=> SearchScope
::single_file(file_id
),
252 if let Definition
::SelfType(impl_
) = self {
253 return match impl_
.source(db
).map(|src
| src
.syntax().cloned()) {
254 Some(def
) => SearchScope
::file_range(def
.as_ref().original_file_range_full(db
)),
255 None
=> SearchScope
::single_file(file_id
),
259 if let Definition
::GenericParam(hir
::GenericParam
::LifetimeParam(param
)) = self {
260 let def
= match param
.parent(db
) {
261 hir
::GenericDef
::Function(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
262 hir
::GenericDef
::Adt(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
263 hir
::GenericDef
::Trait(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
264 hir
::GenericDef
::TraitAlias(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
265 hir
::GenericDef
::TypeAlias(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
266 hir
::GenericDef
::Impl(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
267 hir
::GenericDef
::Variant(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
268 hir
::GenericDef
::Const(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
271 Some(def
) => SearchScope
::file_range(def
.as_ref().original_file_range_full(db
)),
272 None
=> SearchScope
::single_file(file_id
),
276 if let Definition
::Macro(macro_def
) = self {
277 return match macro_def
.kind(db
) {
278 hir
::MacroKind
::Declarative
=> {
279 if macro_def
.attrs(db
).by_key("macro_export").exists() {
280 SearchScope
::reverse_dependencies(db
, module
.krate())
282 SearchScope
::krate(db
, module
.krate())
285 hir
::MacroKind
::BuiltIn
=> SearchScope
::crate_graph(db
),
286 hir
::MacroKind
::Derive
| hir
::MacroKind
::Attr
| hir
::MacroKind
::ProcMacro
=> {
287 SearchScope
::reverse_dependencies(db
, module
.krate())
292 if let Definition
::DeriveHelper(_
) = self {
293 return SearchScope
::reverse_dependencies(db
, module
.krate());
296 let vis
= self.visibility(db
);
297 if let Some(Visibility
::Public
) = vis
{
298 return SearchScope
::reverse_dependencies(db
, module
.krate());
300 if let Some(Visibility
::Module(module
)) = vis
{
301 return SearchScope
::module_and_children(db
, module
.into());
304 let range
= match module_source
{
305 ModuleSource
::Module(m
) => Some(m
.syntax().text_range()),
306 ModuleSource
::BlockExpr(b
) => Some(b
.syntax().text_range()),
307 ModuleSource
::SourceFile(_
) => None
,
310 Some(range
) => SearchScope
::file_range(FileRange { file_id, range }
),
311 None
=> SearchScope
::single_file(file_id
),
315 pub fn usages
<'a
>(self, sema
: &'a Semantics
<'_
, RootDatabase
>) -> FindUsages
<'a
> {
318 assoc_item_container
: self.as_assoc_item(sema
.db
).map(|a
| a
.container(sema
.db
)),
321 include_self_kw_refs
: None
,
322 search_self_mod
: false,
328 pub struct FindUsages
<'a
> {
330 sema
: &'a Semantics
<'a
, RootDatabase
>,
331 scope
: Option
<SearchScope
>,
332 /// The container of our definition should it be an assoc item
333 assoc_item_container
: Option
<hir
::AssocItemContainer
>,
334 /// whether to search for the `Self` type of the definition
335 include_self_kw_refs
: Option
<hir
::Type
>,
336 /// whether to search for the `self` module
337 search_self_mod
: bool
,
340 impl<'a
> FindUsages
<'a
> {
341 /// Enable searching for `Self` when the definition is a type or `self` for modules.
342 pub fn include_self_refs(mut self) -> FindUsages
<'a
> {
343 self.include_self_kw_refs
= def_to_ty(self.sema
, &self.def
);
344 self.search_self_mod
= true;
348 /// Limit the search to a given [`SearchScope`].
349 pub fn in_scope(self, scope
: SearchScope
) -> FindUsages
<'a
> {
350 self.set_scope(Some(scope
))
353 /// Limit the search to a given [`SearchScope`].
354 pub fn set_scope(mut self, scope
: Option
<SearchScope
>) -> FindUsages
<'a
> {
355 assert
!(self.scope
.is_none());
360 pub fn at_least_one(&self) -> bool
{
361 let mut found
= false;
362 self.search(&mut |_
, _
| {
369 pub fn all(self) -> UsageSearchResult
{
370 let mut res
= UsageSearchResult
::default();
371 self.search(&mut |file_id
, reference
| {
372 res
.references
.entry(file_id
).or_default().push(reference
);
378 fn search(&self, sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
) {
379 let _p
= profile
::span("FindUsages:search");
380 let sema
= self.sema
;
383 // FIXME: Is the trait scope needed for trait impl assoc items?
385 as_trait_assoc_def(sema
.db
, self.def
).unwrap_or(self.def
).search_scope(sema
.db
);
388 Some(scope
) => base
.intersection(scope
),
392 let name
= match self.def
{
393 // special case crate modules as these do not have a proper name
394 Definition
::Module(module
) if module
.is_crate_root(self.sema
.db
) => {
395 // FIXME: This assumes the crate name is always equal to its display name when it really isn't
398 .display_name(self.sema
.db
)
399 .map(|crate_name
| crate_name
.crate_name().as_smol_str().clone())
402 let self_kw_refs
= || {
403 self.include_self_kw_refs
.as_ref().and_then(|ty
| {
405 .map(|adt
| adt
.name(self.sema
.db
))
406 .or_else(|| ty
.as_builtin().map(|builtin
| builtin
.name()))
409 // We need to unescape the name in case it is written without "r#" in earlier
410 // editions of Rust where it isn't a keyword.
411 self.def
.name(sema
.db
).or_else(self_kw_refs
).map(|it
| it
.unescaped().to_smol_str())
414 let name
= match &name
{
415 Some(s
) => s
.as_str(),
418 let finder
= &Finder
::new(name
);
419 let include_self_kw_refs
=
420 self.include_self_kw_refs
.as_ref().map(|ty
| (ty
, Finder
::new("Self")));
422 // for<'a> |text: &'a str, name: &'a str, search_range: TextRange| -> impl Iterator<Item = TextSize> + 'a { ... }
423 fn match_indices
<'a
>(
425 finder
: &'a Finder
<'a
>,
426 search_range
: TextRange
,
427 ) -> impl Iterator
<Item
= TextSize
> + 'a
{
428 finder
.find_iter(text
.as_bytes()).filter_map(move |idx
| {
429 let offset
: TextSize
= idx
.try_into().unwrap();
430 if !search_range
.contains_inclusive(offset
) {
437 // for<'a> |scope: &'a SearchScope| -> impl Iterator<Item = (Arc<String>, FileId, TextRange)> + 'a { ... }
439 sema
: &'a Semantics
<'_
, RootDatabase
>,
440 scope
: &'a SearchScope
,
441 ) -> impl Iterator
<Item
= (Arc
<String
>, FileId
, TextRange
)> + 'a
{
442 scope
.entries
.iter().map(|(&file_id
, &search_range
)| {
443 let text
= sema
.db
.file_text(file_id
);
445 search_range
.unwrap_or_else(|| TextRange
::up_to(TextSize
::of(text
.as_str())));
447 (text
, file_id
, search_range
)
451 let find_nodes
= move |name
: &str, node
: &syntax
::SyntaxNode
, offset
: TextSize
| {
452 node
.token_at_offset(offset
)
454 // `name` is stripped of raw ident prefix. See the comment on name retrieval above.
455 it
.text().trim_start_matches("r#") == name
459 // FIXME: There should be optimization potential here
460 // Currently we try to descend everything we find which
461 // means we call `Semantics::descend_into_macros` on
462 // every textual hit. That function is notoriously
463 // expensive even for things that do not get down mapped
465 sema
.descend_into_macros(token
).into_iter().filter_map(|it
| it
.parent())
469 for (text
, file_id
, search_range
) in scope_files(sema
, &search_scope
) {
470 let tree
= Lazy
::new(move || sema
.parse(file_id
).syntax().clone());
472 // Search for occurrences of the items name
473 for offset
in match_indices(&text
, finder
, search_range
) {
474 for name
in find_nodes(name
, &tree
, offset
).filter_map(ast
::NameLike
::cast
) {
476 ast
::NameLike
::NameRef(name_ref
) => self.found_name_ref(&name_ref
, sink
),
477 ast
::NameLike
::Name(name
) => self.found_name(&name
, sink
),
478 ast
::NameLike
::Lifetime(lifetime
) => self.found_lifetime(&lifetime
, sink
),
484 // Search for occurrences of the `Self` referring to our type
485 if let Some((self_ty
, finder
)) = &include_self_kw_refs
{
486 for offset
in match_indices(&text
, finder
, search_range
) {
487 for name_ref
in find_nodes("Self", &tree
, offset
).filter_map(ast
::NameRef
::cast
)
489 if self.found_self_ty_name_ref(self_ty
, &name_ref
, sink
) {
497 // Search for `super` and `crate` resolving to our module
498 if let Definition
::Module(module
) = self.def
{
500 search_scope
.intersection(&SearchScope
::module_and_children(self.sema
.db
, module
));
502 let is_crate_root
= module
.is_crate_root(self.sema
.db
).then(|| Finder
::new("crate"));
503 let finder
= &Finder
::new("super");
505 for (text
, file_id
, search_range
) in scope_files(sema
, &scope
) {
506 let tree
= Lazy
::new(move || sema
.parse(file_id
).syntax().clone());
508 for offset
in match_indices(&text
, finder
, search_range
) {
510 find_nodes("super", &tree
, offset
).filter_map(ast
::NameRef
::cast
)
512 if self.found_name_ref(&name_ref
, sink
) {
517 if let Some(finder
) = &is_crate_root
{
518 for offset
in match_indices(&text
, finder
, search_range
) {
520 find_nodes("crate", &tree
, offset
).filter_map(ast
::NameRef
::cast
)
522 if self.found_name_ref(&name_ref
, sink
) {
531 // search for module `self` references in our module's definition source
533 Definition
::Module(module
) if self.search_self_mod
=> {
534 let src
= module
.definition_source(sema
.db
);
535 let file_id
= src
.file_id
.original_file(sema
.db
);
536 let (file_id
, search_range
) = match src
.value
{
537 ModuleSource
::Module(m
) => (file_id
, Some(m
.syntax().text_range())),
538 ModuleSource
::BlockExpr(b
) => (file_id
, Some(b
.syntax().text_range())),
539 ModuleSource
::SourceFile(_
) => (file_id
, None
),
542 let search_range
= if let Some(&range
) = search_scope
.entries
.get(&file_id
) {
543 match (range
, search_range
) {
544 (None
, range
) | (range
, None
) => range
,
545 (Some(range
), Some(search_range
)) => match range
.intersect(search_range
) {
546 Some(range
) => Some(range
),
554 let text
= sema
.db
.file_text(file_id
);
556 search_range
.unwrap_or_else(|| TextRange
::up_to(TextSize
::of(text
.as_str())));
558 let tree
= Lazy
::new(|| sema
.parse(file_id
).syntax().clone());
559 let finder
= &Finder
::new("self");
561 for offset
in match_indices(&text
, finder
, search_range
) {
562 for name_ref
in find_nodes("self", &tree
, offset
).filter_map(ast
::NameRef
::cast
)
564 if self.found_self_module_name_ref(&name_ref
, sink
) {
574 fn found_self_ty_name_ref(
577 name_ref
: &ast
::NameRef
,
578 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
580 match NameRefClass
::classify(self.sema
, name_ref
) {
581 Some(NameRefClass
::Definition(Definition
::SelfType(impl_
)))
582 if impl_
.self_ty(self.sema
.db
) == *self_ty
=>
584 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
585 let reference
= FileReference
{
587 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
590 sink(file_id
, reference
)
596 fn found_self_module_name_ref(
598 name_ref
: &ast
::NameRef
,
599 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
601 match NameRefClass
::classify(self.sema
, name_ref
) {
602 Some(NameRefClass
::Definition(def @ Definition
::Module(_
))) if def
== self.def
=> {
603 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
604 let reference
= FileReference
{
606 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
607 category
: is_name_ref_in_import(name_ref
).then_some(ReferenceCategory
::Import
),
609 sink(file_id
, reference
)
617 lifetime
: &ast
::Lifetime
,
618 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
620 match NameRefClass
::classify_lifetime(self.sema
, lifetime
) {
621 Some(NameRefClass
::Definition(def
)) if def
== self.def
=> {
622 let FileRange { file_id, range }
= self.sema
.original_range(lifetime
.syntax());
623 let reference
= FileReference
{
625 name
: ast
::NameLike
::Lifetime(lifetime
.clone()),
628 sink(file_id
, reference
)
636 name_ref
: &ast
::NameRef
,
637 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
639 match NameRefClass
::classify(self.sema
, name_ref
) {
640 Some(NameRefClass
::Definition(def
))
642 // is our def a trait assoc item? then we want to find all assoc items from trait impls of our trait
643 || matches
!(self.assoc_item_container
, Some(hir
::AssocItemContainer
::Trait(_
)))
644 && convert_to_def_in_trait(self.sema
.db
, def
) == self.def
=>
646 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
647 let reference
= FileReference
{
649 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
650 category
: ReferenceCategory
::new(&def
, name_ref
),
652 sink(file_id
, reference
)
654 // FIXME: special case type aliases, we can't filter between impl and trait defs here as we lack the substitutions
655 // so we always resolve all assoc type aliases to both their trait def and impl defs
656 Some(NameRefClass
::Definition(def
))
657 if self.assoc_item_container
.is_some()
658 && matches
!(self.def
, Definition
::TypeAlias(_
))
659 && convert_to_def_in_trait(self.sema
.db
, def
)
660 == convert_to_def_in_trait(self.sema
.db
, self.def
) =>
662 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
663 let reference
= FileReference
{
665 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
666 category
: ReferenceCategory
::new(&def
, name_ref
),
668 sink(file_id
, reference
)
670 Some(NameRefClass
::Definition(def
)) if self.include_self_kw_refs
.is_some() => {
671 if self.include_self_kw_refs
== def_to_ty(self.sema
, &def
) {
672 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
673 let reference
= FileReference
{
675 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
676 category
: ReferenceCategory
::new(&def
, name_ref
),
678 sink(file_id
, reference
)
683 Some(NameRefClass
::FieldShorthand { local_ref: local, field_ref: field }
) => {
684 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
686 let field
= Definition
::Field(field
);
687 let local
= Definition
::Local(local
);
688 let access
= match self.def
{
689 Definition
::Field(_
) if field
== self.def
=> {
690 ReferenceCategory
::new(&field
, name_ref
)
692 Definition
::Local(_
) if local
== self.def
=> {
693 ReferenceCategory
::new(&local
, name_ref
)
697 let reference
= FileReference
{
699 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
702 sink(file_id
, reference
)
711 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
713 match NameClass
::classify(self.sema
, name
) {
714 Some(NameClass
::PatFieldShorthand { local_def: _, field_ref }
)
716 self.def
, Definition
::Field(_
) if Definition
::Field(field_ref
) == self.def
719 let FileRange { file_id, range }
= self.sema
.original_range(name
.syntax());
720 let reference
= FileReference
{
722 name
: ast
::NameLike
::Name(name
.clone()),
723 // FIXME: mutable patterns should have `Write` access
724 category
: Some(ReferenceCategory
::Read
),
726 sink(file_id
, reference
)
728 Some(NameClass
::ConstReference(def
)) if self.def
== def
=> {
729 let FileRange { file_id, range }
= self.sema
.original_range(name
.syntax());
730 let reference
= FileReference
{
732 name
: ast
::NameLike
::Name(name
.clone()),
735 sink(file_id
, reference
)
737 Some(NameClass
::Definition(def
)) if def
!= self.def
=> {
738 match (&self.assoc_item_container
, self.def
) {
739 // for type aliases we always want to reference the trait def and all the trait impl counterparts
740 // FIXME: only until we can resolve them correctly, see FIXME above
741 (Some(_
), Definition
::TypeAlias(_
))
742 if convert_to_def_in_trait(self.sema
.db
, def
)
743 != convert_to_def_in_trait(self.sema
.db
, self.def
) =>
747 (Some(_
), Definition
::TypeAlias(_
)) => {}
748 // We looking at an assoc item of a trait definition, so reference all the
749 // corresponding assoc items belonging to this trait's trait implementations
750 (Some(hir
::AssocItemContainer
::Trait(_
)), _
)
751 if convert_to_def_in_trait(self.sema
.db
, def
) == self.def
=> {}
754 let FileRange { file_id, range }
= self.sema
.original_range(name
.syntax());
755 let reference
= FileReference
{
757 name
: ast
::NameLike
::Name(name
.clone()),
760 sink(file_id
, reference
)
767 fn def_to_ty(sema
: &Semantics
<'_
, RootDatabase
>, def
: &Definition
) -> Option
<hir
::Type
> {
769 Definition
::Adt(adt
) => Some(adt
.ty(sema
.db
)),
770 Definition
::TypeAlias(it
) => Some(it
.ty(sema
.db
)),
771 Definition
::BuiltinType(it
) => Some(it
.ty(sema
.db
)),
772 Definition
::SelfType(it
) => Some(it
.self_ty(sema
.db
)),
777 impl ReferenceCategory
{
778 fn new(def
: &Definition
, r
: &ast
::NameRef
) -> Option
<ReferenceCategory
> {
779 // Only Locals and Fields have accesses for now.
780 if !matches
!(def
, Definition
::Local(_
) | Definition
::Field(_
)) {
781 return is_name_ref_in_import(r
).then_some(ReferenceCategory
::Import
);
784 let mode
= r
.syntax().ancestors().find_map(|node
| {
787 ast
::BinExpr(expr
) => {
788 if matches
!(expr
.op_kind()?
, ast
::BinaryOp
::Assignment { .. }
) {
789 // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
790 // FIXME: This is not terribly accurate.
791 if let Some(lhs
) = expr
.lhs() {
792 if lhs
.syntax().text_range().end() == r
.syntax().text_range().end() {
793 return Some(ReferenceCategory
::Write
);
797 Some(ReferenceCategory
::Read
)
804 // Default Locals and Fields to read
805 mode
.or(Some(ReferenceCategory
::Read
))
809 fn is_name_ref_in_import(name_ref
: &ast
::NameRef
) -> bool
{
813 .and_then(ast
::PathSegment
::cast
)
814 .and_then(|it
| it
.parent_path().top_path().syntax().parent())
815 .map_or(false, |it
| it
.kind() == SyntaxKind
::USE_TREE
)