Repository: gleam-lang/gleam (rust)

Issue:
Consider inserting `todo`s in incomplete `use` expressions when formatting.
A new user [recently confused](https://discord.com/channels/768594524158427167/1282783923100844064/1282783923100844064) (discord link) about some of the rules/differences between `let` expressions and `use` expressions. I think part of this confusion comes from being able to write a program like...
```gleam
fn example() {
  use x <- f
}
```
without the formatter inserting `todo`s when saving to indicate the expression is incomplete.

This behaviour already happens with `fn` expressions, and I think it would be both a nice QoL change and a better demonstration of the "use is just syntax sugar for callbacks" mantra that is often repeated.

Pull request description:
Formatter: add `todo` after incomplete `use` expression
This PR closes #3605 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45fa18555..795a6c2d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -238,6 +238,26 @@
 
 ### Formatter
 
+- The formatter now adds a `todo` after a `use` expression if it is the last
+  expression in a block. For example, the following code:
+
+  ```gleam
+  pub fn main() {
+    use user <- result.try(fetch_user())
+  }
+  ```
+
+  Is rewritten as:
+
+  ```gleam
+  pub fn main() {
+    use user <- result.try(fetch_user())
+    todo
+  }
+  ```
+
+  ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
+
 ### Language Server
 
 - The language server can now suggest a code action to assign an unused value to
diff --git a/compiler-core/src/format.rs b/compiler-core/src/format.rs
index a5364b17f..d5a8b6707 100644
--- a/compiler-core/src/format.rs
+++ b/compiler-core/src/format.rs
@@ -846,10 +846,17 @@ impl<'comments> Formatter<'comments> {
             if i != 0 && preceding_newline {
                 documents.push(lines(2));
             } else if i != 0 {
-                documents.push(lines(1));
+                documents.push(line());
             }
             previous_position = statement.location().end;
             documents.push(self.statement(statement).group());
+
+            // If the last statement is a use we make sure it's followed by a
+            // todo to make it explicit it has an unimplemented callback.
+            if statement.is_use() && i == count - 1 {
+                documents.push(line());
+                documents.push("todo".to_doc());
+            }
         }
         if count == 1 && statements.first().is_expression() {
             documents.to_doc()
@@ -1867,7 +1874,7 @@ impl<'comments> Formatter<'comments> {
         } else if space_before {
             lines(2).append(clause_doc)
         } else {
-            lines(1).append(clause_doc)
+            line().append(clause_doc)
         }
     }
 


Test patch:
diff --git a/compiler-core/src/format/tests/use_.rs b/compiler-core/src/format/tests/use_.rs
index e62849a0a..47dc42ede 100644
--- a/compiler-core/src/format/tests/use_.rs
+++ b/compiler-core/src/format/tests/use_.rs
@@ -1,10 +1,11 @@
-use crate::assert_format;
+use crate::{assert_format, assert_format_rewrite};
 
 #[test]
 fn use_1() {
     assert_format!(
         r#"pub fn main() {
   use <- benchmark("thingy")
+  todo
 }
 "#
     );
@@ -15,6 +16,7 @@ fn use_2() {
     assert_format!(
         r#"pub fn main() {
   use user <- login()
+  todo
 }
 "#
     );
@@ -25,6 +27,7 @@ fn use_3() {
     assert_format!(
         r#"pub fn main() {
   use one, two, three, four <- get_multiple_things()
+  todo
 }
 "#
     );
@@ -49,6 +52,7 @@ fn use_4() {
     twelve,
     thirteen
   <- get_multiple_things_with_a_longer_function
+  todo
 }
 "#
     );
@@ -73,6 +77,7 @@ fn use_5() {
     twelve,
     thirteen
   <- get_multiple_things_with_a_longer_function(a, b, c, d)
+  todo
 }
 "#
     );
@@ -106,6 +111,7 @@ fn use_6() {
     "seven",
     "eight",
   )
+  todo
 }
 "#
     );
@@ -130,6 +136,7 @@ fn use_pipe_everything() {
         r#"pub fn main() {
   {
     use <- a
+    todo
   }
   |> b
   c
@@ -152,6 +159,7 @@ fn long_right_hand_side_0_arguments() {
     "seven",
     "eight",
   )
+  todo
 }
 "#
     );
@@ -171,6 +179,7 @@ fn long_right_hand_side_1_argument() {
     "seven",
     "eight",
   )
+  todo
 }
 "#
     );
@@ -190,6 +199,7 @@ fn long_right_hand_side_2_arguments() {
     "seven",
     "eight",
   )
+  todo
 }
 "#
     );
@@ -203,6 +213,7 @@ fn arity_1_var_call() {
     file.read()
     |> promise.map(something),
   )
+  todo
 }
 "#
     );
@@ -216,6 +227,7 @@ fn arity_1_access_call() {
     file.read()
     |> promise.map(something),
   )
+  todo
 }
 "#
     );
@@ -322,6 +334,23 @@ fn comment() {
         r#"fn main() {
   // comment
   use x <- result.then(y)
+  todo
+}
+"#
+    );
+}
+
+// https://github.com/gleam-lang/gleam/issues/3605
+#[test]
+fn use_with_empty_callback_body_is_rewritten_to_have_a_todo() {
+    assert_format_rewrite!(
+        r#"fn main() {
+  use wibble, wobble <- woo
+}
+"#,
+        r#"fn main() {
+  use wibble, wobble <- woo
+  todo
 }
 "#
     );