Repository: rust-analyzer/rust-analyzer
Pull request #6501 (2020-11-08 22:24:39)

Remove text_edit_builder api from AssistBuilder
Also fixes a small bug in `expand_glob_import` in regards to the very nice looking `something::{*}` import when only one item was used. Before it would duplicate the path and just append it, causing the following wrong import `something::something::UsedItem`.


Patch:
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index fcfe2d6ee4..51a160f405 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -275,12 +275,6 @@ impl AssistBuilder {
         algo::diff(&node, &new).into_text_edit(&mut self.edit);
     }
 
-    // FIXME: kill this API
-    /// Get access to the raw `TextEditBuilder`.
-    pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
-        &mut self.edit
-    }
-
     fn finish(mut self) -> SourceChange {
         self.commit();
         let mut change = mem::take(&mut self.change);
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
index c13493fd87..669dd9b217 100644
--- a/crates/assists/src/handlers/add_custom_impl.rs
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -4,15 +4,13 @@ use syntax::{
     ast::{self, make, AstNode},
     Direction, SmolStr,
     SyntaxKind::{IDENT, WHITESPACE},
-    TextSize,
+    TextRange, TextSize,
 };
 
 use crate::{
+    assist_config::SnippetCap,
     assist_context::{AssistBuilder, AssistContext, Assists},
-    utils::{
-        add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor,
-        DefaultMethods,
-    },
+    utils::mod_path_to_ast,
     AssistId, AssistKind,
 };
 
@@ -49,10 +47,11 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
         ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
     let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
 
-    let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
-    let insert_pos = annotated_name.syntax().parent()?.text_range().end();
+    let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
+    let annotated_name = annotated.syntax().text().to_string();
+    let insert_pos = annotated.syntax().parent()?.text_range().end();
 
-    let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
+    let current_module = ctx.sema.scope(annotated.syntax()).module()?;
     let current_crate = current_module.krate();
 
     let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
@@ -70,22 +69,21 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
         });
 
     let mut no_traits_found = true;
-    for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
-        add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?;
+    for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
+        add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
     }
     if no_traits_found {
-        add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?;
+        add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
     }
     Some(())
 }
 
 fn add_assist(
     acc: &mut Assists,
-    ctx: &AssistContext,
+    snippet_cap: Option<SnippetCap>,
     attr: &ast::Attr,
     trait_path: &ast::Path,
-    trait_: Option<hir::Trait>,
-    annotated_name: &ast::Name,
+    annotated_name: &str,
     insert_pos: TextSize,
 ) -> Option<()> {
     let target = attr.syntax().text_range();
@@ -94,62 +92,25 @@ fn add_assist(
     let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
 
     acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
-        let impl_def_with_items =
-            impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path);
         update_attribute(builder, &input, &trait_name, &attr);
-        match (ctx.config.snippet_cap, impl_def_with_items) {
-            (None, _) => builder.insert(
-                insert_pos,
-                format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
-            ),
-            (Some(cap), None) => builder.insert_snippet(
-                cap,
-                insert_pos,
-                format!("\n\nimpl {} for {} {{\n    $0\n}}", trait_path, annotated_name),
-            ),
-            (Some(cap), Some((impl_def, first_assoc_item))) => {
-                let mut cursor = Cursor::Before(first_assoc_item.syntax());
-                let placeholder;
-                if let ast::AssocItem::Fn(ref func) = first_assoc_item {
-                    if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
-                        if m.syntax().text() == "todo!()" {
-                            placeholder = m;
-                            cursor = Cursor::Replace(placeholder.syntax());
-                        }
-                    }
-                }
-
+        match snippet_cap {
+            Some(cap) => {
                 builder.insert_snippet(
                     cap,
                     insert_pos,
-                    format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)),
-                )
+                    format!("\n\nimpl {} for {} {{\n    $0\n}}", trait_path, annotated_name),
+                );
+            }
+            None => {
+                builder.insert(
+                    insert_pos,
+                    format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
+                );
             }
-        };
+        }
     })
 }
 
-fn impl_def_from_trait(
-    sema: &hir::Semantics<ide_db::RootDatabase>,
-    annotated_name: &ast::Name,
-    trait_: Option<hir::Trait>,
-    trait_path: &ast::Path,
-) -> Option<(ast::Impl, ast::AssocItem)> {
-    let trait_ = trait_?;
-    let target_scope = sema.scope(annotated_name.syntax());
-    let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No);
-    if trait_items.is_empty() {
-        return None;
-    }
-    let impl_def = make::impl_trait(
-        trait_path.clone(),
-        make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))),
-    );
-    let (impl_def, first_assoc_item) =
-        add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
-    Some((impl_def, first_assoc_item))
-}
-
 fn update_attribute(
     builder: &mut AssistBuilder,
     input: &ast::TokenTree,
@@ -172,14 +133,13 @@ fn update_attribute(
         let attr_range = attr.syntax().text_range();
         builder.delete(attr_range);
 
-        if let Some(line_break_range) = attr
+        let line_break_range = attr
             .syntax()
             .next_sibling_or_token()
             .filter(|t| t.kind() == WHITESPACE)
             .map(|t| t.text_range())
-        {
-            builder.delete(line_break_range);
-        }
+            .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
+        builder.delete(line_break_range);
     }
 }
 
@@ -190,17 +150,12 @@ mod tests {
     use super::*;
 
     #[test]
-    fn add_custom_impl_debug() {
+    fn add_custom_impl_qualified() {
         check_assist(
             add_custom_impl,
             "
 mod fmt {
-    pub struct Error;
-    pub type Result = Result<(), Error>;
-    pub struct Formatter<'a>;
-    pub trait Debug {
-        fn fmt(&self, f: &mut Formatter<'_>) -> Result;
-    }
+    pub trait Debug {}
 }
 
 #[derive(Debu<|>g)]
@@ -210,12 +165,7 @@ struct Foo {
 ",
             "
 mod fmt {
-    pub struct Error;
-    pub type Result = Result<(), Error>;
-    pub struct Formatter<'a>;
-    pub trait Debug {
-        fn fmt(&self, f: &mut Formatter<'_>) -> Result;
-    }
+    pub trait Debug {}
 }
 
 struct Foo {
@@ -223,58 +173,7 @@ struct Foo {
 }
 
 impl fmt::Debug for Foo {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        ${0:todo!()}
-    }
-}
-",
-        )
-    }
-    #[test]
-    fn add_custom_impl_all() {
-        check_assist(
-            add_custom_impl,
-            "
-mod foo {
-    pub trait Bar {
-        type Qux;
-        const Baz: usize = 42;
-        const Fez: usize;
-        fn foo();
-        fn bar() {}
-    }
-}
-
-#[derive(<|>Bar)]
-struct Foo {
-    bar: String,
-}
-",
-            "
-mod foo {
-    pub trait Bar {
-        type Qux;
-        const Baz: usize = 42;
-        const Fez: usize;
-        fn foo();
-        fn bar() {}
-    }
-}
-
-struct Foo {
-    bar: String,
-}
-
-impl foo::Bar for Foo {
-    $0type Qux;
-
-    const Baz: usize = 42;
-
-    const Fez: usize;
-
-    fn foo() {
-        todo!()
-    }
+    $0
 }
 ",
         )
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index bbb71e261f..b82fb30ad4 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -1,14 +1,27 @@
-use ide_db::traits::resolve_target_trait;
-use syntax::ast::{self, AstNode};
+use hir::HasSource;
+use ide_db::traits::{get_missing_assoc_items, resolve_target_trait};
+use syntax::{
+    ast::{
+        self,
+        edit::{self, AstNodeEdit, IndentLevel},
+        make, AstNode, NameOwner,
+    },
+    SmolStr,
+};
 
 use crate::{
     assist_context::{AssistContext, Assists},
-    utils::add_trait_assoc_items_to_impl,
-    utils::DefaultMethods,
-    utils::{filter_assoc_items, render_snippet, Cursor},
+    ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
+    utils::{render_snippet, Cursor},
     AssistId, AssistKind,
 };
 
+#[derive(PartialEq)]
+enum AddMissingImplMembersMode {
+    DefaultMethodsOnly,
+    NoDefaultMethods,
+}
+
 // Assist: add_impl_missing_members
 //
 // Adds scaffold for required impl members.
@@ -42,7 +55,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
     add_missing_impl_members_inner(
         acc,
         ctx,
-        DefaultMethods::No,
+        AddMissingImplMembersMode::NoDefaultMethods,
         "add_impl_missing_members",
         "Implement missing members",
     )
@@ -84,7 +97,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext
     add_missing_impl_members_inner(
         acc,
         ctx,
-        DefaultMethods::Only,
+        AddMissingImplMembersMode::DefaultMethodsOnly,
         "add_impl_default_members",
         "Implement default members",
     )
@@ -93,7 +106,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext
 fn add_missing_impl_members_inner(
     acc: &mut Assists,
     ctx: &AssistContext,
-    mode: DefaultMethods,
+    mode: AddMissingImplMembersMode,
     assist_id: &'static str,
     label: &'static str,
 ) -> Option<()> {
@@ -101,11 +114,32 @@ fn add_missing_impl_members_inner(
     let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
     let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
 
-    let missing_items = filter_assoc_items(
-        ctx.db(),
-        &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def),
-        mode,
-    );
+    let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
+        match item {
+            ast::AssocItem::Fn(def) => def.name(),
+            ast::AssocItem::TypeAlias(def) => def.name(),
+            ast::AssocItem::Const(def) => def.name(),
+            ast::AssocItem::MacroCall(_) => None,
+        }
+        .map(|it| it.text().clone())
+    };
+
+    let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
+        .iter()
+        .map(|i| match i {
+            hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
+            hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
+            hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
+        })
+        .filter(|t| def_name(&t).is_some())
+        .filter(|t| match t {
+            ast::AssocItem::Fn(def) => match mode {
+                AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
+                AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
+            },
+            _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
+        })
+        .collect::<Vec<_>>();
 
     if missing_items.is_empty() {
         return None;
@@ -113,9 +147,29 @@ fn add_missing_impl_members_inner(
 
     let target = impl_def.syntax().text_range();
     acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
+        let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
+
+        let n_existing_items = impl_item_list.assoc_items().count();
+        let source_scope = ctx.sema.scope_for_def(trait_);
         let target_scope = ctx.sema.scope(impl_def.syntax());
-        let (new_impl_def, first_new_item) =
-            add_trait_assoc_items_to_impl(&ctx.sema, missing_items, trait_, impl_def, target_scope);
+        let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
+            .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
+
+        let items = missing_items
+            .into_iter()
+            .map(|it| ast_transform::apply(&*ast_transform, it))
+            .map(|it| match it {
+                ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
+                ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
+                _ => it,
+            })
+            .map(|it| edit::remove_attrs_and_docs(&it));
+
+        let new_impl_item_list = impl_item_list.append_items(items);
+        let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
+        let first_new_item =
+            new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
+
         match ctx.config.snippet_cap {
             None => builder.replace(target, new_impl_def.to_string()),
             Some(cap) => {
@@ -139,6 +193,14 @@ fn add_missing_impl_members_inner(
     })
 }
 
+fn add_body(fn_def: ast::Fn) -> ast::Fn {
+    if fn_def.body().is_some() {
+        return fn_def;
+    }
+    let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
+    fn_def.with_body(body)
+}
+
 #[cfg(test)]
 mod tests {
     use crate::tests::{check_assist, check_assist_not_applicable};
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs
index 6671153825..1094ed3f3a 100644
--- a/crates/assists/src/handlers/convert_integer_literal.rs
+++ b/crates/assists/src/handlers/convert_integer_literal.rs
@@ -129,6 +129,34 @@ mod tests {
         );
     }
 
+    // Decimal numbers under 3 digits have a special case where they return early because we can't fit a
+    // other base's prefix, so we have a separate test for that.
+    #[test]
+    fn convert_small_decimal_integer() {
+        let before = "const _: i32 = 10<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b1010;",
+            "Convert 10 to 0b1010",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o12;",
+            "Convert 10 to 0o12",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xA;",
+            "Convert 10 to 0xA",
+        );
+    }
+
     #[test]
     fn convert_hexadecimal_integer() {
         let before = "const _: i32 = 0xFF<|>;";
@@ -208,7 +236,7 @@ mod tests {
     }
 
     #[test]
-    fn convert_integer_with_underscores() {
+    fn convert_decimal_integer_with_underscores() {
         let before = "const _: i32 = 1_00_0<|>;";
 
         check_assist_by_label(
@@ -234,7 +262,111 @@ mod tests {
     }
 
     #[test]
-    fn convert_integer_with_suffix() {
+    fn convert_small_decimal_integer_with_underscores() {
+        let before = "const _: i32 = 1_0<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b1010;",
+            "Convert 1_0 to 0b1010",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o12;",
+            "Convert 1_0 to 0o12",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xA;",
+            "Convert 1_0 to 0xA",
+        );
+    }
+
+    #[test]
+    fn convert_hexadecimal_integer_with_underscores() {
+        let before = "const _: i32 = 0x_F_F<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b11111111;",
+            "Convert 0x_F_F to 0b11111111",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o377;",
+            "Convert 0x_F_F to 0o377",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255;",
+            "Convert 0x_F_F to 255",
+        );
+    }
+
+    #[test]
+    fn convert_binary_integer_with_underscores() {
+        let before = "const _: i32 = 0b1111_1111<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o377;",
+            "Convert 0b1111_1111 to 0o377",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255;",
+            "Convert 0b1111_1111 to 255",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xFF;",
+            "Convert 0b1111_1111 to 0xFF",
+        );
+    }
+
+    #[test]
+    fn convert_octal_integer_with_underscores() {
+        let before = "const _: i32 = 0o3_77<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b11111111;",
+            "Convert 0o3_77 to 0b11111111",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255;",
+            "Convert 0o3_77 to 255",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xFF;",
+            "Convert 0o3_77 to 0xFF",
+        );
+    }
+
+    #[test]
+    fn convert_decimal_integer_with_suffix() {
         let before = "const _: i32 = 1000i32<|>;";
 
         check_assist_by_label(
@@ -259,6 +391,240 @@ mod tests {
         );
     }
 
+    #[test]
+    fn convert_small_decimal_integer_with_suffix() {
+        let before = "const _: i32 = 10i32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b1010i32;",
+            "Convert 10i32 to 0b1010i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o12i32;",
+            "Convert 10i32 to 0o12i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xAi32;",
+            "Convert 10i32 to 0xAi32",
+        );
+    }
+
+    #[test]
+    fn convert_hexadecimal_integer_with_suffix() {
+        let before = "const _: i32 = 0xFFi32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b11111111i32;",
+            "Convert 0xFFi32 to 0b11111111i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o377i32;",
+            "Convert 0xFFi32 to 0o377i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255i32;",
+            "Convert 0xFFi32 to 255i32",
+        );
+    }
+
+    #[test]
+    fn convert_binary_integer_with_suffix() {
+        let before = "const _: i32 = 0b11111111i32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o377i32;",
+            "Convert 0b11111111i32 to 0o377i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255i32;",
+            "Convert 0b11111111i32 to 255i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xFFi32;",
+            "Convert 0b11111111i32 to 0xFFi32",
+        );
+    }
+
+    #[test]
+    fn convert_octal_integer_with_suffix() {
+        let before = "const _: i32 = 0o377i32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b11111111i32;",
+            "Convert 0o377i32 to 0b11111111i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255i32;",
+            "Convert 0o377i32 to 255i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xFFi32;",
+            "Convert 0o377i32 to 0xFFi32",
+        );
+    }
+
+    #[test]
+    fn convert_decimal_integer_with_underscores_and_suffix() {
+        let before = "const _: i32 = 1_00_0i32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b1111101000i32;",
+            "Convert 1_00_0i32 to 0b1111101000i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o1750i32;",
+            "Convert 1_00_0i32 to 0o1750i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0x3E8i32;",
+            "Convert 1_00_0i32 to 0x3E8i32",
+        );
+    }
+
+    #[test]
+    fn convert_small_decimal_integer_with_underscores_and_suffix() {
+        let before = "const _: i32 = 1_0i32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b1010i32;",
+            "Convert 1_0i32 to 0b1010i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o12i32;",
+            "Convert 1_0i32 to 0o12i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xAi32;",
+            "Convert 1_0i32 to 0xAi32",
+        );
+    }
+
+    #[test]
+    fn convert_hexadecimal_integer_with_underscores_and_suffix() {
+        let before = "const _: i32 = 0x_F_Fi32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b11111111i32;",
+            "Convert 0x_F_Fi32 to 0b11111111i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o377i32;",
+            "Convert 0x_F_Fi32 to 0o377i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255i32;",
+            "Convert 0x_F_Fi32 to 255i32",
+        );
+    }
+
+    #[test]
+    fn convert_binary_integer_with_underscores_and_suffix() {
+        let before = "const _: i32 = 0b1111_1111i32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0o377i32;",
+            "Convert 0b1111_1111i32 to 0o377i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255i32;",
+            "Convert 0b1111_1111i32 to 255i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xFFi32;",
+            "Convert 0b1111_1111i32 to 0xFFi32",
+        );
+    }
+
+    #[test]
+    fn convert_octal_integer_with_underscores_and_suffix() {
+        let before = "const _: i32 = 0o3_77i32<|>;";
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0b11111111i32;",
+            "Convert 0o3_77i32 to 0b11111111i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 255i32;",
+            "Convert 0o3_77i32 to 255i32",
+        );
+
+        check_assist_by_label(
+            convert_integer_literal,
+            before,
+            "const _: i32 = 0xFFi32;",
+            "Convert 0o3_77i32 to 0xFFi32",
+        );
+    }
+
     #[test]
     fn convert_overflowing_literal() {
         let before = "const _: i32 =
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index 853266395d..f51a9a4ad1 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -5,13 +5,13 @@ use ide_db::{
     search::SearchScope,
 };
 use syntax::{
-    algo,
+    algo::SyntaxRewriter,
     ast::{self, make},
     AstNode, Direction, SyntaxNode, SyntaxToken, T,
 };
 
 use crate::{
-    assist_context::{AssistBuilder, AssistContext, Assists},
+    assist_context::{AssistContext, Assists},
     AssistId, AssistKind,
 };
 
@@ -61,7 +61,9 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti
         "Expand glob import",
         target.text_range(),
         |builder| {
-            replace_ast(builder, parent, mod_path, names_to_import);
+            let mut rewriter = SyntaxRewriter::default();
+            replace_ast(&mut rewriter, parent, mod_path, names_to_import);
+            builder.rewrite(rewriter);
         },
     )
 }
@@ -236,7 +238,7 @@ fn find_names_to_import(
 }
 
 fn replace_ast(
-    builder: &mut AssistBuilder,
+    rewriter: &mut SyntaxRewriter,
     parent: Either<ast::UseTree, ast::UseTreeList>,
     path: ast::Path,
     names_to_import: Vec<Name>,
@@ -264,32 +266,21 @@ fn replace_ast(
     match use_trees.as_slice() {
         [name] => {
             if let Some(end_path) = name.path() {
-                let replacement =
-                    make::use_tree(make::path_concat(path, end_path), None, None, false);
-
-                algo::diff(
-                    &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
-                    replacement.syntax(),
-                )
-                .into_text_edit(builder.text_edit_builder());
+                rewriter.replace_ast(
+                    &parent.left_or_else(|tl| tl.parent_use_tree()),
+                    &make::use_tree(make::path_concat(path, end_path), None, None, false),
+                );
             }
         }
-        names => {
-            let replacement = match parent {
-                Either::Left(_) => {
-                    make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false)
-                        .syntax()
-                        .clone()
-                }
-                Either::Right(_) => make::use_tree_list(names.to_owned()).syntax().clone(),
-            };
-
-            algo::diff(
-                &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
-                &replacement,
-            )
-            .into_text_edit(builder.text_edit_builder());
-        }
+        names => match &parent {
+            Either::Left(parent) => rewriter.replace_ast(
+                parent,
+                &make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false),
+            ),
+            Either::Right(parent) => {
+                rewriter.replace_ast(parent, &make::use_tree_list(names.to_owned()))
+            }
+        },
     };
 }
 
@@ -884,4 +875,33 @@ fn qux(baz: Baz) {}
     ",
         )
     }
+
+    #[test]
+    fn expanding_glob_import_single_nested_glob_only() {
+        check_assist(
+            expand_glob_import,
+            r"
+mod foo {
+    pub struct Bar;
+}
+
+use foo::{*<|>};
+
+struct Baz {
+    bar: Bar
+}
+",
+            r"
+mod foo {
+    pub struct Bar;
+}
+
+use foo::Bar;
+
+struct Baz {
+    bar: Bar
+}
+",
+        );
+    }
 }
diff --git a/crates/assists/src/handlers/reorder_fields.rs b/crates/assists/src/handlers/reorder_fields.rs
index 527f457a79..7c0f0f44e1 100644
--- a/crates/assists/src/handlers/reorder_fields.rs
+++ b/crates/assists/src/handlers/reorder_fields.rs
@@ -47,9 +47,11 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
         "Reorder record fields",
         target,
         |edit| {
+            let mut rewriter = algo::SyntaxRewriter::default();
             for (old, new) in fields.iter().zip(&sorted_fields) {
-                algo::diff(old, new).into_text_edit(edit.text_edit_builder());
+                rewriter.replace(old, new);
             }
+            edit.rewrite(rewriter);
         },
     )
 }
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 7071fe96b1..56f925ee69 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -4,22 +4,17 @@ pub(crate) mod import_assets;
 
 use std::ops;
 
-use hir::{Crate, Enum, HasSource, Module, ScopeDef, Semantics, Trait};
+use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait};
 use ide_db::RootDatabase;
 use itertools::Itertools;
 use syntax::{
-    ast::edit::AstNodeEdit,
-    ast::NameOwner,
-    ast::{self, edit, make, ArgListOwner},
+    ast::{self, make, ArgListOwner},
     AstNode, Direction,
     SyntaxKind::*,
     SyntaxNode, TextSize, T,
 };
 
-use crate::{
-    assist_config::SnippetCap,
-    ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
-};
+use crate::assist_config::SnippetCap;
 
 pub use insert_use::MergeBehaviour;
 pub(crate) use insert_use::{insert_use, ImportScope};
@@ -82,87 +77,6 @@ pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
     None
 }
 
-#[derive(Copy, Clone, PartialEq)]
-pub enum DefaultMethods {
-    Only,
-    No,
-}
-
-pub fn filter_assoc_items(
-    db: &RootDatabase,
-    items: &[hir::AssocItem],
-    default_methods: DefaultMethods,
-) -> Vec<ast::AssocItem> {
-    fn has_def_name(item: &ast::AssocItem) -> bool {
-        match item {
-            ast::AssocItem::Fn(def) => def.name(),
-            ast::AssocItem::TypeAlias(def) => def.name(),
-            ast::AssocItem::Const(def) => def.name(),
-            ast::AssocItem::MacroCall(_) => None,
-        }
-        .is_some()
-    };
-
-    items
-        .iter()
-        .map(|i| match i {
-            hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db).value),
-            hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db).value),
-            hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db).value),
-        })
-        .filter(has_def_name)
-        .filter(|it| match it {
-            ast::AssocItem::Fn(def) => matches!(
-                (default_methods, def.body()),
-                (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
-            ),
-            _ => default_methods == DefaultMethods::No,
-        })
-        .collect::<Vec<_>>()
-}
-
-pub fn add_trait_assoc_items_to_impl(
-    sema: &hir::Semantics<ide_db::RootDatabase>,
-    items: Vec<ast::AssocItem>,
-    trait_: hir::Trait,
-    impl_def: ast::Impl,
-    target_scope: hir::SemanticsScope,
-) -> (ast::Impl, ast::AssocItem) {
-    let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
-
-    let n_existing_items = impl_item_list.assoc_items().count();
-    let source_scope = sema.scope_for_def(trait_);
-    let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
-        .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
-
-    let items = items
-        .into_iter()
-        .map(|it| ast_transform::apply(&*ast_transform, it))
-        .map(|it| match it {
-            ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
-            ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
-            _ => it,
-        })
-        .map(|it| edit::remove_attrs_and_docs(&it));
-
-    let new_impl_item_list = impl_item_list.append_items(items);
-    let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
-    let first_new_item =
-        new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
-    return (new_impl_def, first_new_item);
-
-    fn add_body(fn_def: ast::Fn) -> ast::Fn {
-        match fn_def.body() {
-            Some(_) => fn_def,
-            None => {
-                let body =
-                    make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1));
-                fn_def.with_body(body)
-            }
-        }
-    }
-}
-
 #[derive(Clone, Copy, Debug)]
 pub(crate) enum Cursor<'a> {
     Replace(&'a SyntaxNode),
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 876659a2b7..b1578820fe 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -25,10 +25,6 @@ pub fn assoc_item_list() -> ast::AssocItemList {
     ast_from_text("impl C for D {};")
 }
 
-pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {
-    ast_from_text(&format!("impl {} for {} {{}}", trait_, ty))
-}
-
 pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
     ast_from_text(&format!("use {};", name_ref))
 }
diff --git a/editors/code/rust.tmGrammar.json b/editors/code/rust.tmGrammar.json
index b3a10795eb..77595aa006 100644
--- a/editors/code/rust.tmGrammar.json
+++ b/editors/code/rust.tmGrammar.json
@@ -50,7 +50,7 @@
         {
             "comment": "macro type metavariables",
             "name": "meta.macro.metavariable.type.rust",
-            "match": "(\\$)((crate)|([A-Z][A-Za-z0-9_]*))((:)(block|expr|ident|item|lifetime|literal|meta|path?|stmt|tt|ty|vis))?",
+            "match": "(\\$)((crate)|([A-Z][A-Za-z0-9_]*))((:)(block|expr|ident|item|lifetime|literal|meta|pat|path|stmt|tt|ty|vis))?",
             "captures": {
                 "1": {
                     "name": "keyword.operator.macro.dollar.rust"
@@ -77,7 +77,7 @@
         {
             "comment": "macro metavariables",
             "name": "meta.macro.metavariable.rust",
-            "match": "(\\$)([a-z][A-Za-z0-9_]*)((:)(block|expr|ident|item|lifetime|literal|meta|path?|stmt|tt|ty|vis))?",
+            "match": "(\\$)([a-z][A-Za-z0-9_]*)((:)(block|expr|ident|item|lifetime|literal|meta|pat|path|stmt|tt|ty|vis))?",
             "captures": {
                 "1": {
                     "name": "keyword.operator.macro.dollar.rust"
@@ -167,7 +167,7 @@
             "match": "(mod)\\s+((?:r#(?!crate|[Ss]elf|super))?[a-z][A-Za-z0-9_]*)",
             "captures": {
                 "1": {
-                    "name": "storage.type.rust"
+                    "name": "keyword.control.rust"
                 },
                 "2": {
                     "name": "entity.name.module.rust"
@@ -180,7 +180,7 @@
             "begin": "\\b(extern)\\s+(crate)",
             "beginCaptures": {
                 "1": {
-                    "name": "storage.type.rust"
+                    "name": "keyword.control.rust"
                 },
                 "2": {
                     "name": "keyword.other.crate.rust"
@@ -213,7 +213,7 @@
             "begin": "\\b(use)\\s",
             "beginCaptures": {
                 "1": {
-                    "name": "keyword.other.rust"
+                    "name": "keyword.control.rust"
                 }
             },
             "end": ";",
@@ -342,7 +342,7 @@
                     "match": "\\b(const)\\s+([A-Z][A-Za-z0-9_]*)\\b",
                     "captures": {
                         "1": {
-                            "name": "storage.type.rust"
+                            "name": "keyword.control.rust"
                         },
                         "2": {
                             "name": "constant.other.caps.rust"
@@ -450,7 +450,7 @@
                     "begin": "\\b(fn)\\s+((?:r#(?!crate|[Ss]elf|super))?[A-Za-z0-9_]+)((\\()|(<))",
                     "beginCaptures": {
                         "1": {
-                            "name": "keyword.other.fn.rust"
+                            "name": "keyword.control.fn.rust"
                         },
                         "2": {
                             "name": "entity.name.function.rust"
@@ -643,7 +643,7 @@
                 {
                     "comment": "control flow keywords",
                     "name": "keyword.control.rust",
-                    "match": "\\b(await|break|continue|do|else|for|if|loop|match|return|try|while|yield)\\b"
+                    "match": "\\b(async|await|break|continue|do|else|for|if|loop|match|move|return|try|where|while|yield)\\b"
                 },
                 {
                     "comment": "storage keywords",
@@ -658,7 +658,7 @@
                 {
                     "comment": "other keywords",
                     "name": "keyword.other.rust",
-                    "match": "\\b(as|async|become|box|dyn|move|final|impl|in|override|priv|pub|ref|typeof|union|unsafe|unsized|use|virtual|where)\\b"
+                    "match": "\\b(as|become|box|dyn|final|impl|in|override|priv|pub|ref|typeof|union|unsafe|unsized|use|virtual)\\b"
                 },
                 {
                     "comment": "fn",
@@ -675,6 +675,11 @@
                     "name": "storage.modifier.mut.rust",
                     "match": "\\bmut\\b"
                 },
+                {
+                    "comment": "math operators",
+                    "name": "keyword.operator.math.rust",
+                    "match": "(([+%]|(\\*(?!\\w)))(?!=))|(-(?!>))|(/(?!/))"
+                },
                 {
                     "comment": "logical operators",
                     "name": "keyword.operator.logical.rust",
@@ -688,7 +693,7 @@
                 {
                     "comment": "assignment operators",
                     "name": "keyword.operator.assignment.rust",
-                    "match": "(\\+=|-=|\\*=|/=|%=|\\^=|&=|\\|=|<<=|>>=)"
+                    "match": "(-=|\\*=|/=|%=|\\^=|&=|\\|=|<<=|>>=)"
                 },
                 {
                     "comment": "single equal",
@@ -700,11 +705,6 @@
                     "name": "keyword.operator.comparison.rust",
                     "match": "(=(=)?(?!>)|!=|<=|(?<!=)>=)"
                 },
-                {
-                    "comment": "math operators",
-                    "name": "keyword.operator.math.rust",
-                    "match": "(([+%]|(\\*(?!\\w)))(?!=))|(-(?!>))|(/(?!/))"
-                },
                 {
                     "comment": "less than, greater than (special case)",
                     "match": "(?:\\b|(?:(\\))|(\\])|(\\})))[ \\t]+([<>])[ \\t]+(?:\\b|(?:(\\()|(\\[)|(\\{)))",
@@ -1127,7 +1127,7 @@
                 {
                     "comment": "variables",
                     "name": "variable.other.rust",
-                    "match": "\\b(?<!(?<!\\.)\\.)(?:r#(?!(crate|[Ss]elf|super)))?[a-z0-9_]+\\b"
+                    "match": "\\b(?<!\\.)(?:r#(?!(crate|[Ss]elf|super)))?[a-z0-9_]+\\b"
                 }
             ]
         }


Test patch:
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 4c7db8405e..9de60c76c9 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -215,7 +215,6 @@ fn check_todo(path: &Path, text: &str) {
         "tests/cli.rs",
         // Some of our assists generate `todo!()`.
         "tests/generated.rs",
-        "handlers/add_custom_impl.rs",
         "handlers/add_missing_impl_members.rs",
         "handlers/add_turbo_fish.rs",
         "handlers/generate_function.rs",