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}
;
10 use hir
::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility}
;
11 use memchr
::memmem
::Finder
;
12 use once_cell
::unsync
::Lazy
;
13 use parser
::SyntaxKind
;
14 use stdx
::hash
::NoHashHashMap
;
15 use syntax
::{ast, match_ast, AstNode, TextRange, TextSize}
;
18 defs
::{Definition, NameClass, NameRefClass}
,
19 traits
::{as_trait_assoc_def, convert_to_def_in_trait}
,
23 #[derive(Debug, Default, Clone)]
24 pub struct UsageSearchResult
{
25 pub references
: NoHashHashMap
<FileId
, Vec
<FileReference
>>,
28 impl UsageSearchResult
{
29 pub fn is_empty(&self) -> bool
{
30 self.references
.is_empty()
33 pub fn len(&self) -> usize {
37 pub fn iter(&self) -> impl Iterator
<Item
= (&FileId
, &[FileReference
])> + '_
{
38 self.references
.iter().map(|(file_id
, refs
)| (file_id
, &**refs
))
41 pub fn file_ranges(&self) -> impl Iterator
<Item
= FileRange
> + '_
{
42 self.references
.iter().flat_map(|(&file_id
, refs
)| {
43 refs
.iter().map(move |&FileReference { range, .. }
| FileRange { file_id, range }
)
48 impl IntoIterator
for UsageSearchResult
{
49 type Item
= (FileId
, Vec
<FileReference
>);
50 type IntoIter
= <NoHashHashMap
<FileId
, Vec
<FileReference
>> as IntoIterator
>::IntoIter
;
52 fn into_iter(self) -> Self::IntoIter
{
53 self.references
.into_iter()
57 #[derive(Debug, Clone)]
58 pub struct FileReference
{
59 /// The range of the reference in the original file
61 /// The node of the reference in the (macro-)file
62 pub name
: ast
::NameLike
,
63 pub category
: Option
<ReferenceCategory
>,
66 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
67 pub enum ReferenceCategory
{
68 // FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
73 // FIXME: Some day should be able to search in doc comments. Would probably
74 // need to switch from enum to bitflags then?
78 /// Generally, `search_scope` returns files that might contain references for the element.
79 /// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
80 /// In some cases, the location of the references is known to within a `TextRange`,
81 /// e.g. for things like local variables.
82 #[derive(Clone, Debug)]
83 pub struct SearchScope
{
84 entries
: NoHashHashMap
<FileId
, Option
<TextRange
>>,
88 fn new(entries
: NoHashHashMap
<FileId
, Option
<TextRange
>>) -> SearchScope
{
89 SearchScope { entries }
92 /// Build a search scope spanning the entire crate graph of files.
93 fn crate_graph(db
: &RootDatabase
) -> SearchScope
{
94 let mut entries
= NoHashHashMap
::default();
96 let graph
= db
.crate_graph();
97 for krate
in graph
.iter() {
98 let root_file
= graph
[krate
].root_file_id
;
99 let source_root_id
= db
.file_source_root(root_file
);
100 let source_root
= db
.source_root(source_root_id
);
101 entries
.extend(source_root
.iter().map(|id
| (id
, None
)));
103 SearchScope { entries }
106 /// Build a search scope spanning all the reverse dependencies of the given crate.
107 fn reverse_dependencies(db
: &RootDatabase
, of
: hir
::Crate
) -> SearchScope
{
108 let mut entries
= NoHashHashMap
::default();
109 for rev_dep
in of
.transitive_reverse_dependencies(db
) {
110 let root_file
= rev_dep
.root_file(db
);
111 let source_root_id
= db
.file_source_root(root_file
);
112 let source_root
= db
.source_root(source_root_id
);
113 entries
.extend(source_root
.iter().map(|id
| (id
, None
)));
115 SearchScope { entries }
118 /// Build a search scope spanning the given crate.
119 fn krate(db
: &RootDatabase
, of
: hir
::Crate
) -> SearchScope
{
120 let root_file
= of
.root_file(db
);
121 let source_root_id
= db
.file_source_root(root_file
);
122 let source_root
= db
.source_root(source_root_id
);
123 SearchScope { entries: source_root.iter().map(|id| (id, None)).collect() }
126 /// Build a search scope spanning the given module and all its submodules.
127 fn module_and_children(db
: &RootDatabase
, module
: hir
::Module
) -> SearchScope
{
128 let mut entries
= NoHashHashMap
::default();
130 let (file_id
, range
) = {
131 let InFile { file_id, value }
= module
.definition_source(db
);
132 if let Some((file_id
, call_source
)) = file_id
.original_call_node(db
) {
133 (file_id
, Some(call_source
.text_range()))
136 file_id
.original_file(db
),
138 ModuleSource
::SourceFile(_
) => None
,
139 ModuleSource
::Module(it
) => Some(it
.syntax().text_range()),
140 ModuleSource
::BlockExpr(it
) => Some(it
.syntax().text_range()),
145 entries
.insert(file_id
, range
);
147 let mut to_visit
: Vec
<_
> = module
.children(db
).collect();
148 while let Some(module
) = to_visit
.pop() {
149 if let InFile { file_id, value: ModuleSource::SourceFile(_) }
=
150 module
.definition_source(db
)
152 entries
.insert(file_id
.original_file(db
), None
);
154 to_visit
.extend(module
.children(db
));
156 SearchScope { entries }
159 /// Build an empty search scope.
160 pub fn empty() -> SearchScope
{
161 SearchScope
::new(NoHashHashMap
::default())
164 /// Build a empty search scope spanning the given file.
165 pub fn single_file(file
: FileId
) -> SearchScope
{
166 SearchScope
::new(std
::iter
::once((file
, None
)).collect())
169 /// Build a empty search scope spanning the text range of the given file.
170 pub fn file_range(range
: FileRange
) -> SearchScope
{
171 SearchScope
::new(std
::iter
::once((range
.file_id
, Some(range
.range
))).collect())
174 /// Build a empty search scope spanning the given files.
175 pub fn files(files
: &[FileId
]) -> SearchScope
{
176 SearchScope
::new(files
.iter().map(|f
| (*f
, None
)).collect())
179 pub fn intersection(&self, other
: &SearchScope
) -> SearchScope
{
180 let (mut small
, mut large
) = (&self.entries
, &other
.entries
);
181 if small
.len() > large
.len() {
182 mem
::swap(&mut small
, &mut large
)
185 let intersect_ranges
=
186 |r1
: Option
<TextRange
>, r2
: Option
<TextRange
>| -> Option
<Option
<TextRange
>> {
188 (None
, r
) | (r
, None
) => Some(r
),
189 (Some(r1
), Some(r2
)) => r1
.intersect(r2
).map(Some
),
194 .filter_map(|(&file_id
, &r1
)| {
195 let &r2
= large
.get(&file_id
)?
;
196 let r
= intersect_ranges(r1
, r2
)?
;
201 SearchScope
::new(res
)
205 impl IntoIterator
for SearchScope
{
206 type Item
= (FileId
, Option
<TextRange
>);
207 type IntoIter
= std
::collections
::hash_map
::IntoIter
<FileId
, Option
<TextRange
>>;
209 fn into_iter(self) -> Self::IntoIter
{
210 self.entries
.into_iter()
215 fn search_scope(&self, db
: &RootDatabase
) -> SearchScope
{
216 let _p
= profile
::span("search_scope");
218 if let Definition
::BuiltinType(_
) = self {
219 return SearchScope
::crate_graph(db
);
223 // FIXME: We don't do searches for crates currently, as a crate does not actually have a single name
224 if let &Definition
::Module(module
) = self {
225 if module
.is_crate_root(db
) {
226 return SearchScope
::reverse_dependencies(db
, module
.krate());
230 let module
= match self.module(db
) {
232 None
=> return SearchScope
::empty(),
234 let InFile { file_id, value: module_source }
= module
.definition_source(db
);
235 let file_id
= file_id
.original_file(db
);
237 if let Definition
::Local(var
) = self {
238 let def
= match var
.parent(db
) {
239 DefWithBody
::Function(f
) => f
.source(db
).map(|src
| src
.syntax().cloned()),
240 DefWithBody
::Const(c
) => c
.source(db
).map(|src
| src
.syntax().cloned()),
241 DefWithBody
::Static(s
) => s
.source(db
).map(|src
| src
.syntax().cloned()),
242 DefWithBody
::Variant(v
) => v
.source(db
).map(|src
| src
.syntax().cloned()),
245 Some(def
) => SearchScope
::file_range(def
.as_ref().original_file_range(db
)),
246 None
=> SearchScope
::single_file(file_id
),
250 if let Definition
::SelfType(impl_
) = self {
251 return match impl_
.source(db
).map(|src
| src
.syntax().cloned()) {
252 Some(def
) => SearchScope
::file_range(def
.as_ref().original_file_range(db
)),
253 None
=> SearchScope
::single_file(file_id
),
257 if let Definition
::GenericParam(hir
::GenericParam
::LifetimeParam(param
)) = self {
258 let def
= match param
.parent(db
) {
259 hir
::GenericDef
::Function(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
260 hir
::GenericDef
::Adt(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
261 hir
::GenericDef
::Trait(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
262 hir
::GenericDef
::TypeAlias(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
263 hir
::GenericDef
::Impl(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
264 hir
::GenericDef
::Variant(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
265 hir
::GenericDef
::Const(it
) => it
.source(db
).map(|src
| src
.syntax().cloned()),
268 Some(def
) => SearchScope
::file_range(def
.as_ref().original_file_range(db
)),
269 None
=> SearchScope
::single_file(file_id
),
273 if let Definition
::Macro(macro_def
) = self {
274 return match macro_def
.kind(db
) {
275 hir
::MacroKind
::Declarative
=> {
276 if macro_def
.attrs(db
).by_key("macro_export").exists() {
277 SearchScope
::reverse_dependencies(db
, module
.krate())
279 SearchScope
::krate(db
, module
.krate())
282 hir
::MacroKind
::BuiltIn
=> SearchScope
::crate_graph(db
),
283 hir
::MacroKind
::Derive
| hir
::MacroKind
::Attr
| hir
::MacroKind
::ProcMacro
=> {
284 SearchScope
::reverse_dependencies(db
, module
.krate())
289 if let Definition
::DeriveHelper(_
) = self {
290 return SearchScope
::reverse_dependencies(db
, module
.krate());
293 let vis
= self.visibility(db
);
294 if let Some(Visibility
::Public
) = vis
{
295 return SearchScope
::reverse_dependencies(db
, module
.krate());
297 if let Some(Visibility
::Module(module
)) = vis
{
298 return SearchScope
::module_and_children(db
, module
.into());
301 let range
= match module_source
{
302 ModuleSource
::Module(m
) => Some(m
.syntax().text_range()),
303 ModuleSource
::BlockExpr(b
) => Some(b
.syntax().text_range()),
304 ModuleSource
::SourceFile(_
) => None
,
307 Some(range
) => SearchScope
::file_range(FileRange { file_id, range }
),
308 None
=> SearchScope
::single_file(file_id
),
312 pub fn usages
<'a
>(self, sema
: &'a Semantics
<'_
, RootDatabase
>) -> FindUsages
<'a
> {
314 local_repr
: match self {
315 Definition
::Local(local
) => Some(local
.representative(sema
.db
)),
319 trait_assoc_def
: as_trait_assoc_def(sema
.db
, self),
322 include_self_kw_refs
: None
,
323 search_self_mod
: false,
329 pub struct FindUsages
<'a
> {
331 /// If def is an assoc item from a trait or trait impl, this is the corresponding item of the trait definition
332 trait_assoc_def
: Option
<Definition
>,
333 sema
: &'a Semantics
<'a
, RootDatabase
>,
334 scope
: Option
<SearchScope
>,
335 include_self_kw_refs
: Option
<hir
::Type
>,
336 local_repr
: Option
<hir
::Local
>,
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 let base
= self.trait_assoc_def
.unwrap_or(self.def
).search_scope(sema
.db
);
386 Some(scope
) => base
.intersection(scope
),
390 let name
= match self.def
{
391 // special case crate modules as these do not have a proper name
392 Definition
::Module(module
) if module
.is_crate_root(self.sema
.db
) => {
393 // FIXME: This assumes the crate name is always equal to its display name when it really isn't
396 .display_name(self.sema
.db
)
397 .map(|crate_name
| crate_name
.crate_name().as_smol_str().clone())
400 let self_kw_refs
= || {
401 self.include_self_kw_refs
.as_ref().and_then(|ty
| {
403 .map(|adt
| adt
.name(self.sema
.db
))
404 .or_else(|| ty
.as_builtin().map(|builtin
| builtin
.name()))
407 // We need to unescape the name in case it is written without "r#" in earlier
408 // editions of Rust where it isn't a keyword.
409 self.def
.name(sema
.db
).or_else(self_kw_refs
).map(|it
| it
.unescaped().to_smol_str())
412 let name
= match &name
{
413 Some(s
) => s
.as_str(),
416 let finder
= &Finder
::new(name
);
417 let include_self_kw_refs
=
418 self.include_self_kw_refs
.as_ref().map(|ty
| (ty
, Finder
::new("Self")));
420 // for<'a> |text: &'a str, name: &'a str, search_range: TextRange| -> impl Iterator<Item = TextSize> + 'a { ... }
421 fn match_indices
<'a
>(
423 finder
: &'a Finder
<'a
>,
424 search_range
: TextRange
,
425 ) -> impl Iterator
<Item
= TextSize
> + 'a
{
426 finder
.find_iter(text
.as_bytes()).filter_map(move |idx
| {
427 let offset
: TextSize
= idx
.try_into().unwrap();
428 if !search_range
.contains_inclusive(offset
) {
435 // for<'a> |scope: &'a SearchScope| -> impl Iterator<Item = (Arc<String>, FileId, TextRange)> + 'a { ... }
437 sema
: &'a Semantics
<'_
, RootDatabase
>,
438 scope
: &'a SearchScope
,
439 ) -> impl Iterator
<Item
= (Arc
<String
>, FileId
, TextRange
)> + 'a
{
440 scope
.entries
.iter().map(|(&file_id
, &search_range
)| {
441 let text
= sema
.db
.file_text(file_id
);
443 search_range
.unwrap_or_else(|| TextRange
::up_to(TextSize
::of(text
.as_str())));
445 (text
, file_id
, search_range
)
449 let find_nodes
= move |name
: &str, node
: &syntax
::SyntaxNode
, offset
: TextSize
| {
450 node
.token_at_offset(offset
).find(|it
| it
.text() == name
).map(|token
| {
451 // FIXME: There should be optimization potential here
452 // Currently we try to descend everything we find which
453 // means we call `Semantics::descend_into_macros` on
454 // every textual hit. That function is notoriously
455 // expensive even for things that do not get down mapped
457 sema
.descend_into_macros(token
).into_iter().filter_map(|it
| it
.parent())
461 for (text
, file_id
, search_range
) in scope_files(sema
, &search_scope
) {
462 let tree
= Lazy
::new(move || sema
.parse(file_id
).syntax().clone());
464 // Search for occurrences of the items name
465 for offset
in match_indices(&text
, finder
, search_range
) {
466 if let Some(iter
) = find_nodes(name
, &tree
, offset
) {
467 for name
in iter
.filter_map(ast
::NameLike
::cast
) {
469 ast
::NameLike
::NameRef(name_ref
) => {
470 self.found_name_ref(&name_ref
, sink
)
472 ast
::NameLike
::Name(name
) => self.found_name(&name
, sink
),
473 ast
::NameLike
::Lifetime(lifetime
) => {
474 self.found_lifetime(&lifetime
, sink
)
482 // Search for occurrences of the `Self` referring to our type
483 if let Some((self_ty
, finder
)) = &include_self_kw_refs
{
484 for offset
in match_indices(&text
, finder
, search_range
) {
485 if let Some(iter
) = find_nodes("Self", &tree
, offset
) {
486 for name_ref
in iter
.filter_map(ast
::NameRef
::cast
) {
487 if self.found_self_ty_name_ref(self_ty
, &name_ref
, sink
) {
496 // Search for `super` and `crate` resolving to our module
498 Definition
::Module(module
) => {
499 let scope
= search_scope
500 .intersection(&SearchScope
::module_and_children(self.sema
.db
, module
));
503 module
.is_crate_root(self.sema
.db
).then(|| Finder
::new("crate"));
504 let finder
= &Finder
::new("super");
506 for (text
, file_id
, search_range
) in scope_files(sema
, &scope
) {
507 let tree
= Lazy
::new(move || sema
.parse(file_id
).syntax().clone());
509 for offset
in match_indices(&text
, finder
, search_range
) {
510 if let Some(iter
) = find_nodes("super", &tree
, offset
) {
511 for name_ref
in iter
.filter_map(ast
::NameRef
::cast
) {
512 if self.found_name_ref(&name_ref
, sink
) {
518 if let Some(finder
) = &is_crate_root
{
519 for offset
in match_indices(&text
, finder
, search_range
) {
520 if let Some(iter
) = find_nodes("crate", &tree
, offset
) {
521 for name_ref
in iter
.filter_map(ast
::NameRef
::cast
) {
522 if self.found_name_ref(&name_ref
, sink
) {
534 // search for module `self` references in our module's definition source
536 Definition
::Module(module
) if self.search_self_mod
=> {
537 let src
= module
.definition_source(sema
.db
);
538 let file_id
= src
.file_id
.original_file(sema
.db
);
539 let (file_id
, search_range
) = match src
.value
{
540 ModuleSource
::Module(m
) => (file_id
, Some(m
.syntax().text_range())),
541 ModuleSource
::BlockExpr(b
) => (file_id
, Some(b
.syntax().text_range())),
542 ModuleSource
::SourceFile(_
) => (file_id
, None
),
545 let search_range
= if let Some(&range
) = search_scope
.entries
.get(&file_id
) {
546 match (range
, search_range
) {
547 (None
, range
) | (range
, None
) => range
,
548 (Some(range
), Some(search_range
)) => match range
.intersect(search_range
) {
549 Some(range
) => Some(range
),
557 let text
= sema
.db
.file_text(file_id
);
559 search_range
.unwrap_or_else(|| TextRange
::up_to(TextSize
::of(text
.as_str())));
561 let tree
= Lazy
::new(|| sema
.parse(file_id
).syntax().clone());
562 let finder
= &Finder
::new("self");
564 for offset
in match_indices(&text
, finder
, search_range
) {
565 if let Some(iter
) = find_nodes("self", &tree
, offset
) {
566 for name_ref
in iter
.filter_map(ast
::NameRef
::cast
) {
567 if self.found_self_module_name_ref(&name_ref
, sink
) {
578 fn found_self_ty_name_ref(
581 name_ref
: &ast
::NameRef
,
582 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
584 match NameRefClass
::classify(self.sema
, name_ref
) {
585 Some(NameRefClass
::Definition(Definition
::SelfType(impl_
)))
586 if impl_
.self_ty(self.sema
.db
) == *self_ty
=>
588 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
589 let reference
= FileReference
{
591 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
594 sink(file_id
, reference
)
600 fn found_self_module_name_ref(
602 name_ref
: &ast
::NameRef
,
603 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
605 match NameRefClass
::classify(self.sema
, name_ref
) {
606 Some(NameRefClass
::Definition(def @ Definition
::Module(_
))) if def
== self.def
=> {
607 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
608 let reference
= FileReference
{
610 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
611 category
: is_name_ref_in_import(name_ref
).then(|| ReferenceCategory
::Import
),
613 sink(file_id
, reference
)
621 lifetime
: &ast
::Lifetime
,
622 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
624 match NameRefClass
::classify_lifetime(self.sema
, lifetime
) {
625 Some(NameRefClass
::Definition(def
)) if def
== self.def
=> {
626 let FileRange { file_id, range }
= self.sema
.original_range(lifetime
.syntax());
627 let reference
= FileReference
{
629 name
: ast
::NameLike
::Lifetime(lifetime
.clone()),
632 sink(file_id
, reference
)
640 name_ref
: &ast
::NameRef
,
641 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
643 match NameRefClass
::classify(self.sema
, name_ref
) {
644 Some(NameRefClass
::Definition(def @ Definition
::Local(local
)))
646 self.local_repr
, Some(repr
) if repr
== local
.representative(self.sema
.db
)
649 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
650 let reference
= FileReference
{
652 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
653 category
: ReferenceCategory
::new(&def
, name_ref
),
655 sink(file_id
, reference
)
657 Some(NameRefClass
::Definition(def
))
658 if match self.trait_assoc_def
{
659 Some(trait_assoc_def
) => {
660 // we have a trait assoc item, so force resolve all assoc items to their trait version
661 convert_to_def_in_trait(self.sema
.db
, def
) == trait_assoc_def
663 None
=> self.def
== def
,
666 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
667 let reference
= FileReference
{
669 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
670 category
: ReferenceCategory
::new(&def
, name_ref
),
672 sink(file_id
, reference
)
674 Some(NameRefClass
::Definition(def
)) if self.include_self_kw_refs
.is_some() => {
675 if self.include_self_kw_refs
== def_to_ty(self.sema
, &def
) {
676 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
677 let reference
= FileReference
{
679 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
680 category
: ReferenceCategory
::new(&def
, name_ref
),
682 sink(file_id
, reference
)
687 Some(NameRefClass
::FieldShorthand { local_ref: local, field_ref: field }
) => {
688 let field
= Definition
::Field(field
);
689 let FileRange { file_id, range }
= self.sema
.original_range(name_ref
.syntax());
690 let access
= match self.def
{
691 Definition
::Field(_
) if field
== self.def
=> {
692 ReferenceCategory
::new(&field
, name_ref
)
694 Definition
::Local(_
) if matches
!(self.local_repr
, Some(repr
) if repr
== local
.representative(self.sema
.db
)) => {
695 ReferenceCategory
::new(&Definition
::Local(local
), name_ref
)
699 let reference
= FileReference
{
701 name
: ast
::NameLike
::NameRef(name_ref
.clone()),
704 sink(file_id
, reference
)
713 sink
: &mut dyn FnMut(FileId
, FileReference
) -> bool
,
715 match NameClass
::classify(self.sema
, name
) {
716 Some(NameClass
::PatFieldShorthand { local_def: _, field_ref }
)
718 self.def
, Definition
::Field(_
) if Definition
::Field(field_ref
) == self.def
721 let FileRange { file_id, range }
= self.sema
.original_range(name
.syntax());
722 let reference
= FileReference
{
724 name
: ast
::NameLike
::Name(name
.clone()),
725 // FIXME: mutable patterns should have `Write` access
726 category
: Some(ReferenceCategory
::Read
),
728 sink(file_id
, reference
)
730 Some(NameClass
::ConstReference(def
)) if self.def
== def
=> {
731 let FileRange { file_id, range }
= self.sema
.original_range(name
.syntax());
732 let reference
= FileReference
{
734 name
: ast
::NameLike
::Name(name
.clone()),
737 sink(file_id
, reference
)
739 Some(NameClass
::Definition(def @ Definition
::Local(local
))) if def
!= self.def
=> {
742 Some(repr
) if local
.representative(self.sema
.db
) == repr
744 let FileRange { file_id, range }
= self.sema
.original_range(name
.syntax());
745 let reference
= FileReference
{
747 name
: ast
::NameLike
::Name(name
.clone()),
750 return sink(file_id
, reference
);
754 Some(NameClass
::Definition(def
)) if def
!= self.def
=> {
755 // if the def we are looking for is a trait (impl) assoc item, we'll have to resolve the items to trait definition assoc item
757 self.trait_assoc_def
,
758 Some(trait_assoc_def
)
759 if convert_to_def_in_trait(self.sema
.db
, def
) == trait_assoc_def
763 let FileRange { file_id, range }
= self.sema
.original_range(name
.syntax());
764 let reference
= FileReference
{
766 name
: ast
::NameLike
::Name(name
.clone()),
769 sink(file_id
, reference
)
776 fn def_to_ty(sema
: &Semantics
<'_
, RootDatabase
>, def
: &Definition
) -> Option
<hir
::Type
> {
778 Definition
::Adt(adt
) => Some(adt
.ty(sema
.db
)),
779 Definition
::TypeAlias(it
) => Some(it
.ty(sema
.db
)),
780 Definition
::BuiltinType(it
) => Some(it
.ty(sema
.db
)),
781 Definition
::SelfType(it
) => Some(it
.self_ty(sema
.db
)),
786 impl ReferenceCategory
{
787 fn new(def
: &Definition
, r
: &ast
::NameRef
) -> Option
<ReferenceCategory
> {
788 // Only Locals and Fields have accesses for now.
789 if !matches
!(def
, Definition
::Local(_
) | Definition
::Field(_
)) {
790 return is_name_ref_in_import(r
).then(|| ReferenceCategory
::Import
);
793 let mode
= r
.syntax().ancestors().find_map(|node
| {
796 ast
::BinExpr(expr
) => {
797 if matches
!(expr
.op_kind()?
, ast
::BinaryOp
::Assignment { .. }
) {
798 // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
799 // FIXME: This is not terribly accurate.
800 if let Some(lhs
) = expr
.lhs() {
801 if lhs
.syntax().text_range().end() == r
.syntax().text_range().end() {
802 return Some(ReferenceCategory
::Write
);
806 Some(ReferenceCategory
::Read
)
813 // Default Locals and Fields to read
814 mode
.or(Some(ReferenceCategory
::Read
))
818 fn is_name_ref_in_import(name_ref
: &ast
::NameRef
) -> bool
{
822 .and_then(ast
::PathSegment
::cast
)
823 .and_then(|it
| it
.parent_path().top_path().syntax().parent())
824 .map_or(false, |it
| it
.kind() == SyntaxKind
::USE_TREE
)