Revert r-a completions breakage

Repeats the revert to `stable` https://github.com/rust-lang/rust/pull/133476
using https://patch-diff.githubusercontent.com/raw/rust-lang/rust/pull/133476.diff
This commit is contained in:
Kirill Bulatov 2024-11-27 20:37:46 +02:00
parent b4297a573b
commit 8d3b048c12
11 changed files with 75 additions and 244 deletions

View file

@ -7,7 +7,7 @@
use hir::ImportPathConfig; use hir::ImportPathConfig;
use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap}; use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap};
use crate::{snippet::Snippet, CompletionFieldsToResolve}; use crate::snippet::Snippet;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompletionConfig { pub struct CompletionConfig {
@ -27,7 +27,6 @@ pub struct CompletionConfig {
pub prefer_absolute: bool, pub prefer_absolute: bool,
pub snippets: Vec<Snippet>, pub snippets: Vec<Snippet>,
pub limit: Option<usize>, pub limit: Option<usize>,
pub fields_to_resolve: CompletionFieldsToResolve,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]

View file

@ -38,31 +38,6 @@ pub use crate::{
snippet::{Snippet, SnippetScope}, snippet::{Snippet, SnippetScope},
}; };
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CompletionFieldsToResolve {
pub resolve_label_details: bool,
pub resolve_tags: bool,
pub resolve_detail: bool,
pub resolve_documentation: bool,
pub resolve_filter_text: bool,
pub resolve_text_edit: bool,
pub resolve_command: bool,
}
impl CompletionFieldsToResolve {
pub const fn empty() -> Self {
Self {
resolve_label_details: false,
resolve_tags: false,
resolve_detail: false,
resolve_documentation: false,
resolve_filter_text: false,
resolve_text_edit: false,
resolve_command: false,
}
}
}
//FIXME: split the following feature into fine-grained features. //FIXME: split the following feature into fine-grained features.
// Feature: Magic Completions // Feature: Magic Completions

View file

@ -37,8 +37,8 @@ use test_fixture::ChangeFixture;
use test_utils::assert_eq_text; use test_utils::assert_eq_text;
use crate::{ use crate::{
resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionItem,
CompletionItem, CompletionItemKind, CompletionItemKind,
}; };
/// Lots of basic item definitions /// Lots of basic item definitions
@ -84,7 +84,6 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
prefer_absolute: false, prefer_absolute: false,
snippets: Vec::new(), snippets: Vec::new(),
limit: None, limit: None,
fields_to_resolve: CompletionFieldsToResolve::empty(),
}; };
pub(crate) fn completion_list(ra_fixture: &str) -> String { pub(crate) fn completion_list(ra_fixture: &str) -> String {

View file

@ -119,8 +119,8 @@ pub use ide_assists::{
Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve, Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve,
}; };
pub use ide_completion::{ pub use ide_completion::{
CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem, CallableSnippets, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance,
CompletionItemKind, CompletionRelevance, Snippet, SnippetScope, Snippet, SnippetScope,
}; };
pub use ide_db::text_edit::{Indel, TextEdit}; pub use ide_db::text_edit::{Indel, TextEdit};
pub use ide_db::{ pub use ide_db::{

View file

@ -13,7 +13,7 @@ use cfg::{CfgAtom, CfgDiff};
use hir::Symbol; use hir::Symbol;
use ide::{ use ide::{
AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig, AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig,
CompletionFieldsToResolve, DiagnosticsConfig, ExprFillDefaultMode, GenericParameterHints, DiagnosticsConfig, ExprFillDefaultMode, GenericParameterHints,
HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve,
InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
Snippet, SnippetScope, SourceRootId, Snippet, SnippetScope, SourceRootId,
@ -1418,7 +1418,6 @@ impl Config {
} }
pub fn completion(&self, source_root: Option<SourceRootId>) -> CompletionConfig { pub fn completion(&self, source_root: Option<SourceRootId>) -> CompletionConfig {
let client_capability_fields = self.completion_resolve_support_properties();
CompletionConfig { CompletionConfig {
enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(), enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(),
enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned() enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned()
@ -1443,15 +1442,6 @@ impl Config {
limit: self.completion_limit(source_root).to_owned(), limit: self.completion_limit(source_root).to_owned(),
enable_term_search: self.completion_termSearch_enable(source_root).to_owned(), enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64, term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
fields_to_resolve: CompletionFieldsToResolve {
resolve_label_details: client_capability_fields.contains("labelDetails"),
resolve_tags: client_capability_fields.contains("tags"),
resolve_detail: client_capability_fields.contains("detail"),
resolve_documentation: client_capability_fields.contains("documentation"),
resolve_filter_text: client_capability_fields.contains("filterText"),
resolve_text_edit: client_capability_fields.contains("textEdit"),
resolve_command: client_capability_fields.contains("command"),
},
} }
} }

View file

@ -11,9 +11,9 @@ use std::{
use anyhow::Context; use anyhow::Context;
use ide::{ use ide::{
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve, AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
FilePosition, FileRange, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
RangeInfo, ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
}; };
use ide_db::{FxHashMap, SymbolKind}; use ide_db::{FxHashMap, SymbolKind};
use itertools::Itertools; use itertools::Itertools;
@ -1086,11 +1086,9 @@ pub(crate) fn handle_completion(
let items = to_proto::completion_items( let items = to_proto::completion_items(
&snap.config, &snap.config,
&completion_config.fields_to_resolve,
&line_index, &line_index,
snap.file_version(position.file_id), snap.file_version(position.file_id),
text_document_position, text_document_position,
completion_trigger_character,
items, items,
); );
@ -1123,68 +1121,36 @@ pub(crate) fn handle_completion_resolve(
}; };
let source_root = snap.analysis.source_root_id(file_id)?; let source_root = snap.analysis.source_root_id(file_id)?;
let mut forced_resolve_completions_config = snap.config.completion(Some(source_root)); let additional_edits = snap
forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty(); .analysis
.resolve_completion_edits(
&snap.config.completion(Some(source_root)),
FilePosition { file_id, offset },
resolve_data
.imports
.into_iter()
.map(|import| (import.full_import_path, import.imported_name)),
)?
.into_iter()
.flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
.collect::<Vec<_>>();
let position = FilePosition { file_id, offset }; if !all_edits_are_disjoint(&original_completion, &additional_edits) {
let Some(resolved_completions) = snap.analysis.completions( return Err(LspError::new(
&forced_resolve_completions_config, ErrorCode::InternalError as i32,
position, "Import edit overlaps with the original completion edits, this is not LSP-compliant"
resolve_data.trigger_character, .into(),
)? )
else { .into());
return Ok(original_completion);
};
let mut resolved_completions = to_proto::completion_items(
&snap.config,
&forced_resolve_completions_config.fields_to_resolve,
&line_index,
snap.file_version(position.file_id),
resolve_data.position,
resolve_data.trigger_character,
resolved_completions,
);
let mut resolved_completion =
if resolved_completions.get(resolve_data.completion_item_index).is_some() {
resolved_completions.swap_remove(resolve_data.completion_item_index)
} else {
return Ok(original_completion);
};
if !resolve_data.imports.is_empty() {
let additional_edits = snap
.analysis
.resolve_completion_edits(
&forced_resolve_completions_config,
position,
resolve_data
.imports
.into_iter()
.map(|import| (import.full_import_path, import.imported_name)),
)?
.into_iter()
.flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
.collect::<Vec<_>>();
if !all_edits_are_disjoint(&resolved_completion, &additional_edits) {
return Err(LspError::new(
ErrorCode::InternalError as i32,
"Import edit overlaps with the original completion edits, this is not LSP-compliant"
.into(),
)
.into());
}
if let Some(original_additional_edits) = resolved_completion.additional_text_edits.as_mut()
{
original_additional_edits.extend(additional_edits)
} else {
resolved_completion.additional_text_edits = Some(additional_edits);
}
} }
Ok(resolved_completion) if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() {
original_additional_edits.extend(additional_edits)
} else {
original_completion.additional_text_edits = Some(additional_edits);
}
Ok(original_completion)
} }
pub(crate) fn handle_folding_range( pub(crate) fn handle_folding_range(

View file

@ -12,8 +12,7 @@
use hir::ChangeWithProcMacros; use hir::ChangeWithProcMacros;
use ide::{ use ide::{
AnalysisHost, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig, AnalysisHost, CallableSnippets, CompletionConfig, DiagnosticsConfig, FilePosition, TextSize,
FilePosition, TextSize,
}; };
use ide_db::{ use ide_db::{
imports::insert_use::{ImportGranularity, InsertUseConfig}, imports::insert_use::{ImportGranularity, InsertUseConfig},
@ -173,7 +172,6 @@ fn integrated_completion_benchmark() {
snippets: Vec::new(), snippets: Vec::new(),
limit: None, limit: None,
add_semicolon_to_unit: true, add_semicolon_to_unit: true,
fields_to_resolve: CompletionFieldsToResolve::empty(),
}; };
let position = let position =
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@ -221,7 +219,6 @@ fn integrated_completion_benchmark() {
snippets: Vec::new(), snippets: Vec::new(),
limit: None, limit: None,
add_semicolon_to_unit: true, add_semicolon_to_unit: true,
fields_to_resolve: CompletionFieldsToResolve::empty(),
}; };
let position = let position =
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@ -267,7 +264,6 @@ fn integrated_completion_benchmark() {
snippets: Vec::new(), snippets: Vec::new(),
limit: None, limit: None,
add_semicolon_to_unit: true, add_semicolon_to_unit: true,
fields_to_resolve: CompletionFieldsToResolve::empty(),
}; };
let position = let position =
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };

View file

@ -468,7 +468,7 @@ impl ClientCapabilities {
.unwrap_or_default() .unwrap_or_default()
} }
pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<&str> { pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<String> {
self.0 self.0
.text_document .text_document
.as_ref() .as_ref()
@ -477,22 +477,8 @@ impl ClientCapabilities {
.map(|inlay_resolve| inlay_resolve.properties.iter()) .map(|inlay_resolve| inlay_resolve.properties.iter())
.into_iter() .into_iter()
.flatten() .flatten()
.map(|s| s.as_str()) .cloned()
.collect() .collect::<FxHashSet<_>>()
}
pub fn completion_resolve_support_properties(&self) -> FxHashSet<&str> {
self.0
.text_document
.as_ref()
.and_then(|text| text.completion.as_ref())
.and_then(|completion_caps| completion_caps.completion_item.as_ref())
.and_then(|completion_item_caps| completion_item_caps.resolve_support.as_ref())
.map(|resolve_support| resolve_support.properties.iter())
.into_iter()
.flatten()
.map(|s| s.as_str())
.collect()
} }
pub fn hover_markdown_support(&self) -> bool { pub fn hover_markdown_support(&self) -> bool {

View file

@ -825,8 +825,6 @@ pub struct CompletionResolveData {
pub position: lsp_types::TextDocumentPositionParams, pub position: lsp_types::TextDocumentPositionParams,
pub imports: Vec<CompletionImport>, pub imports: Vec<CompletionImport>,
pub version: Option<i32>, pub version: Option<i32>,
pub trigger_character: Option<char>,
pub completion_item_index: usize,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]

View file

@ -6,9 +6,9 @@ use std::{
}; };
use ide::{ use ide::{
Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionFieldsToResolve, Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup,
NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
@ -228,11 +228,9 @@ pub(crate) fn snippet_text_edit_vec(
pub(crate) fn completion_items( pub(crate) fn completion_items(
config: &Config, config: &Config,
fields_to_resolve: &CompletionFieldsToResolve,
line_index: &LineIndex, line_index: &LineIndex,
version: Option<i32>, version: Option<i32>,
tdpp: lsp_types::TextDocumentPositionParams, tdpp: lsp_types::TextDocumentPositionParams,
completion_trigger_character: Option<char>,
mut items: Vec<CompletionItem>, mut items: Vec<CompletionItem>,
) -> Vec<lsp_types::CompletionItem> { ) -> Vec<lsp_types::CompletionItem> {
if config.completion_hide_deprecated() { if config.completion_hide_deprecated() {
@ -242,17 +240,7 @@ pub(crate) fn completion_items(
let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default(); let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
let mut res = Vec::with_capacity(items.len()); let mut res = Vec::with_capacity(items.len());
for item in items { for item in items {
completion_item( completion_item(&mut res, config, line_index, version, &tdpp, max_relevance, item);
&mut res,
config,
fields_to_resolve,
line_index,
version,
&tdpp,
max_relevance,
completion_trigger_character,
item,
);
} }
if let Some(limit) = config.completion(None).limit { if let Some(limit) = config.completion(None).limit {
@ -266,33 +254,21 @@ pub(crate) fn completion_items(
fn completion_item( fn completion_item(
acc: &mut Vec<lsp_types::CompletionItem>, acc: &mut Vec<lsp_types::CompletionItem>,
config: &Config, config: &Config,
fields_to_resolve: &CompletionFieldsToResolve,
line_index: &LineIndex, line_index: &LineIndex,
version: Option<i32>, version: Option<i32>,
tdpp: &lsp_types::TextDocumentPositionParams, tdpp: &lsp_types::TextDocumentPositionParams,
max_relevance: u32, max_relevance: u32,
completion_trigger_character: Option<char>,
item: CompletionItem, item: CompletionItem,
) { ) {
let insert_replace_support = config.insert_replace_support().then_some(tdpp.position); let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
let ref_match = item.ref_match(); let ref_match = item.ref_match();
let lookup = item.lookup().to_owned();
let mut additional_text_edits = Vec::new(); let mut additional_text_edits = Vec::new();
let mut something_to_resolve = false;
let filter_text = if fields_to_resolve.resolve_filter_text { // LSP does not allow arbitrary edits in completion, so we have to do a
something_to_resolve |= !item.lookup().is_empty(); // non-trivial mapping here.
None let text_edit = {
} else {
Some(item.lookup().to_owned())
};
let text_edit = if fields_to_resolve.resolve_text_edit {
something_to_resolve |= true;
None
} else {
// LSP does not allow arbitrary edits in completion, so we have to do a
// non-trivial mapping here.
let mut text_edit = None; let mut text_edit = None;
let source_range = item.source_range; let source_range = item.source_range;
for indel in item.text_edit { for indel in item.text_edit {
@ -315,49 +291,25 @@ fn completion_item(
additional_text_edits.push(text_edit); additional_text_edits.push(text_edit);
} }
} }
Some(text_edit.unwrap()) text_edit.unwrap()
}; };
let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET); let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET);
let tags = if fields_to_resolve.resolve_tags { let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
something_to_resolve |= item.deprecated;
None
} else {
item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED])
};
let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints { let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
if fields_to_resolve.resolve_command { Some(command::trigger_parameter_hints())
something_to_resolve |= true;
None
} else {
Some(command::trigger_parameter_hints())
}
} else { } else {
None None
}; };
let detail = if fields_to_resolve.resolve_detail {
something_to_resolve |= item.detail.is_some();
None
} else {
item.detail.clone()
};
let documentation = if fields_to_resolve.resolve_documentation {
something_to_resolve |= item.documentation.is_some();
None
} else {
item.documentation.map(documentation)
};
let mut lsp_item = lsp_types::CompletionItem { let mut lsp_item = lsp_types::CompletionItem {
label: item.label.to_string(), label: item.label.to_string(),
detail, detail: item.detail,
filter_text, filter_text: Some(lookup),
kind: Some(completion_item_kind(item.kind)), kind: Some(completion_item_kind(item.kind)),
text_edit, text_edit: Some(text_edit),
additional_text_edits: Some(additional_text_edits), additional_text_edits: Some(additional_text_edits),
documentation, documentation: item.documentation.map(documentation),
deprecated: Some(item.deprecated), deprecated: Some(item.deprecated),
tags, tags,
command, command,
@ -366,62 +318,33 @@ fn completion_item(
}; };
if config.completion_label_details_support() { if config.completion_label_details_support() {
if fields_to_resolve.resolve_label_details { lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails {
something_to_resolve |= true; detail: item.label_detail.as_ref().map(ToString::to_string),
} else { description: lsp_item.detail.clone(),
lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails { });
detail: item.label_detail.as_ref().map(ToString::to_string),
description: item.detail,
});
}
} else if let Some(label_detail) = item.label_detail { } else if let Some(label_detail) = item.label_detail {
lsp_item.label.push_str(label_detail.as_str()); lsp_item.label.push_str(label_detail.as_str());
} }
set_score(&mut lsp_item, max_relevance, item.relevance); set_score(&mut lsp_item, max_relevance, item.relevance);
let imports = if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() {
if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() { let imports = item
item.import_to_add .import_to_add
.into_iter() .into_iter()
.map(|(import_path, import_name)| lsp_ext::CompletionImport { .map(|(import_path, import_name)| lsp_ext::CompletionImport {
full_import_path: import_path, full_import_path: import_path,
imported_name: import_name, imported_name: import_name,
}) })
.collect() .collect::<Vec<_>>();
} else { if !imports.is_empty() {
Vec::new() let data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports, version };
}; lsp_item.data = Some(to_value(data).unwrap());
let (ref_resolve_data, resolve_data) = if something_to_resolve || !imports.is_empty() { }
let mut item_index = acc.len(); }
let ref_resolve_data = if ref_match.is_some() {
let ref_resolve_data = lsp_ext::CompletionResolveData {
position: tdpp.clone(),
imports: Vec::new(),
version,
trigger_character: completion_trigger_character,
completion_item_index: item_index,
};
item_index += 1;
Some(to_value(ref_resolve_data).unwrap())
} else {
None
};
let resolve_data = lsp_ext::CompletionResolveData {
position: tdpp.clone(),
imports,
version,
trigger_character: completion_trigger_character,
completion_item_index: item_index,
};
(ref_resolve_data, Some(to_value(resolve_data).unwrap()))
} else {
(None, None)
};
if let Some((label, indel, relevance)) = ref_match { if let Some((label, indel, relevance)) = ref_match {
let mut lsp_item_with_ref = let mut lsp_item_with_ref = lsp_types::CompletionItem { label, ..lsp_item.clone() };
lsp_types::CompletionItem { label, data: ref_resolve_data, ..lsp_item.clone() };
lsp_item_with_ref lsp_item_with_ref
.additional_text_edits .additional_text_edits
.get_or_insert_with(Default::default) .get_or_insert_with(Default::default)
@ -430,7 +353,6 @@ fn completion_item(
acc.push(lsp_item_with_ref); acc.push(lsp_item_with_ref);
}; };
lsp_item.data = resolve_data;
acc.push(lsp_item); acc.push(lsp_item);
fn set_score( fn set_score(

View file

@ -1,5 +1,5 @@
<!--- <!---
lsp/ext.rs hash: 96f88b7a5d0080c6 lsp/ext.rs hash: 6292ee8d88d4c9ec
If you need to change the above hash to make the test pass, please check if you If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue: need to adjust this doc as well and ping this issue: