Repository: rust-analyzer/rust-analyzer
Pull request #7800 (2021-02-27 20:23:19)

[WIP] 7708: Initial implementation of generate Default assist.
The Generate Default impl from new function.

Veykril: I think you didn't commit your latest changes

Patch:
diff --git a/crates/ide_assists/src/handlers/generate_default_from_new.rs b/crates/ide_assists/src/handlers/generate_default_from_new.rs
new file mode 100644
index 0000000000..fa12545792
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_default_from_new.rs
@@ -0,0 +1,374 @@
+use crate::{
+    assist_context::{AssistContext, Assists},
+    AssistId,
+};
+use ide_db::helpers::FamousDefs;
+use syntax::{
+    ast::{self, Impl, NameOwner},
+    AstNode,
+};
+use test_utils::mark;
+
+// Assist: generate_default_from_new
+//
+// Generates default implementation from new method.
+//
+// ```
+// struct Example { _inner: () }
+//
+// impl Example {
+//     pub fn n$0ew() -> Self {
+//         Self { _inner: () }
+//     }
+// }
+// ```
+// ->
+// ```
+// struct Example { _inner: () }
+//
+// impl Example {
+//     pub fn new() -> Self {
+//         Self { _inner: () }
+//     }
+// }
+//
+// impl Default for Example {
+//     fn default() -> Self {
+//         Self::new()
+//     }
+// }
+// ```
+pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let fn_node = ctx.find_node_at_offset::<ast::Fn>()?;
+    let fn_name = fn_node.name()?;
+
+    if fn_name.text() != "new" {
+        mark::hit!(other_function_than_new);
+        return None;
+    }
+
+    if fn_node.param_list()?.params().next().is_some() {
+        mark::hit!(new_function_with_parameters);
+        return None;
+    }
+
+    let impl_ = fn_node.syntax().ancestors().into_iter().find_map(ast::Impl::cast)?;
+    if is_default_implemented(ctx, &impl_) {
+        mark::hit!(default_block_is_already_present);
+        mark::hit!(struct_in_module_with_default);
+        return None;
+    }
+
+    let insert_location = impl_.syntax().text_range();
+
+    acc.add(
+        AssistId("generate_default_from_new", crate::AssistKind::Generate),
+        "Generate a Default impl from a new fn",
+        insert_location,
+        move |builder| {
+            let code = default_fn_node_for_new(impl_);
+            builder.insert(insert_location.end(), code);
+        },
+    )
+}
+
+fn default_fn_node_for_new(impl_: Impl) -> String {
+    format!(
+        "
+
+impl Default for {} {{
+    fn default() -> Self {{
+        Self::new()
+    }}
+}}",
+        impl_.self_ty().unwrap().syntax().text()
+    )
+}
+
+fn is_default_implemented(ctx: &AssistContext, impl_: &Impl) -> bool {
+    let db = ctx.sema.db;
+    let impl_ = ctx.sema.to_def(impl_);
+    let impl_def = match impl_ {
+        Some(value) => value,
+        None => return false,
+    };
+
+    let ty = impl_def.target_ty(db);
+    let krate = impl_def.module(db).krate();
+    let default = FamousDefs(&ctx.sema, Some(krate)).core_default_Default();
+    let default_trait = match default {
+        Some(value) => value,
+        None => return false,
+    };
+
+    ty.impls_trait(db, default_trait, &[])
+}
+
+#[cfg(test)]
+mod tests {
+    use ide_db::helpers::FamousDefs;
+
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn generate_default() {
+        check_pass(
+            r#"
+struct Example { _inner: () }
+
+impl Example {
+    pub fn ne$0w() -> Self {
+        Self { _inner: () }
+    }
+}
+
+fn main() {}
+"#,
+            r#"
+struct Example { _inner: () }
+
+impl Example {
+    pub fn new() -> Self {
+        Self { _inner: () }
+    }
+}
+
+impl Default for Example {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+fn main() {}
+"#,
+        );
+    }
+
+    #[test]
+    fn generate_default2() {
+        check_pass(
+            r#"
+struct Test { value: u32 }
+
+impl Test {
+    pub fn ne$0w() -> Self {
+        Self { value: 0 }
+    }
+}
+"#,
+            r#"
+struct Test { value: u32 }
+
+impl Test {
+    pub fn new() -> Self {
+        Self { value: 0 }
+    }
+}
+
+impl Default for Test {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn new_function_with_parameters() {
+        mark::check!(new_function_with_parameters);
+        check_not_applicable(
+            r#"
+struct Example { _inner: () }
+
+impl Example {
+    pub fn $0new(value: ()) -> Self {
+        Self { _inner: value }
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn other_function_than_new() {
+        mark::check!(other_function_than_new);
+        check_not_applicable(
+            r#"
+struct Example { _inner: () }
+
+impl Example {
+    pub fn a$0dd() -> Self {
+        Self { _inner: () }
+    }
+}
+
+"#,
+        );
+    }
+
+    #[test]
+    fn default_block_is_already_present() {
+        mark::check!(default_block_is_already_present);
+        check_not_applicable(
+            r#"
+struct Example { _inner: () }
+
+impl Example {
+    pub fn n$0ew() -> Self {
+        Self { _inner: () }
+    }
+}
+
+impl Default for Example {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn standalone_new_function() {
+        check_not_applicable(
+            r#"
+fn n$0ew() -> u32 {
+    0
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn multiple_struct_blocks() {
+        check_pass(
+            r#"
+struct Example { _inner: () }
+struct Test { value: u32 }
+
+impl Example {
+    pub fn new$0() -> Self {
+        Self { _inner: () }
+    }
+}
+"#,
+            r#"
+struct Example { _inner: () }
+struct Test { value: u32 }
+
+impl Example {
+    pub fn new() -> Self {
+        Self { _inner: () }
+    }
+}
+
+impl Default for Example {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn when_struct_is_after_impl() {
+        check_pass(
+            r#"
+impl Example {
+    pub fn $0new() -> Self {
+        Self { _inner: () }
+    }
+}
+
+struct Example { _inner: () }
+"#,
+            r#"
+impl Example {
+    pub fn new() -> Self {
+        Self { _inner: () }
+    }
+}
+
+impl Default for Example {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+struct Example { _inner: () }
+"#,
+        );
+    }
+
+    #[test]
+    fn struct_in_module() {
+        check_pass(
+            r#"
+mod test {
+    struct Example { _inner: () }
+
+    impl Example {
+        pub fn n$0ew() -> Self {
+            Self { _inner: () }
+        }
+    }
+}
+"#,
+            r#"
+mod test {
+    struct Example { _inner: () }
+
+    impl Example {
+        pub fn new() -> Self {
+            Self { _inner: () }
+        }
+    }
+
+impl Default for Example {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn struct_in_module_with_default() {
+        mark::check!(struct_in_module_with_default);
+        check_not_applicable(
+            r#"
+mod test {
+    struct Example { _inner: () }
+
+    impl Example {
+        pub fn n$0ew() -> Self {
+            Self { _inner: () }
+        }
+    }
+
+    impl Default for Example {
+        fn default() -> Self {
+            Self::new()
+        }
+    }
+}
+"#,
+        );
+    }
+
+    fn check_pass(before: &str, after: &str) {
+        let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
+        check_assist(generate_default_from_new, before, after);
+    }
+
+    fn check_not_applicable(before: &str) {
+        let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
+        check_assist_not_applicable(generate_default_from_new, before);
+    }
+}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 9c8148462c..ea62d5f5da 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -127,6 +127,7 @@ mod handlers {
     mod flip_comma;
     mod flip_trait_bound;
     mod generate_default_from_enum_variant;
+    mod generate_default_from_new;
     mod generate_derive;
     mod generate_enum_is_method;
     mod generate_enum_projection_method;
@@ -189,6 +190,7 @@ mod handlers {
             flip_comma::flip_comma,
             flip_trait_bound::flip_trait_bound,
             generate_default_from_enum_variant::generate_default_from_enum_variant,
+            generate_default_from_new::generate_default_from_new,
             generate_derive::generate_derive,
             generate_enum_is_method::generate_enum_is_method,
             generate_enum_projection_method::generate_enum_as_method,


Test patch:
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 4f007aa48b..304b5798f2 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -439,6 +439,37 @@ impl Default for Version {
     )
 }
 
+#[test]
+fn doctest_generate_default_from_new() {
+    check_doc_test(
+        "generate_default_from_new",
+        r#####"
+struct Example { _inner: () }
+
+impl Example {
+    pub fn n$0ew() -> Self {
+        Self { _inner: () }
+    }
+}
+"#####,
+        r#####"
+struct Example { _inner: () }
+
+impl Example {
+    pub fn new() -> Self {
+        Self { _inner: () }
+    }
+}
+
+impl Default for Example {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_generate_derive() {
     check_doc_test(