From 4b7ae9fedc2154a31f04400babb1dd0a93dffd16 Mon Sep 17 00:00:00 2001
From: Yoshua Wuyts <yoshuawuyts@gmail.com>
Date: Sun, 8 Aug 2021 15:24:04 +0200
Subject: [PATCH] generate Debug for enums

---
 .../replace_derive_with_manual_impl.rs        | 78 ++++++++++++++++++-
 crates/syntax/src/ast/make.rs                 |  3 +
 2 files changed, 80 insertions(+), 1 deletion(-)

diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
index e0319adccc6..8c3a5d25d9b 100644
--- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -181,7 +181,37 @@ fn impl_def_from_trait(
 fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn, annotated_name: &ast::Name) {
     match adt {
         ast::Adt::Union(_) => {} // `Debug` cannot be derived for unions, so no default impl can be provided.
-        ast::Adt::Enum(enum_) => {} // TODO
+        ast::Adt::Enum(enum_) => {
+            if let Some(list) = enum_.variant_list() {
+                let mut arms = vec![];
+                for variant in list.variants() {
+                    let name = variant.name().unwrap();
+
+                    // => Self::<Variant>
+                    let first = make::ext::ident_path("Self");
+                    let second = make::ext::ident_path(&format!("{}", name));
+                    let pat = make::path_pat(make::path_concat(first, second));
+
+                    // => write!(f, "<Variant>")
+                    let target = make::expr_path(make::ext::ident_path("f").into());
+                    let fmt_string = make::expr_literal(&(format!("\"{}\"", name))).into();
+                    let args = make::arg_list(vec![target, fmt_string]);
+                    let target = make::expr_path(make::ext::ident_path("write"));
+                    let expr = make::expr_macro_call(target, args);
+
+                    // => Self::<Variant> => write!(f, "<Variant>"),
+                    arms.push(make::match_arm(Some(pat.into()), None, expr.into()));
+                }
+
+                // => match self { ... }
+                let f_path = make::expr_path(make::ext::ident_path("self"));
+                let list = make::match_arm_list(arms);
+                let expr = make::expr_match(f_path, list);
+
+                let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
+                ted::replace(func.body().unwrap().syntax(), body.clone_for_update().syntax());
+            }
+        }
         ast::Adt::Struct(strukt) => match strukt.field_list() {
             Some(ast::FieldList::RecordFieldList(field_list)) => {
                 let name = format!("\"{}\"", annotated_name);
@@ -383,6 +413,52 @@ impl fmt::Debug for Foo {
         f.debug_struct("Foo").finish()
     }
 }
+"#,
+        )
+    }
+    #[test]
+    fn add_custom_impl_debug_enum() {
+        check_assist(
+            replace_derive_with_manual_impl,
+            r#"
+mod fmt {
+    pub struct Error;
+    pub type Result = Result<(), Error>;
+    pub struct Formatter<'a>;
+    pub trait Debug {
+        fn fmt(&self, f: &mut Formatter<'_>) -> Result;
+    }
+}
+
+#[derive(Debu$0g)]
+enum Foo {
+    Bar,
+    Baz,
+}
+"#,
+            r#"
+mod fmt {
+    pub struct Error;
+    pub type Result = Result<(), Error>;
+    pub struct Formatter<'a>;
+    pub trait Debug {
+        fn fmt(&self, f: &mut Formatter<'_>) -> Result;
+    }
+}
+
+enum Foo {
+    Bar,
+    Baz,
+}
+
+impl fmt::Debug for Foo {
+    $0fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+        Self::Bar => write!(f, "Bar"),
+        Self::Baz => write!(f, "Baz"),
+        }
+    }
+}
 "#,
         )
     }
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 71254f08543..87faac0aa32 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -311,6 +311,9 @@ pub fn expr_method_call(
 ) -> ast::Expr {
     expr_from_text(&format!("{}.{}{}", receiver, method, arg_list))
 }
+pub fn expr_macro_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr {
+    expr_from_text(&format!("{}!{}", f, arg_list))
+}
 pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
     expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) })
 }