Merge pull request #22093 from ShoyuVanilla/bump-scip minor: Bump `scip`
diff --git a/crates/hir-def/src/attrs.rs b/crates/hir-def/src/attrs.rs index 92d4928..5cf5a9b 100644 --- a/crates/hir-def/src/attrs.rs +++ b/crates/hir-def/src/attrs.rs
@@ -258,6 +258,9 @@ Some(second_segment) => match &*first_segment { "rust_analyzer" => match &*second_segment { "skip" => attr_flags.insert(AttrFlags::RUST_ANALYZER_SKIP), + "prefer_underscore_import" => { + attr_flags.insert(AttrFlags::PREFER_UNDERSCORE_IMPORT) + } _ => {} }, _ => {} @@ -330,6 +333,8 @@ const MACRO_STYLE_BRACES = 1 << 46; const MACRO_STYLE_BRACKETS = 1 << 47; const MACRO_STYLE_PARENTHESES = 1 << 48; + + const PREFER_UNDERSCORE_IMPORT = 1 << 49; } }
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 1ae6643..dd77934 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs
@@ -3336,6 +3336,19 @@ pub fn complete(self, db: &dyn HirDatabase) -> Complete { Complete::extract(true, self.attrs(db).attrs) } + + // Feature: Prefer Underscore Import Attribute + // Crate authors can declare that their trait prefers to be imported `as _`. This can be used + // for example for extension traits. To do that, a trait has to include the attribute + // `#[rust_analyzer::prefer_underscore_import]` + // + // When a trait includes this attribute, flyimport will import it `as _`, and the quickfix + // to import it will prefer to import it `as _` (but allow to import it normally as well). + // + // Malformed attributes will be ignored without warnings. + pub fn prefer_underscore_import(self, db: &dyn HirDatabase) -> bool { + AttrFlags::query(db, self.id.into()).contains(AttrFlags::PREFER_UNDERSCORE_IMPORT) + } } impl HasVisibility for Trait {
diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs index adeb191..f9d6187 100644 --- a/crates/ide-assists/src/handlers/auto_import.rs +++ b/crates/ide-assists/src/handlers/auto_import.rs
@@ -6,7 +6,7 @@ active_parameter::ActiveParameter, helpers::mod_path_to_ast, imports::{ - import_assets::{ImportAssets, ImportCandidate, LocatedImport}, + import_assets::{ImportAssets, ImportCandidate, LocatedImport, TraitImportCandidate}, insert_use::{ImportScope, insert_use, insert_use_as_alias}, }, }; @@ -123,44 +123,48 @@ let (assist_id, import_name) = (AssistId::quick_fix("auto_import"), import_path.display(ctx.db(), edition)); - acc.add_group( - &group_label, - assist_id, - format!("Import `{import_name}`"), - range, - |builder| { + let add_normal_import = |acc: &mut Assists, label| { + acc.add_group(&group_label, assist_id, label, range, |builder| { let scope = builder.make_import_scope_mut(scope.clone()); insert_use(&scope, mod_path_to_ast(&import_path, edition), &ctx.config.insert_use); - }, - ); - - match import_assets.import_candidate() { - ImportCandidate::TraitAssocItem(name) | ImportCandidate::TraitMethod(name) => { - let is_method = - matches!(import_assets.import_candidate(), ImportCandidate::TraitMethod(_)); - let type_ = if is_method { "method" } else { "item" }; - let group_label = GroupLabel(format!( - "Import a trait for {} {} by alias", - type_, - name.assoc_item_name.text() - )); - acc.add_group( - &group_label, - assist_id, - format!("Import `{import_name} as _`"), - range, - |builder| { - let scope = builder.make_import_scope_mut(scope.clone()); - insert_use_as_alias( - &scope, - mod_path_to_ast(&import_path, edition), - &ctx.config.insert_use, - edition, - ); - }, + }) + }; + let add_underscore_import = |acc: &mut Assists, name: &TraitImportCandidate<'_>, label| { + let is_method = + matches!(import_assets.import_candidate(), ImportCandidate::TraitMethod(_)); + let type_ = if is_method { "method" } else { "item" }; + let group_label = GroupLabel(format!( + "Import a trait for {} {} by alias", + type_, + name.assoc_item_name.text() + )); + acc.add_group(&group_label, assist_id, label, range, |builder| { + let scope = builder.make_import_scope_mut(scope.clone()); + insert_use_as_alias( + &scope, + mod_path_to_ast(&import_path, edition), + &ctx.config.insert_use, + edition, ); + }); + }; + + if let ImportCandidate::TraitAssocItem(name) | ImportCandidate::TraitMethod(name) = + import_assets.import_candidate() + { + if let hir::ItemInNs::Types(hir::ModuleDef::Trait(trait_to_import)) = + import.item_to_import + && trait_to_import.prefer_underscore_import(ctx.db()) + { + // Flip the order of the suggestions and show a preference for `as _` in the name. + add_underscore_import(acc, name, format!("Import `{import_name}`")); + add_normal_import(acc, format!("Import `{import_name}` without `as _`")); + } else { + add_normal_import(acc, format!("Import `{import_name}`")); + add_underscore_import(acc, name, format!("Import `{import_name} as _`")); } - _ => {} + } else { + add_normal_import(acc, format!("Import `{import_name}`")); } } Some(()) @@ -1957,4 +1961,72 @@ "#, ); } + + #[test] + fn prefer_underscore_import() { + check_assist_by_label( + auto_import, + r#" +mod foo { + #[rust_analyzer::prefer_underscore_import] + pub trait Ext { + fn bar(&self) {} + } + impl<T> Ext for T {} +} + +fn baz() { + 1.b$0ar(); +} + "#, + r#" +use foo::Ext as _; + +mod foo { + #[rust_analyzer::prefer_underscore_import] + pub trait Ext { + fn bar(&self) {} + } + impl<T> Ext for T {} +} + +fn baz() { + 1.bar(); +} + "#, + "Import `foo::Ext`", + ); + check_assist_by_label( + auto_import, + r#" +mod foo { + #[rust_analyzer::prefer_underscore_import] + pub trait Ext { + fn bar(&self) {} + } + impl<T> Ext for T {} +} + +fn baz() { + 1.b$0ar(); +} + "#, + r#" +use foo::Ext; + +mod foo { + #[rust_analyzer::prefer_underscore_import] + pub trait Ext { + fn bar(&self) {} + } + impl<T> Ext for T {} +} + +fn baz() { + 1.bar(); +} + "#, + "Import `foo::Ext` without `as _`", + ); + } }
diff --git a/crates/ide-assists/src/handlers/generate_enum_is_method.rs b/crates/ide-assists/src/handlers/generate_enum_is_method.rs index b866022..e278381 100644 --- a/crates/ide-assists/src/handlers/generate_enum_is_method.rs +++ b/crates/ide-assists/src/handlers/generate_enum_is_method.rs
@@ -1,12 +1,14 @@ use ide_db::assists::GroupLabel; -use itertools::Itertools; use stdx::to_lower_snake_case; -use syntax::ast::HasVisibility; -use syntax::ast::{self, AstNode, HasName}; +use syntax::{ + AstNode, Edition, + ast::{self, HasName, HasVisibility, edit::AstNodeEdit}, + syntax_editor::Position, +}; use crate::{ AssistContext, AssistId, Assists, - utils::{add_method_to_adt, find_struct_impl, is_selected}, + utils::{find_struct_impl, generate_impl_with_item, is_selected}, }; // Assist: generate_enum_is_method @@ -64,27 +66,63 @@ target, |builder| { let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} ")); - let method = methods - .iter() - .map(|Method { pattern_suffix, fn_name, variant_name }| { - format!( - " \ - /// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`]. - /// - /// [`{variant_name}`]: {enum_name}::{variant_name} - #[must_use] - {vis}fn {fn_name}(&self) -> bool {{ - matches!(self, Self::{variant_name}{pattern_suffix}) - }}", - ) - }) - .join("\n\n"); - add_method_to_adt(builder, &parent_enum, impl_def, &method); + let fn_items: Vec<ast::AssocItem> = methods + .iter() + .map(|method| build_fn_item(method, &enum_lowercase_name, &enum_name, &vis)) + .collect(); + + if let Some(impl_def) = &impl_def { + let editor = builder.make_editor(impl_def.syntax()); + impl_def.assoc_item_list().unwrap().add_items(&editor, fn_items); + builder.add_file_edits(ctx.vfs_file_id(), editor); + return; + } + + let editor = builder.make_editor(parent_enum.syntax()); + let make = editor.make(); + let indent = parent_enum.indent_level(); + let assoc_list = make.assoc_item_list(fn_items); + let new_impl = generate_impl_with_item(make, &parent_enum, Some(assoc_list)); + editor.insert_all( + Position::after(parent_enum.syntax()), + vec![ + make.whitespace(&format!("\n\n{indent}")).into(), + new_impl.syntax().clone().into(), + ], + ); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } +fn build_fn_item( + method: &Method, + enum_lowercase_name: &str, + enum_name: &ast::Name, + vis: &str, +) -> ast::AssocItem { + let Method { pattern_suffix, fn_name, variant_name } = method; + let fn_text = format!( + "/// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`]. +/// +/// [`{variant_name}`]: {enum_name}::{variant_name} +#[must_use] +{vis}fn {fn_name}(&self) -> bool {{ + matches!(self, Self::{variant_name}{pattern_suffix}) +}}" + ); + let wrapped = format!("impl X {{ {fn_text} }}"); + let parse = syntax::SourceFile::parse(&wrapped, Edition::CURRENT); + let fn_ = parse + .tree() + .syntax() + .descendants() + .find_map(ast::Fn::cast) + .expect("fn text must produce a valid fn node"); + ast::AssocItem::Fn(fn_.indent(1.into())) +} + struct Method { pattern_suffix: &'static str, fn_name: String,
diff --git a/crates/ide-assists/src/handlers/generate_enum_projection_method.rs b/crates/ide-assists/src/handlers/generate_enum_projection_method.rs index 39a6382..9a97ad1 100644 --- a/crates/ide-assists/src/handlers/generate_enum_projection_method.rs +++ b/crates/ide-assists/src/handlers/generate_enum_projection_method.rs
@@ -1,12 +1,14 @@ use ide_db::assists::GroupLabel; -use itertools::Itertools; use stdx::to_lower_snake_case; -use syntax::ast::HasVisibility; -use syntax::ast::{self, AstNode, HasName}; +use syntax::{ + AstNode, Edition, + ast::{self, HasName, HasVisibility, edit::AstNodeEdit}, + syntax_editor::Position, +}; use crate::{ AssistContext, AssistId, Assists, - utils::{add_method_to_adt, find_struct_impl, is_selected}, + utils::{find_struct_impl, generate_impl_with_item, is_selected}, }; // Assist: generate_enum_try_into_method @@ -116,15 +118,6 @@ assist_description: &str, props: ProjectionProps, ) -> Option<()> { - let ProjectionProps { - fn_name_prefix, - self_param, - return_prefix, - return_suffix, - happy_case, - sad_case, - } = props; - let variant = ctx.find_node_at_offset::<ast::Variant>()?; let parent_enum = ast::Adt::Enum(variant.parent_enum()); let variants = variant @@ -135,7 +128,7 @@ .collect::<Vec<_>>(); let methods = variants .iter() - .map(|variant| Method::new(variant, fn_name_prefix)) + .map(|variant| Method::new(variant, props.fn_name_prefix)) .collect::<Option<Vec<_>>>()?; let fn_names = methods.iter().map(|it| it.fn_name.clone()).collect::<Vec<_>>(); stdx::never!(variants.is_empty()); @@ -151,30 +144,66 @@ target, |builder| { let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} ")); + let must_use = if ctx.config.assist_emit_must_use { "#[must_use]\n" } else { "" }; - let must_use = if ctx.config.assist_emit_must_use { "#[must_use]\n " } else { "" }; - - let method = methods + let fn_items: Vec<ast::AssocItem> = methods .iter() - .map(|Method { pattern_suffix, field_type, bound_name, fn_name, variant_name }| { - format!( - " \ - {must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{ - if let Self::{variant_name}{pattern_suffix} = self {{ - {happy_case}({bound_name}) - }} else {{ - {sad_case} - }} - }}" - ) - }) - .join("\n\n"); + .map(|method| build_fn_item(method, &vis, must_use, &props)) + .collect(); - add_method_to_adt(builder, &parent_enum, impl_def, &method); + if let Some(impl_def) = &impl_def { + let editor = builder.make_editor(impl_def.syntax()); + impl_def.assoc_item_list().unwrap().add_items(&editor, fn_items); + builder.add_file_edits(ctx.vfs_file_id(), editor); + return; + } + + let editor = builder.make_editor(parent_enum.syntax()); + let make = editor.make(); + let indent = parent_enum.indent_level(); + let assoc_list = make.assoc_item_list(fn_items); + let new_impl = generate_impl_with_item(make, &parent_enum, Some(assoc_list)); + editor.insert_all( + Position::after(parent_enum.syntax()), + vec![ + make.whitespace(&format!("\n\n{indent}")).into(), + new_impl.syntax().clone().into(), + ], + ); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } +fn build_fn_item( + method: &Method, + vis: &str, + must_use: &str, + props: &ProjectionProps, +) -> ast::AssocItem { + let Method { pattern_suffix, field_type, bound_name, fn_name, variant_name } = method; + let ProjectionProps { self_param, return_prefix, return_suffix, happy_case, sad_case, .. } = + props; + let fn_text = format!( + "{must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{ + if let Self::{variant_name}{pattern_suffix} = self {{ + {happy_case}({bound_name}) + }} else {{ + {sad_case} + }} +}}" + ); + let wrapped = format!("impl X {{ {fn_text} }}"); + let parse = syntax::SourceFile::parse(&wrapped, Edition::CURRENT); + let fn_ = parse + .tree() + .syntax() + .descendants() + .find_map(ast::Fn::cast) + .expect("fn text must produce a valid fn node"); + ast::AssocItem::Fn(fn_.indent(1.into())) +} + struct Method { pattern_suffix: String, field_type: ast::Type, @@ -185,6 +214,7 @@ impl Method { fn new(variant: &ast::Variant, fn_name_prefix: &str) -> Option<Self> { + use itertools::Itertools as _; let variant_name = variant.name()?; let fn_name = format!("{fn_name_prefix}_{}", &to_lower_snake_case(&variant_name.text()));
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index d316162..bf1062d 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs
@@ -15,7 +15,6 @@ syntax_helpers::{node_ext::preorder_expr, prettify_macro_expansion}, }; use itertools::Itertools; -use stdx::format_to; use syntax::{ AstNode, AstToken, Direction, NodeOrToken, SourceFile, SyntaxKind::*, @@ -530,102 +529,6 @@ false } -/// Find the end of the `impl` block for the given `ast::Impl`. -// -// FIXME: this partially overlaps with `find_struct_impl` -pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> { - buf.push('\n'); - let end = impl_def - .assoc_item_list() - .and_then(|it| it.r_curly_token())? - .prev_sibling_or_token()? - .text_range() - .end(); - Some(end) -} - -/// Generates the surrounding `impl Type { <code> }` including type and lifetime -/// parameters. -// FIXME: migrate remaining uses to `generate_impl` -pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { - generate_impl_text_inner(adt, None, true, code) -} - -fn generate_impl_text_inner( - adt: &ast::Adt, - trait_text: Option<&str>, - trait_is_transitive: bool, - code: &str, -) -> String { - // Ensure lifetime params are before type & const params - let generic_params = adt.generic_param_list().map(|generic_params| { - let lifetime_params = - generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); - let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { - let param = match param { - ast::TypeOrConstParam::Type(param) => { - // remove defaults since they can't be specified in impls - let mut bounds = - param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect()); - if let Some(trait_) = trait_text { - // Add the current trait to `bounds` if the trait is transitive, - // meaning `impl<T> Trait for U<T>` requires `T: Trait`. - if trait_is_transitive { - bounds.push(make::type_bound_text(trait_)); - } - }; - // `{ty_param}: {bounds}` - let param = make::type_param(param.name()?, make::type_bound_list(bounds)); - ast::GenericParam::TypeParam(param) - } - ast::TypeOrConstParam::Const(param) => { - // remove defaults since they can't be specified in impls - let param = make::const_param(param.name()?, param.ty()?); - ast::GenericParam::ConstParam(param) - } - }; - Some(param) - }); - - make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) - }); - - // FIXME: use syntax::make & mutable AST apis instead - // `trait_text` and `code` can't be opaque blobs of text - let mut buf = String::with_capacity(code.len()); - - // Copy any cfg attrs from the original adt - buf.push_str("\n\n"); - let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); - cfg_attrs.for_each(|attr| buf.push_str(&format!("{attr}\n"))); - - // `impl{generic_params} {trait_text} for {name}{generic_params.to_generic_args()}` - buf.push_str("impl"); - if let Some(generic_params) = &generic_params { - format_to!(buf, "{generic_params}"); - } - buf.push(' '); - if let Some(trait_text) = trait_text { - buf.push_str(trait_text); - buf.push_str(" for "); - } - buf.push_str(&adt.name().unwrap().text()); - if let Some(generic_params) = generic_params { - format_to!(buf, "{}", generic_params.to_generic_args()); - } - - match adt.where_clause() { - Some(where_clause) => { - format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}"); - } - None => { - format_to!(buf, " {{\n{code}\n}}"); - } - } - - buf -} - /// Generates the corresponding `impl Type {}` including type and lifetime /// parameters. pub(crate) fn generate_impl_with_item( @@ -917,28 +820,6 @@ trait_where_clause.peek().is_some().then(|| make.where_clause(trait_where_clause)) } -pub(crate) fn add_method_to_adt( - builder: &mut SourceChangeBuilder, - adt: &ast::Adt, - impl_def: Option<ast::Impl>, - method: &str, -) { - let mut buf = String::with_capacity(method.len() + 2); - if impl_def.is_some() { - buf.push('\n'); - } - buf.push_str(method); - - let start_offset = impl_def - .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) - .unwrap_or_else(|| { - buf = generate_impl_text(adt, &buf); - adt.syntax().text_range().end() - }); - - builder.insert(start_offset, buf); -} - #[derive(Debug)] pub(crate) struct ReferenceConversion<'db> { conversion: ReferenceConversionType,
diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 62211a8..6abf4f6 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs
@@ -84,7 +84,15 @@ pub ref_match: Option<(CompletionItemRefMode, TextSize)>, /// The import data to add to completion's edits. - pub import_to_add: SmallVec<[String; 1]>, + pub import_to_add: SmallVec<[CompletionItemImport; 1]>, +} + +#[derive(Clone, UpmapFromRaFixture)] +pub struct CompletionItemImport { + /// The path to import. + pub path: String, + /// Whether to import `as _`. + pub as_underscore: bool, } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -585,7 +593,18 @@ let import_to_add = self .imports_to_add .into_iter() - .map(|import| import.import_path.display(db, self.edition).to_string()) + .map(|import| { + let path = import.import_path.display(db, self.edition).to_string(); + let as_underscore = + if let hir::ItemInNs::Types(hir::ModuleDef::Trait(trait_to_import)) = + import.item_to_import + { + trait_to_import.prefer_underscore_import(db) + } else { + false + }; + CompletionItemImport { path, as_underscore } + }) .collect(); CompletionItem {
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 3867e65..3df511a 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs
@@ -36,8 +36,8 @@ pub use crate::{ config::{AutoImportExclusionType, CallableSnippets, CompletionConfig}, item::{ - CompletionItem, CompletionItemKind, CompletionItemRefMode, CompletionRelevance, - CompletionRelevancePostfixMatch, CompletionRelevanceReturnType, + CompletionItem, CompletionItemImport, CompletionItemKind, CompletionItemRefMode, + CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceReturnType, CompletionRelevanceTypeMatch, }, snippet::{Snippet, SnippetScope}, @@ -280,7 +280,7 @@ db: &RootDatabase, config: &CompletionConfig<'_>, FilePosition { file_id, offset }: FilePosition, - imports: impl IntoIterator<Item = String>, + imports: impl IntoIterator<Item = CompletionItemImport>, ) -> Option<Vec<TextEdit>> { let _p = tracing::info_span!("resolve_completion_edits").entered(); let sema = hir::Semantics::new(db); @@ -299,12 +299,18 @@ let new_ast = scope.clone_for_update(); let mut import_insert = TextEdit::builder(); - imports.into_iter().for_each(|full_import_path| { - insert_use::insert_use( - &new_ast, - make::path_from_text_with_edition(&full_import_path, current_edition), - &config.insert_use, - ); + imports.into_iter().for_each(|import| { + let full_path = make::path_from_text_with_edition(&import.path, current_edition); + if import.as_underscore { + insert_use::insert_use_as_alias( + &new_ast, + full_path, + &config.insert_use, + current_edition, + ); + } else { + insert_use::insert_use(&new_ast, full_path, &config.insert_use); + } }); diff(scope.as_syntax_node(), new_ast.as_syntax_node()).into_text_edit(&mut import_insert);
diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs index 5391e6c..60ae077 100644 --- a/crates/ide-completion/src/tests/flyimport.rs +++ b/crates/ide-completion/src/tests/flyimport.rs
@@ -2057,3 +2057,38 @@ "#, ); } + +#[test] +fn prefer_underscore_import() { + check_edit( + "bar", + r#" +mod foo { + #[rust_analyzer::prefer_underscore_import] + pub trait Ext { + fn bar(&self) {} + } + impl<T> Ext for T {} +} + +fn baz() { + 1.bar$0 +} + "#, + r#" +use foo::Ext as _; + +mod foo { + #[rust_analyzer::prefer_underscore_import] + pub trait Ext { + fn bar(&self) {} + } + impl<T> Ext for T {} +} + +fn baz() { + 1.bar();$0 +} + "#, + ); +}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 9f8374f..0af2a1f 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs
@@ -128,7 +128,8 @@ }; pub use ide_completion::{ CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem, - CompletionItemKind, CompletionItemRefMode, CompletionRelevance, Snippet, SnippetScope, + CompletionItemImport, CompletionItemKind, CompletionItemRefMode, CompletionRelevance, Snippet, + SnippetScope, }; pub use ide_db::{ FileId, FilePosition, FileRange, RootDatabase, Severity, SymbolKind, @@ -778,7 +779,7 @@ &self, config: &CompletionConfig<'_>, position: FilePosition, - imports: impl IntoIterator<Item = String> + std::panic::UnwindSafe, + imports: impl IntoIterator<Item = CompletionItemImport> + std::panic::UnwindSafe, ) -> Cancellable<Vec<TextEdit>> { Ok(self .with_db(|db| ide_completion::resolve_completion_edits(db, config, position, imports))?
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 4193bd2..0c1c067 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -7,10 +7,10 @@ use base64::{Engine, prelude::BASE64_STANDARD}; use ide::{ - AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve, FilePosition, - FileRange, FileStructureConfig, FindAllRefsConfig, HoverAction, HoverGotoTypeData, - InlayFieldsToResolve, Query, RangeInfo, Runnable, RunnableKind, SingleResolve, SourceChange, - TextEdit, + AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve, + CompletionItemImport, FilePosition, FileRange, FileStructureConfig, FindAllRefsConfig, + HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, Runnable, RunnableKind, + SingleResolve, SourceChange, TextEdit, }; use ide_db::{FxHashMap, SymbolKind}; use itertools::Itertools; @@ -1233,7 +1233,10 @@ .resolve_completion_edits( &forced_resolve_completions_config, position, - resolve_data.imports.into_iter().map(|import| import.full_import_path), + resolve_data.imports.into_iter().map(|import| CompletionItemImport { + path: import.full_import_path, + as_underscore: import.as_underscore, + }), )? .into_iter() .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
diff --git a/crates/rust-analyzer/src/lsp.rs b/crates/rust-analyzer/src/lsp.rs index c7a5a95..a6a35da 100644 --- a/crates/rust-analyzer/src/lsp.rs +++ b/crates/rust-analyzer/src/lsp.rs
@@ -3,7 +3,7 @@ use core::fmt; use hir::Mutability; -use ide::{CompletionItem, CompletionItemRefMode, CompletionRelevance}; +use ide::{CompletionItem, CompletionItemImport, CompletionItemRefMode, CompletionRelevance}; use tenthash::TentHash; pub mod ext; @@ -136,8 +136,10 @@ hasher.update(item.import_to_add.len().to_ne_bytes()); for import_path in &item.import_to_add { - hasher.update(import_path.len().to_ne_bytes()); - hasher.update(import_path); + let CompletionItemImport { path, as_underscore } = import_path; + hasher.update(path.len().to_ne_bytes()); + hasher.update(path); + hasher.update([u8::from(*as_underscore)]); } hasher.finalize()
diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index e6493ee..5d0d920 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs
@@ -858,6 +858,7 @@ #[derive(Debug, Serialize, Deserialize)] pub struct CompletionImport { pub full_import_path: String, + pub as_underscore: bool, } #[derive(Debug, Deserialize, Default)]
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index 5fa9525..d857f23 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -413,7 +413,10 @@ item.import_to_add .clone() .into_iter() - .map(|import_path| lsp_ext::CompletionImport { full_import_path: import_path }) + .map(|import| lsp_ext::CompletionImport { + full_import_path: import.path, + as_underscore: import.as_underscore, + }) .collect() } else { Vec::new()
diff --git a/docs/book/src/contributing/lsp-extensions.md b/docs/book/src/contributing/lsp-extensions.md index 22c1784..28fb979 100644 --- a/docs/book/src/contributing/lsp-extensions.md +++ b/docs/book/src/contributing/lsp-extensions.md
@@ -1,5 +1,5 @@ <!--- -lsp/ext.rs hash: 235f56089da3dbb5 +lsp/ext.rs hash: dc4ba5f417c74aa6 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: