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: