Skip to content

Commit cbc0069

Browse files
Draft completion hashing
1 parent 91adfec commit cbc0069

File tree

9 files changed

+142
-24
lines changed

9 files changed

+142
-24
lines changed

src/tools/rust-analyzer/Cargo.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,7 @@ dependencies = [
16591659
"hir-def",
16601660
"hir-ty",
16611661
"ide",
1662+
"ide-completion",
16621663
"ide-db",
16631664
"ide-ssr",
16641665
"intern",
@@ -1687,6 +1688,7 @@ dependencies = [
16871688
"stdx",
16881689
"syntax",
16891690
"syntax-bridge",
1691+
"tenthash",
16901692
"test-fixture",
16911693
"test-utils",
16921694
"tikv-jemallocator",
@@ -1990,6 +1992,12 @@ dependencies = [
19901992
"tt",
19911993
]
19921994

1995+
[[package]]
1996+
name = "tenthash"
1997+
version = "0.4.0"
1998+
source = "registry+https://github.com/rust-lang/crates.io-index"
1999+
checksum = "d67f9f3cf70e0852941d7bc3cb884b49b24b8ee956baf91ad0abae31f5ef11fb"
2000+
19932001
[[package]]
19942002
name = "test-fixture"
19952003
version = "0.0.0"

src/tools/rust-analyzer/crates/ide-completion/src/item.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,7 @@ pub enum CompletionItemKind {
346346
impl_from!(SymbolKind for CompletionItemKind);
347347

348348
impl CompletionItemKind {
349-
#[cfg(test)]
350-
pub(crate) fn tag(self) -> &'static str {
349+
pub fn tag(self) -> &'static str {
351350
match self {
352351
CompletionItemKind::SymbolKind(kind) => match kind {
353352
SymbolKind::Attribute => "at",

src/tools/rust-analyzer/crates/ide-completion/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub use crate::{
3434
config::{CallableSnippets, CompletionConfig},
3535
item::{
3636
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
37+
CompletionRelevanceReturnType, CompletionRelevanceTypeMatch,
3738
},
3839
snippet::{Snippet, SnippetScope},
3940
};

src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ anyhow.workspace = true
2424
crossbeam-channel.workspace = true
2525
dirs = "5.0.1"
2626
dissimilar.workspace = true
27+
ide-completion.workspace = true
2728
itertools.workspace = true
2829
scip = "0.5.1"
2930
lsp-types = { version = "=0.95.0", features = ["proposed"] }
@@ -34,6 +35,7 @@ rayon.workspace = true
3435
rustc-hash.workspace = true
3536
serde_json = { workspace = true, features = ["preserve_order"] }
3637
serde.workspace = true
38+
tenthash = "0.4.0"
3739
num_cpus = "1.15.0"
3840
mimalloc = { version = "0.1.30", default-features = false, optional = true }
3941
lsp-server.workspace = true
@@ -90,13 +92,13 @@ jemalloc = ["jemallocator", "profile/jemalloc"]
9092
force-always-assert = ["always-assert/force"]
9193
sysroot-abi = []
9294
in-rust-tree = [
93-
"sysroot-abi",
94-
"syntax/in-rust-tree",
95-
"parser/in-rust-tree",
96-
"hir/in-rust-tree",
97-
"hir-def/in-rust-tree",
98-
"hir-ty/in-rust-tree",
99-
"load-cargo/in-rust-tree",
95+
"sysroot-abi",
96+
"syntax/in-rust-tree",
97+
"parser/in-rust-tree",
98+
"hir/in-rust-tree",
99+
"hir-def/in-rust-tree",
100+
"hir-ty/in-rust-tree",
101+
"load-cargo/in-rust-tree",
100102
]
101103

102104
[lints]

src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use triomphe::Arc;
3636
use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
3737

3838
use crate::{
39+
completion_item_hash,
3940
config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
4041
diagnostics::convert_diagnostic,
4142
global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
@@ -1122,35 +1123,42 @@ pub(crate) fn handle_completion_resolve(
11221123
return Ok(original_completion);
11231124
};
11241125
let source_root = snap.analysis.source_root_id(file_id)?;
1126+
let Some(completion_hash_for_resolve) = &resolve_data.completion_item_hash else {
1127+
return Ok(original_completion);
1128+
};
11251129

11261130
let mut forced_resolve_completions_config = snap.config.completion(Some(source_root));
11271131
forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty();
11281132

11291133
let position = FilePosition { file_id, offset };
1130-
let Some(resolved_completions) = snap.analysis.completions(
1134+
let Some(completions) = snap.analysis.completions(
11311135
&forced_resolve_completions_config,
11321136
position,
11331137
resolve_data.trigger_character,
11341138
)?
11351139
else {
11361140
return Ok(original_completion);
11371141
};
1142+
1143+
let Some(corresponding_completion) = completions.into_iter().find(|completion_item| {
1144+
let hash = completion_item_hash(&completion_item, resolve_data.for_ref);
1145+
&hash == completion_hash_for_resolve
1146+
}) else {
1147+
return Ok(original_completion);
1148+
};
1149+
11381150
let mut resolved_completions = to_proto::completion_items(
11391151
&snap.config,
11401152
&forced_resolve_completions_config.fields_to_resolve,
11411153
&line_index,
11421154
snap.file_version(position.file_id),
11431155
resolve_data.position,
11441156
resolve_data.trigger_character,
1145-
resolved_completions,
1157+
vec![corresponding_completion],
11461158
);
1147-
1148-
let mut resolved_completion =
1149-
if resolved_completions.get(resolve_data.completion_item_index).is_some() {
1150-
resolved_completions.swap_remove(resolve_data.completion_item_index)
1151-
} else {
1152-
return Ok(original_completion);
1153-
};
1159+
let Some(mut resolved_completion) = resolved_completions.pop() else {
1160+
return Ok(original_completion);
1161+
};
11541162

11551163
if !resolve_data.imports.is_empty() {
11561164
let additional_edits = snap

src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ use self::lsp::ext as lsp_ext;
4747
#[cfg(test)]
4848
mod integrated_benchmarks;
4949

50+
use ide::{CompletionItem, CompletionRelevance, TextEdit, TextRange};
5051
use serde::de::DeserializeOwned;
52+
use tenthash::TentHasher;
5153

5254
pub use crate::{
5355
lsp::capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph,
@@ -61,3 +63,90 @@ pub fn from_json<T: DeserializeOwned>(
6163
serde_json::from_value(json.clone())
6264
.map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}"))
6365
}
66+
67+
fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; 20] {
68+
fn hash_text_range(hasher: &mut TentHasher, text_range: &TextRange) {
69+
hasher.update(u32::from(text_range.start()).to_le_bytes());
70+
hasher.update(u32::from(text_range.end()).to_le_bytes());
71+
}
72+
73+
fn hash_text_edit(hasher: &mut TentHasher, edit: &TextEdit) {
74+
for indel in edit.iter() {
75+
hasher.update(&indel.insert);
76+
hash_text_range(hasher, &indel.delete);
77+
}
78+
}
79+
80+
fn has_completion_relevance(hasher: &mut TentHasher, relevance: &CompletionRelevance) {
81+
use ide_completion::{
82+
CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
83+
CompletionRelevanceTypeMatch,
84+
};
85+
86+
if let Some(type_match) = &relevance.type_match {
87+
let label = match type_match {
88+
CompletionRelevanceTypeMatch::CouldUnify => "could_unify",
89+
CompletionRelevanceTypeMatch::Exact => "exact",
90+
};
91+
hasher.update(label);
92+
}
93+
hasher.update(&[u8::from(relevance.exact_name_match), u8::from(relevance.is_local)]);
94+
if let Some(trait_) = &relevance.trait_ {
95+
hasher.update(&[u8::from(trait_.is_op_method), u8::from(trait_.notable_trait)]);
96+
}
97+
hasher.update(&[
98+
u8::from(relevance.is_name_already_imported),
99+
u8::from(relevance.requires_import),
100+
u8::from(relevance.is_private_editable),
101+
]);
102+
if let Some(postfix_match) = &relevance.postfix_match {
103+
let label = match postfix_match {
104+
CompletionRelevancePostfixMatch::NonExact => "non_exact",
105+
CompletionRelevancePostfixMatch::Exact => "exact",
106+
};
107+
hasher.update(label);
108+
}
109+
if let Some(function) = &relevance.function {
110+
hasher.update(&[u8::from(function.has_params), u8::from(function.has_self_param)]);
111+
let label = match function.return_type {
112+
CompletionRelevanceReturnType::Other => "other",
113+
CompletionRelevanceReturnType::DirectConstructor => "direct_constructor",
114+
CompletionRelevanceReturnType::Constructor => "constructor",
115+
CompletionRelevanceReturnType::Builder => "builder",
116+
};
117+
hasher.update(label);
118+
}
119+
}
120+
121+
let mut hasher = TentHasher::new();
122+
hasher.update(&[
123+
u8::from(is_ref_completion),
124+
u8::from(item.is_snippet),
125+
u8::from(item.deprecated),
126+
u8::from(item.trigger_call_info),
127+
]);
128+
hasher.update(&item.label);
129+
if let Some(label_detail) = &item.label_detail {
130+
hasher.update(label_detail);
131+
}
132+
hash_text_range(&mut hasher, &item.source_range);
133+
hash_text_edit(&mut hasher, &item.text_edit);
134+
hasher.update(item.kind.tag());
135+
hasher.update(&item.lookup);
136+
if let Some(detail) = &item.detail {
137+
hasher.update(detail);
138+
}
139+
if let Some(documentation) = &item.documentation {
140+
hasher.update(documentation.as_str());
141+
}
142+
has_completion_relevance(&mut hasher, &item.relevance);
143+
if let Some((mutability, text_size)) = &item.ref_match {
144+
hasher.update(mutability.as_keyword_for_ref());
145+
hasher.update(u32::from(*text_size).to_le_bytes());
146+
}
147+
for (import_path, import_name) in &item.import_to_add {
148+
hasher.update(import_path);
149+
hasher.update(import_name);
150+
}
151+
hasher.finalize()
152+
}

src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,8 @@ pub struct CompletionResolveData {
826826
pub imports: Vec<CompletionImport>,
827827
pub version: Option<i32>,
828828
pub trigger_character: Option<char>,
829-
pub completion_item_index: usize,
829+
pub for_ref: bool,
830+
pub completion_item_hash: Option<[u8; 20]>,
830831
}
831832

832833
#[derive(Debug, Serialize, Deserialize)]

src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use serde_json::to_value;
2121
use vfs::AbsPath;
2222

2323
use crate::{
24+
completion_item_hash,
2425
config::{CallInfoConfig, Config},
2526
global_state::GlobalStateSnapshot,
2627
line_index::{LineEndings, LineIndex, PositionEncoding},
@@ -274,6 +275,11 @@ fn completion_item(
274275
completion_trigger_character: Option<char>,
275276
item: CompletionItem,
276277
) {
278+
let original_completion_item = if fields_to_resolve == &CompletionFieldsToResolve::empty() {
279+
None
280+
} else {
281+
Some(item.clone())
282+
};
277283
let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
278284
let ref_match = item.ref_match();
279285

@@ -393,16 +399,17 @@ fn completion_item(
393399
Vec::new()
394400
};
395401
let (ref_resolve_data, resolve_data) = if something_to_resolve || !imports.is_empty() {
396-
let mut item_index = acc.len();
397402
let ref_resolve_data = if ref_match.is_some() {
398403
let ref_resolve_data = lsp_ext::CompletionResolveData {
399404
position: tdpp.clone(),
400405
imports: Vec::new(),
401406
version,
402407
trigger_character: completion_trigger_character,
403-
completion_item_index: item_index,
408+
for_ref: true,
409+
completion_item_hash: original_completion_item
410+
.as_ref()
411+
.map(|item| completion_item_hash(item, true)),
404412
};
405-
item_index += 1;
406413
Some(to_value(ref_resolve_data).unwrap())
407414
} else {
408415
None
@@ -412,7 +419,10 @@ fn completion_item(
412419
imports,
413420
version,
414421
trigger_character: completion_trigger_character,
415-
completion_item_index: item_index,
422+
for_ref: false,
423+
completion_item_hash: original_completion_item
424+
.as_ref()
425+
.map(|item| completion_item_hash(item, false)),
416426
};
417427
(ref_resolve_data, Some(to_value(resolve_data).unwrap()))
418428
} else {

src/tools/rust-analyzer/docs/dev/lsp-extensions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!---
2-
lsp/ext.rs hash: 96f88b7a5d0080c6
2+
lsp/ext.rs hash: 7d77940d7e63a8b
33
44
If you need to change the above hash to make the test pass, please check if you
55
need to adjust this doc as well and ping this issue:

0 commit comments

Comments
 (0)