about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Kladov <aleksey.kladov@gmail.com>2024-04-16 16:10:36 +0100
committerAlex Kladov <aleksey.kladov@gmail.com>2024-04-16 17:17:46 +0100
commit9bd8eee21e2d2425bfd2946849b45b91f1313174 (patch)
tree573b46dcc1240f5294b259f69ce786e5351390b7
parent1179c3ee83eb72508049c78d06c06057c21885a3 (diff)
downloadrust-9bd8eee21e2d2425bfd2946849b45b91f1313174.tar.gz
rust-9bd8eee21e2d2425bfd2946849b45b91f1313174.zip
ide: improve ReferenceCategoryType
It is bitset semantically --- many categorical things can be true about
a reference at the same time.

In parciular, a reference can be a "test" and a "write" at the same
time.
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ide-assists/src/handlers/extract_function.rs8
-rw-r--r--crates/ide-assists/src/handlers/remove_unused_imports.rs2
-rw-r--r--crates/ide-db/Cargo.toml1
-rw-r--r--crates/ide-db/src/search.rs85
-rw-r--r--crates/ide/src/highlight_related.rs83
-rw-r--r--crates/ide/src/references.rs102
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs6
-rw-r--r--crates/rust-analyzer/src/lsp/to_proto.rs11
9 files changed, 167 insertions, 132 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 20bd36b894a..a6e460134f2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -695,6 +695,7 @@ version = "0.0.0"
 dependencies = [
  "arrayvec",
  "base-db",
+ "bitflags 2.4.2",
  "cov-mark",
  "crossbeam-channel",
  "either",
diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs
index d111005c2ec..34326294d2e 100644
--- a/crates/ide-assists/src/handlers/extract_function.rs
+++ b/crates/ide-assists/src/handlers/extract_function.rs
@@ -1149,8 +1149,14 @@ fn reference_is_exclusive(
     node: &dyn HasTokenAtOffset,
     ctx: &AssistContext<'_>,
 ) -> bool {
+    // FIXME: this quite an incorrect way to go about doing this :-)
+    // `FileReference` is an IDE-type --- it encapsulates data communicated to the human,
+    // but doesn't necessary fully reflect all the intricacies of the underlying language semantics
+    // The correct approach here would be to expose this entire analysis as a method on some hir
+    // type. Something like `body.free_variables(statement_range)`.
+
     // we directly modify variable with set: `n = 0`, `n += 1`
-    if reference.category == Some(ReferenceCategory::Write) {
+    if reference.category.contains(ReferenceCategory::WRITE) {
         return true;
     }
 
diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs
index d67b259d2f5..0f0f13bbc80 100644
--- a/crates/ide-assists/src/handlers/remove_unused_imports.rs
+++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs
@@ -145,7 +145,7 @@ fn used_once_in_scope(ctx: &AssistContext<'_>, def: Definition, scopes: &Vec<Sea
     for scope in scopes {
         let mut search_non_import = |_, r: FileReference| {
             // The import itself is a use; we must skip that.
-            if r.category != Some(ReferenceCategory::Import) {
+            if !r.category.contains(ReferenceCategory::IMPORT) {
                 found = true;
                 true
             } else {
diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml
index 071e1b47179..9a6826a5c42 100644
--- a/crates/ide-db/Cargo.toml
+++ b/crates/ide-db/Cargo.toml
@@ -26,6 +26,7 @@ indexmap.workspace = true
 memchr = "2.6.4"
 triomphe.workspace = true
 nohash-hasher.workspace = true
+bitflags.workspace = true
 
 # local deps
 base-db.workspace = true
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index a3ecc103605..cb103313c9e 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -64,7 +64,7 @@ pub struct FileReference {
     pub range: TextRange,
     /// The node of the reference in the (macro-)file
     pub name: FileReferenceNode,
-    pub category: Option<ReferenceCategory>,
+    pub category: ReferenceCategory,
 }
 
 #[derive(Debug, Clone)]
@@ -124,17 +124,16 @@ impl FileReferenceNode {
     }
 }
 
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum ReferenceCategory {
-    // FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
-    // Create
-    Write,
-    Read,
-    Import,
-    // FIXME: Some day should be able to search in doc comments. Would probably
-    // need to switch from enum to bitflags then?
-    // DocComment
-    Test,
+bitflags::bitflags! {
+    #[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
+    pub struct ReferenceCategory: u8 {
+        // FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
+        // const CREATE = 1 << 0;
+        const WRITE = 1 << 0;
+        const READ = 1 << 1;
+        const IMPORT = 1 << 2;
+        const TEST = 1 << 3;
+    }
 }
 
 /// Generally, `search_scope` returns files that might contain references for the element.
@@ -660,7 +659,7 @@ impl<'a> FindUsages<'a> {
                 let reference = FileReference {
                     range,
                     name: FileReferenceNode::NameRef(name_ref.clone()),
-                    category: None,
+                    category: ReferenceCategory::empty(),
                 };
                 sink(file_id, reference)
             }
@@ -676,10 +675,15 @@ impl<'a> FindUsages<'a> {
         match NameRefClass::classify(self.sema, name_ref) {
             Some(NameRefClass::Definition(def @ Definition::Module(_))) if def == self.def => {
                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+                let category = if is_name_ref_in_import(name_ref) {
+                    ReferenceCategory::IMPORT
+                } else {
+                    ReferenceCategory::empty()
+                };
                 let reference = FileReference {
                     range,
                     name: FileReferenceNode::NameRef(name_ref.clone()),
-                    category: is_name_ref_in_import(name_ref).then_some(ReferenceCategory::Import),
+                    category,
                 };
                 sink(file_id, reference)
             }
@@ -700,7 +704,7 @@ impl<'a> FindUsages<'a> {
                 let reference = FileReference {
                     range,
                     name: FileReferenceNode::FormatStringEntry(token, range),
-                    category: Some(ReferenceCategory::Read),
+                    category: ReferenceCategory::READ,
                 };
                 sink(file_id, reference)
             }
@@ -719,7 +723,7 @@ impl<'a> FindUsages<'a> {
                 let reference = FileReference {
                     range,
                     name: FileReferenceNode::Lifetime(lifetime.clone()),
-                    category: None,
+                    category: ReferenceCategory::empty(),
                 };
                 sink(file_id, reference)
             }
@@ -817,7 +821,7 @@ impl<'a> FindUsages<'a> {
                     range,
                     name: FileReferenceNode::Name(name.clone()),
                     // FIXME: mutable patterns should have `Write` access
-                    category: Some(ReferenceCategory::Read),
+                    category: ReferenceCategory::READ,
                 };
                 sink(file_id, reference)
             }
@@ -826,7 +830,7 @@ impl<'a> FindUsages<'a> {
                 let reference = FileReference {
                     range,
                     name: FileReferenceNode::Name(name.clone()),
-                    category: None,
+                    category: ReferenceCategory::empty(),
                 };
                 sink(file_id, reference)
             }
@@ -851,7 +855,7 @@ impl<'a> FindUsages<'a> {
                 let reference = FileReference {
                     range,
                     name: FileReferenceNode::Name(name.clone()),
-                    category: None,
+                    category: ReferenceCategory::empty(),
                 };
                 sink(file_id, reference)
             }
@@ -875,38 +879,41 @@ impl ReferenceCategory {
         sema: &Semantics<'_, RootDatabase>,
         def: &Definition,
         r: &ast::NameRef,
-    ) -> Option<ReferenceCategory> {
+    ) -> ReferenceCategory {
+        let mut result = ReferenceCategory::empty();
         if is_name_ref_in_test(sema, r) {
-            return Some(ReferenceCategory::Test);
+            result |= ReferenceCategory::TEST;
         }
 
         // Only Locals and Fields have accesses for now.
         if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
-            return is_name_ref_in_import(r).then_some(ReferenceCategory::Import);
+            if is_name_ref_in_import(r) {
+                result |= ReferenceCategory::IMPORT;
+            }
+            return result;
         }
 
         let mode = r.syntax().ancestors().find_map(|node| {
-        match_ast! {
-            match node {
-                ast::BinExpr(expr) => {
-                    if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
-                        // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
-                        // FIXME: This is not terribly accurate.
-                        if let Some(lhs) = expr.lhs() {
-                            if lhs.syntax().text_range().end() == r.syntax().text_range().end() {
-                                return Some(ReferenceCategory::Write);
+            match_ast! {
+                match node {
+                    ast::BinExpr(expr) => {
+                        if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
+                            // If the variable or field ends on the LHS's end then it's a Write
+                            // (covers fields and locals). FIXME: This is not terribly accurate.
+                            if let Some(lhs) = expr.lhs() {
+                                if lhs.syntax().text_range().end() == r.syntax().text_range().end() {
+                                    return Some(ReferenceCategory::WRITE)
+                                }
                             }
                         }
-                    }
-                    Some(ReferenceCategory::Read)
-                },
-                _ => None
+                        Some(ReferenceCategory::READ)
+                    },
+                    _ => None,
+                }
             }
-        }
-    });
+        }).unwrap_or(ReferenceCategory::READ);
 
-        // Default Locals and Fields to read
-        mode.or(Some(ReferenceCategory::Read))
+        result | mode
     }
 }
 
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index e20e0b67f4b..6f32ce76b22 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -26,7 +26,7 @@ pub struct HighlightedRange {
     // FIXME: This needs to be more precise. Reference category makes sense only
     // for references, but we also have defs. And things like exit points are
     // neither.
-    pub category: Option<ReferenceCategory>,
+    pub category: ReferenceCategory,
 }
 
 #[derive(Default, Clone)]
@@ -113,7 +113,11 @@ fn highlight_closure_captures(
                         range,
                         category,
                     });
-                let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
+                let category = if local.is_mut(sema.db) {
+                    ReferenceCategory::WRITE
+                } else {
+                    ReferenceCategory::empty()
+                };
                 local
                     .sources(sema.db)
                     .into_iter()
@@ -137,7 +141,9 @@ fn highlight_references(
     {
         match resolution.map(Definition::from) {
             Some(def) => iter::once(def).collect(),
-            None => return Some(vec![HighlightedRange { range, category: None }]),
+            None => {
+                return Some(vec![HighlightedRange { range, category: ReferenceCategory::empty() }])
+            }
         }
     } else {
         find_defs(sema, token.clone())
@@ -211,7 +217,11 @@ fn highlight_references(
         // highlight the defs themselves
         match def {
             Definition::Local(local) => {
-                let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
+                let category = if local.is_mut(sema.db) {
+                    ReferenceCategory::WRITE
+                } else {
+                    ReferenceCategory::empty()
+                };
                 local
                     .sources(sema.db)
                     .into_iter()
@@ -238,8 +248,11 @@ fn highlight_references(
                         continue;
                     }
                     let hl_range = nav.focus_range.map(|range| {
-                        let category = matches!(def, Definition::Local(l) if l.is_mut(sema.db))
-                            .then_some(ReferenceCategory::Write);
+                        let category = if matches!(def, Definition::Local(l) if l.is_mut(sema.db)) {
+                            ReferenceCategory::WRITE
+                        } else {
+                            ReferenceCategory::empty()
+                        };
                         HighlightedRange { range, category }
                     });
                     if let Some(hl_range) = hl_range {
@@ -272,24 +285,30 @@ fn highlight_exit_points(
             def_ranges
                 .into_iter()
                 .flatten()
-                .map(|range| HighlightedRange { category: None, range }),
+                .map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }),
         );
         let body = body?;
         walk_expr(&body, &mut |expr| match expr {
             ast::Expr::ReturnExpr(expr) => {
                 if let Some(token) = expr.return_token() {
-                    highlights.push(HighlightedRange { category: None, range: token.text_range() });
+                    highlights.push(HighlightedRange {
+                        category: ReferenceCategory::empty(),
+                        range: token.text_range(),
+                    });
                 }
             }
             ast::Expr::TryExpr(try_) => {
                 if let Some(token) = try_.question_mark_token() {
-                    highlights.push(HighlightedRange { category: None, range: token.text_range() });
+                    highlights.push(HighlightedRange {
+                        category: ReferenceCategory::empty(),
+                        range: token.text_range(),
+                    });
                 }
             }
             ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => {
                 if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) {
                     highlights.push(HighlightedRange {
-                        category: None,
+                        category: ReferenceCategory::empty(),
                         range: expr.syntax().text_range(),
                     });
                 }
@@ -309,7 +328,7 @@ fn highlight_exit_points(
                         .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
                     _ => tail.syntax().text_range(),
                 };
-                highlights.push(HighlightedRange { category: None, range })
+                highlights.push(HighlightedRange { category: ReferenceCategory::empty(), range })
             });
         }
         Some(highlights)
@@ -354,7 +373,9 @@ fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
             token.map(|tok| tok.text_range()),
             label.as_ref().map(|it| it.syntax().text_range()),
         );
-        highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
+        highlights.extend(
+            range.map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }),
+        );
         for_each_break_and_continue_expr(label, body, &mut |expr| {
             let range: Option<TextRange> = match (cursor_token_kind, expr) {
                 (T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => {
@@ -372,7 +393,9 @@ fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
                 ),
                 _ => None,
             };
-            highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
+            highlights.extend(
+                range.map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }),
+            );
         });
         Some(highlights)
     }
@@ -430,14 +453,18 @@ fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
         async_token: Option<SyntaxToken>,
         body: Option<ast::Expr>,
     ) -> Option<Vec<HighlightedRange>> {
-        let mut highlights =
-            vec![HighlightedRange { category: None, range: async_token?.text_range() }];
+        let mut highlights = vec![HighlightedRange {
+            category: ReferenceCategory::empty(),
+            range: async_token?.text_range(),
+        }];
         if let Some(body) = body {
             walk_expr(&body, &mut |expr| {
                 if let ast::Expr::AwaitExpr(expr) = expr {
                     if let Some(token) = expr.await_token() {
-                        highlights
-                            .push(HighlightedRange { category: None, range: token.text_range() });
+                        highlights.push(HighlightedRange {
+                            category: ReferenceCategory::empty(),
+                            range: token.text_range(),
+                        });
                     }
                 }
             });
@@ -481,6 +508,8 @@ fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSe
 
 #[cfg(test)]
 mod tests {
+    use itertools::Itertools;
+
     use crate::fixture;
 
     use super::*;
@@ -504,28 +533,18 @@ mod tests {
 
         let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();
 
-        let mut expected = annotations
-            .into_iter()
-            .map(|(r, access)| (r.range, (!access.is_empty()).then_some(access)))
-            .collect::<Vec<_>>();
+        let mut expected =
+            annotations.into_iter().map(|(r, access)| (r.range, access)).collect::<Vec<_>>();
 
-        let mut actual = hls
+        let mut actual: Vec<(TextRange, String)> = hls
             .into_iter()
             .map(|hl| {
                 (
                     hl.range,
-                    hl.category.map(|it| {
-                        match it {
-                            ReferenceCategory::Read => "read",
-                            ReferenceCategory::Write => "write",
-                            ReferenceCategory::Import => "import",
-                            ReferenceCategory::Test => "test",
-                        }
-                        .to_owned()
-                    }),
+                    hl.category.iter_names().map(|(name, _flag)| name.to_lowercase()).join(","),
                 )
             })
-            .collect::<Vec<_>>();
+            .collect();
         actual.sort_by_key(|(range, _)| range.start());
         expected.sort_by_key(|(range, _)| range.start());
 
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index fef2aba3c61..8eb4bfe6d0d 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -30,7 +30,7 @@ use crate::{FilePosition, NavigationTarget, TryToNav};
 #[derive(Debug, Clone)]
 pub struct ReferenceSearchResult {
     pub declaration: Option<Declaration>,
-    pub references: IntMap<FileId, Vec<(TextRange, Option<ReferenceCategory>)>>,
+    pub references: IntMap<FileId, Vec<(TextRange, ReferenceCategory)>>,
 }
 
 #[derive(Debug, Clone)]
@@ -66,7 +66,7 @@ pub(crate) fn find_all_refs(
                 retain_adt_literal_usages(&mut usages, def, sema);
             }
 
-            let mut references = usages
+            let mut references: IntMap<FileId, Vec<(TextRange, ReferenceCategory)>> = usages
                 .into_iter()
                 .map(|(file_id, refs)| {
                     (
@@ -77,7 +77,7 @@ pub(crate) fn find_all_refs(
                             .collect(),
                     )
                 })
-                .collect::<IntMap<_, Vec<_>>>();
+                .collect();
             let declaration = match def {
                 Definition::Module(module) => {
                     Some(NavigationTarget::from_module_to_decl(sema.db, module))
@@ -93,7 +93,7 @@ pub(crate) fn find_all_refs(
                     references
                         .entry(extra_ref.file_id)
                         .or_default()
-                        .push((extra_ref.focus_or_full_range(), None));
+                        .push((extra_ref.focus_or_full_range(), ReferenceCategory::empty()));
                 }
                 Declaration {
                     is_mut: matches!(def, Definition::Local(l) if l.is_mut(sema.db)),
@@ -300,7 +300,7 @@ fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool {
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
-    use ide_db::{base_db::FileId, search::ReferenceCategory};
+    use ide_db::base_db::FileId;
     use stdx::format_to;
 
     use crate::{fixture, SearchScope};
@@ -324,7 +324,7 @@ fn test() {
                 test_func Function FileId(0) 0..17 3..12
 
                 FileId(0) 35..44
-                FileId(0) 75..84 Test
+                FileId(0) 75..84 test
             "#]],
         );
 
@@ -345,7 +345,7 @@ fn test() {
                 test_func Function FileId(0) 0..17 3..12
 
                 FileId(0) 35..44
-                FileId(0) 96..105 Test
+                FileId(0) 96..105 test
             "#]],
         );
     }
@@ -600,12 +600,12 @@ fn main() {
     i = 5;
 }"#,
             expect![[r#"
-                i Local FileId(0) 20..25 24..25 Write
+                i Local FileId(0) 20..25 24..25 write
 
-                FileId(0) 50..51 Write
-                FileId(0) 54..55 Read
-                FileId(0) 76..77 Write
-                FileId(0) 94..95 Write
+                FileId(0) 50..51 write
+                FileId(0) 54..55 read
+                FileId(0) 76..77 write
+                FileId(0) 94..95 write
             "#]],
         );
     }
@@ -626,8 +626,8 @@ fn bar() {
             expect![[r#"
                 spam Local FileId(0) 19..23 19..23
 
-                FileId(0) 34..38 Read
-                FileId(0) 41..45 Read
+                FileId(0) 34..38 read
+                FileId(0) 41..45 read
             "#]],
         );
     }
@@ -641,7 +641,7 @@ fn foo(i : u32) -> u32 { i$0 }
             expect![[r#"
                 i ValueParam FileId(0) 7..8 7..8
 
-                FileId(0) 25..26 Read
+                FileId(0) 25..26 read
             "#]],
         );
     }
@@ -655,7 +655,7 @@ fn foo(i$0 : u32) -> u32 { i }
             expect![[r#"
                 i ValueParam FileId(0) 7..8 7..8
 
-                FileId(0) 25..26 Read
+                FileId(0) 25..26 read
             "#]],
         );
     }
@@ -676,7 +676,7 @@ fn main(s: Foo) {
             expect![[r#"
                 spam Field FileId(0) 17..30 21..25
 
-                FileId(0) 67..71 Read
+                FileId(0) 67..71 read
             "#]],
         );
     }
@@ -824,7 +824,7 @@ pub struct Foo {
             expect![[r#"
                 foo Module FileId(0) 0..8 4..7
 
-                FileId(0) 14..17 Import
+                FileId(0) 14..17 import
             "#]],
         );
     }
@@ -842,7 +842,7 @@ use self$0;
             expect![[r#"
                 foo Module FileId(0) 0..8 4..7
 
-                FileId(1) 4..8 Import
+                FileId(1) 4..8 import
             "#]],
         );
     }
@@ -857,7 +857,7 @@ use self$0;
             expect![[r#"
                 Module FileId(0) 0..10
 
-                FileId(0) 4..8 Import
+                FileId(0) 4..8 import
             "#]],
         );
     }
@@ -885,7 +885,7 @@ pub(super) struct Foo$0 {
             expect![[r#"
                 Foo Struct FileId(2) 0..41 18..21 some
 
-                FileId(1) 20..23 Import
+                FileId(1) 20..23 import
                 FileId(1) 47..50
             "#]],
         );
@@ -960,10 +960,10 @@ fn foo() {
 }
 "#,
             expect![[r#"
-                i Local FileId(0) 19..24 23..24 Write
+                i Local FileId(0) 19..24 23..24 write
 
-                FileId(0) 34..35 Write
-                FileId(0) 38..39 Read
+                FileId(0) 34..35 write
+                FileId(0) 38..39 read
             "#]],
         );
     }
@@ -984,8 +984,8 @@ fn foo() {
             expect![[r#"
                 f Field FileId(0) 15..21 15..16
 
-                FileId(0) 55..56 Read
-                FileId(0) 68..69 Write
+                FileId(0) 55..56 read
+                FileId(0) 68..69 write
             "#]],
         );
     }
@@ -1002,7 +1002,7 @@ fn foo() {
             expect![[r#"
                 i Local FileId(0) 19..20 19..20
 
-                FileId(0) 26..27 Write
+                FileId(0) 26..27 write
             "#]],
         );
     }
@@ -1048,7 +1048,7 @@ fn g() { f(); }
             expect![[r#"
                 f Function FileId(0) 22..31 25..26
 
-                FileId(1) 11..12 Import
+                FileId(1) 11..12 import
                 FileId(1) 24..25
             "#]],
         );
@@ -1071,7 +1071,7 @@ fn f(s: S) {
             expect![[r#"
                 field Field FileId(0) 15..24 15..20
 
-                FileId(0) 68..73 Read
+                FileId(0) 68..73 read
             "#]],
         );
     }
@@ -1095,7 +1095,7 @@ fn f(e: En) {
             expect![[r#"
                 field Field FileId(0) 32..41 32..37
 
-                FileId(0) 102..107 Read
+                FileId(0) 102..107 read
             "#]],
         );
     }
@@ -1119,7 +1119,7 @@ fn f() -> m::En {
             expect![[r#"
                 field Field FileId(0) 56..65 56..61
 
-                FileId(0) 125..130 Read
+                FileId(0) 125..130 read
             "#]],
         );
     }
@@ -1144,8 +1144,8 @@ impl Foo {
             expect![[r#"
                 self SelfParam FileId(0) 47..51 47..51
 
-                FileId(0) 71..75 Read
-                FileId(0) 152..156 Read
+                FileId(0) 71..75 read
+                FileId(0) 152..156 read
             "#]],
         );
     }
@@ -1165,7 +1165,7 @@ impl Foo {
             expect![[r#"
                 self SelfParam FileId(0) 47..51 47..51
 
-                FileId(0) 63..67 Read
+                FileId(0) 63..67 read
             "#]],
         );
     }
@@ -1185,16 +1185,16 @@ impl Foo {
             if let Some(decl) = refs.declaration {
                 format_to!(actual, "{}", decl.nav.debug_render());
                 if decl.is_mut {
-                    format_to!(actual, " {:?}", ReferenceCategory::Write)
+                    format_to!(actual, " write",)
                 }
                 actual += "\n\n";
             }
 
             for (file_id, references) in &refs.references {
-                for (range, access) in references {
+                for (range, category) in references {
                     format_to!(actual, "{:?} {:?}", file_id, range);
-                    if let Some(access) = access {
-                        format_to!(actual, " {:?}", access);
+                    for (name, _flag) in category.iter_names() {
+                        format_to!(actual, " {}", name.to_lowercase());
                     }
                     actual += "\n";
                 }
@@ -1281,7 +1281,7 @@ fn main() {
             expect![[r#"
                 a Local FileId(0) 59..60 59..60
 
-                FileId(0) 80..81 Read
+                FileId(0) 80..81 read
             "#]],
         );
     }
@@ -1299,7 +1299,7 @@ fn main() {
             expect![[r#"
                 a Local FileId(0) 59..60 59..60
 
-                FileId(0) 80..81 Read
+                FileId(0) 80..81 read
             "#]],
         );
     }
@@ -1479,7 +1479,7 @@ fn test$0() {
             expect![[r#"
                 test Function FileId(0) 0..33 11..15
 
-                FileId(0) 24..28 Test
+                FileId(0) 24..28 test
             "#]],
         );
     }
@@ -1538,9 +1538,9 @@ pub use level1::Foo;
             expect![[r#"
                 Foo Struct FileId(0) 0..15 11..14
 
-                FileId(1) 16..19 Import
-                FileId(2) 16..19 Import
-                FileId(3) 16..19 Import
+                FileId(1) 16..19 import
+                FileId(2) 16..19 import
+                FileId(3) 16..19 import
             "#]],
         );
     }
@@ -1568,7 +1568,7 @@ lib::foo!();
             expect![[r#"
                 foo Macro FileId(1) 0..61 29..32
 
-                FileId(0) 46..49 Import
+                FileId(0) 46..49 import
                 FileId(2) 0..3
                 FileId(3) 5..8
             "#]],
@@ -1731,7 +1731,7 @@ struct Foo;
             expect![[r#"
                 derive_identity Derive FileId(2) 1..107 45..60
 
-                FileId(0) 17..31 Import
+                FileId(0) 17..31 import
                 FileId(0) 56..70
             "#]],
         );
@@ -2055,7 +2055,7 @@ fn method() {}
             expect![[r#"
                 method Field FileId(0) 60..70 60..66
 
-                FileId(0) 136..142 Read
+                FileId(0) 136..142 read
             "#]],
         );
         check(
@@ -2101,7 +2101,7 @@ fn method() {}
             expect![[r#"
                 method Field FileId(0) 60..70 60..66
 
-                FileId(0) 136..142 Read
+                FileId(0) 136..142 read
             "#]],
         );
         check(
@@ -2160,9 +2160,9 @@ fn test() {
             expect![[r#"
                 a Local FileId(0) 20..21 20..21
 
-                FileId(0) 56..57 Read
-                FileId(0) 60..61 Read
-                FileId(0) 68..69 Read
+                FileId(0) 56..57 read
+                FileId(0) 60..61 read
+                FileId(0) 68..69 read
             "#]],
         );
     }
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 06c98166321..9c86704ba3e 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -1164,8 +1164,8 @@ pub(crate) fn handle_references(
                 .flat_map(|(file_id, refs)| {
                     refs.into_iter()
                         .filter(|&(_, category)| {
-                            (!exclude_imports || category != Some(ReferenceCategory::Import))
-                                && (!exclude_tests || category != Some(ReferenceCategory::Test))
+                            (!exclude_imports || !category.contains(ReferenceCategory::IMPORT))
+                                && (!exclude_tests || !category.contains(ReferenceCategory::TEST))
                         })
                         .map(move |(range, _)| FileRange { file_id, range })
                 })
@@ -1452,7 +1452,7 @@ pub(crate) fn handle_document_highlight(
         .into_iter()
         .map(|ide::HighlightedRange { range, category }| lsp_types::DocumentHighlight {
             range: to_proto::range(&line_index, range),
-            kind: category.and_then(to_proto::document_highlight_kind),
+            kind: to_proto::document_highlight_kind(category),
         })
         .collect();
     Ok(Some(res))
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index 7e03458c529..d02f4612dc1 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -92,12 +92,13 @@ pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolK
 pub(crate) fn document_highlight_kind(
     category: ReferenceCategory,
 ) -> Option<lsp_types::DocumentHighlightKind> {
-    match category {
-        ReferenceCategory::Read => Some(lsp_types::DocumentHighlightKind::READ),
-        ReferenceCategory::Write => Some(lsp_types::DocumentHighlightKind::WRITE),
-        ReferenceCategory::Import => None,
-        ReferenceCategory::Test => None,
+    if category.contains(ReferenceCategory::WRITE) {
+        return Some(lsp_types::DocumentHighlightKind::WRITE);
     }
+    if category.contains(ReferenceCategory::READ) {
+        return Some(lsp_types::DocumentHighlightKind::READ);
+    }
+    None
 }
 
 pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {