Repository: gleam-lang/gleam (rust)

Issue:
Multiline `use` desugar adds invalid extra comma
Desugaring a multiline `use` such as this:

```gleam
import gleam/bool

pub fn main() {
  use <- bool.guard(
    "                                                 " == "",
    0,
  )

  1
}
```

Results in code containing an extra and invalid comma just before the new anonymous function:

```gleam
pub fn main() {
  bool.guard(
    "                                                 " == "",
    0,
  , fn() {

    1
  })
}
```

Gleam version: 1.7.0

Pull request description:
Fix "convert from use action" for expressions with trailing commas
This PR fixes #4149, now a comma is added before the callback only if needed.

**Note**
As you can see from the snapshots the code is not very nicely formatted, since that's a job for the formatter I didn't think it was worth the effort complicating the code further; but let me know if you think it should try and output nicer code for those multiline use expressions!

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 075f93343..940bf7f63 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,3 +11,7 @@
 ### Formatter
 
 ### Bug fixes
+
+- Fixed a bug where the "convert from use" code action would generate invalid
+  code for use expressions ending with a trailing comma.
+  ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
diff --git a/compiler-core/src/language_server/code_action.rs b/compiler-core/src/language_server/code_action.rs
index c09f5e58a..15923c22c 100644
--- a/compiler-core/src/language_server/code_action.rs
+++ b/compiler-core/src/language_server/code_action.rs
@@ -2239,15 +2239,16 @@ impl<'a> DesugarUse<'a> {
             EcoString::from("")
         };
 
-        // The use callback is not necessarily the last argumen. If you have the
-        // following function: `wibble(a a, b b) { todo }`
+        // The use callback is not necessarily the last argument. If you have
+        // the following function: `wibble(a a, b b) { todo }`
         // And use it like this: `use <- wibble(b: 1)`, the first argument `a`
         // is going to be the use callback, not the last one!
+        let use_callback = args.iter().find(|arg| arg.is_use_implicit_callback());
         let Some(CallArg {
             implicit: Some(ImplicitCallArgOrigin::Use),
             value: TypedExpr::Fn { body, type_, .. },
             ..
-        }) = args.iter().find(|arg| arg.is_use_implicit_callback())
+        }) = use_callback
         else {
             return vec![];
         };
@@ -2273,13 +2274,38 @@ impl<'a> DesugarUse<'a> {
         });
 
         let use_line_end = use_.right_hand_side_location.end;
-        let use_rhs_function_has_some_explicit_args = args.len() > 1;
+        let use_rhs_function_has_some_explicit_args = args
+            .iter()
+            .filter(|arg| !arg.is_use_implicit_callback())
+            .peekable()
+            .peek()
+            .is_some();
+
         let use_rhs_function_ends_with_closed_parentheses = self
             .module
             .code
             .get(use_line_end as usize - 1..use_line_end as usize)
             == Some(")");
 
+        let last_explicit_arg = args.iter().filter(|arg| !arg.is_implicit()).last();
+        let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end);
+
+        // This is the piece of code between the end of the last argument and
+        // the end of the use_expression:
+        //
+        //   use <- wibble(a, b,    )
+        //                     ^^^^^ This piece right here, from `,` included
+        //                           up to `)` excluded.
+        //
+        let text_after_last_argument = self
+            .module
+            .code
+            .get(last_arg_end as usize..use_line_end as usize - 1);
+        let use_rhs_has_comma_after_last_argument =
+            text_after_last_argument.is_some_and(|code| code.contains(','));
+        let needs_space_before_callback =
+            text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' '));
+
         if use_rhs_function_ends_with_closed_parentheses {
             // If the function on the right hand side of use ends with a closed
             // parentheses then we have to remove it and add it later at the end
@@ -2289,6 +2315,7 @@ impl<'a> DesugarUse<'a> {
             //                   ^ To add the fn() we need to first remove this
             //
             // So here we write over the last closed parentheses to remove it.
+            let callback_start = format!("{callback_label}fn({assignments}) {{");
             self.edits.replace(
                 SrcSpan {
                     start: use_line_end - 1,
@@ -2297,10 +2324,13 @@ impl<'a> DesugarUse<'a> {
                 // If the function on the rhs of use has other orguments besides
                 // the implicit fn expression then we need to put a comma after
                 // the last argument.
-                if use_rhs_function_has_some_explicit_args {
-                    format!(", {callback_label}fn({assignments}) {{")
+                if use_rhs_function_has_some_explicit_args && !use_rhs_has_comma_after_last_argument
+                {
+                    format!(", {callback_start}")
+                } else if needs_space_before_callback {
+                    format!(" {callback_start}")
                 } else {
-                    format!("{callback_label}fn({assignments}) {{")
+                    callback_start.to_string()
                 },
             )
         } else {


Test patch:
diff --git a/compiler-core/src/language_server/tests/action.rs b/compiler-core/src/language_server/tests/action.rs
index 6371e45c0..6bb019bc0 100644
--- a/compiler-core/src/language_server/tests/action.rs
+++ b/compiler-core/src/language_server/tests/action.rs
@@ -66,7 +66,7 @@ const ASSIGN_UNUSED_RESULT: &str = "Assign unused Result value to `_`";
 const ADD_MISSING_PATTERNS: &str = "Add missing patterns";
 const ADD_ANNOTATION: &str = "Add type annotation";
 const ADD_ANNOTATIONS: &str = "Add type annotations";
-const DESUGAR_USE_EXPRESSION: &str = "Convert from `use`";
+const CONVERT_FROM_USE: &str = "Convert from `use`";
 const CONVERT_TO_USE: &str = "Convert to `use`";
 const EXTRACT_VARIABLE: &str = "Extract variable";
 const EXPAND_FUNCTION_CAPTURE: &str = "Expand function capture";
@@ -3239,7 +3239,7 @@ pub fn main() {
 */
 
 #[test]
-fn desugar_use_expression_with_no_parens() {
+fn convert_from_use_expression_with_no_parens() {
     let src = r#"
 pub fn main() {
   use <- wibble
@@ -3252,14 +3252,14 @@ fn wibble(f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("use").select_until(find_position_of("todo")),
     );
 }
 
 #[test]
-fn desugar_use_expression_with_empty_parens() {
+fn convert_from_use_expression_with_empty_parens() {
     let src = r#"
 pub fn main() {
   use <- wibble()
@@ -3272,14 +3272,14 @@ fn wibble(f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("use").to_selection(),
     );
 }
 
 #[test]
-fn desugar_use_expression_with_parens_and_other_args() {
+fn convert_from_use_expression_with_parens_and_other_args() {
     let src = r#"
 pub fn main() {
   use <- wibble(1, 2)
@@ -3292,14 +3292,14 @@ fn wibble(n, m, f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("wibble").select_until(find_position_of("1")),
     );
 }
 
 #[test]
-fn desugar_use_expression_with_single_pattern() {
+fn convert_from_use_expression_with_single_pattern() {
     let src = r#"
 pub fn main() {
   use a <- wibble(1, 2)
@@ -3312,14 +3312,14 @@ fn wibble(n, m, f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("todo").to_selection(),
     );
 }
 
 #[test]
-fn desugar_use_expression_with_multiple_patterns() {
+fn convert_from_use_expression_with_multiple_patterns() {
     let src = r#"
 pub fn main() {
   use a, b <- wibble(1, 2)
@@ -3332,7 +3332,7 @@ fn wibble(n, m, f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("todo").nth_occurrence(2).to_selection(),
     );
@@ -3352,7 +3352,7 @@ fn wibble(n, m, f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("todo").under_last_char().to_selection(),
     );
@@ -3372,14 +3372,14 @@ fn wibble(n, m, f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("<-").select_until(find_position_of("wibble")),
     );
 }
 
 #[test]
-fn desugar_use_expression_with_type_annotations() {
+fn convert_from_use_expression_with_type_annotations() {
     let src = r#"
 pub fn main() {
   use a: Int, b: Int <- wibble(1, 2)
@@ -3391,14 +3391,14 @@ fn wibble(n, m, f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("<-").select_until(find_position_of("wibble")),
     );
 }
 
 #[test]
-fn desugar_use_expression_doesnt_work_with_complex_patterns() {
+fn convert_from_use_expression_doesnt_work_with_complex_patterns() {
     let src = r#"
 pub fn main() {
   use #(a, b), 1 <- wibble(1, 2)
@@ -3410,14 +3410,14 @@ fn wibble(n, m, f) {
 }
 "#;
     assert_no_code_actions!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("<-").select_until(find_position_of("wibble")),
     );
 }
 
 #[test]
-fn desugar_use_with_labels() {
+fn convert_from_use_with_labels() {
     let src = r#"
 pub fn main() {
   use a <- wibble(one: 1, two: 2)
@@ -3429,14 +3429,14 @@ fn wibble(one _, two _, three f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("todo").to_selection(),
     );
 }
 
 #[test]
-fn desugar_use_with_labels_2() {
+fn convert_from_use_with_labels_2() {
     let src = r#"
 pub fn main() {
   use a <- wibble(1, two: 2)
@@ -3448,14 +3448,14 @@ fn wibble(one _, two _, three f) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("todo").to_selection(),
     );
 }
 
 #[test]
-fn desugar_use_with_labels_3() {
+fn convert_from_use_with_labels_3() {
     let src = r#"
 pub fn main() {
   use a <- wibble(1, three: 3)
@@ -3467,14 +3467,14 @@ fn wibble(one _, two f, three _) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("todo").to_selection(),
     );
 }
 
 #[test]
-fn desugar_use_with_labels_4() {
+fn convert_from_use_with_labels_4() {
     let src = r#"
 pub fn main() {
   use a <- wibble(two: 2, three: 3)
@@ -3486,12 +3486,98 @@ fn wibble(one f, two _, three _) {
 }
 "#;
     assert_code_action!(
-        DESUGAR_USE_EXPRESSION,
+        CONVERT_FROM_USE,
         TestProject::for_source(src),
         find_position_of("todo").to_selection(),
     );
 }
 
+#[test]
+// https://github.com/gleam-lang/gleam/issues/4149
+fn convert_from_use_with_trailing_comma() {
+    let src = r#"
+pub fn main() {
+  use a, b <- wibble(1, 2,)
+  todo
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
+"#;
+    assert_code_action!(
+        CONVERT_FROM_USE,
+        TestProject::for_source(src),
+        find_position_of("<-").select_until(find_position_of("wibble")),
+    );
+}
+
+#[test]
+fn convert_from_use_with_trailing_comma_2() {
+    let src = r#"
+pub fn main() {
+  use a, b <- wibble(
+    1,
+    2,
+  )
+  todo
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
+"#;
+    assert_code_action!(
+        CONVERT_FROM_USE,
+        TestProject::for_source(src),
+        find_position_of("<-").select_until(find_position_of("wibble")),
+    );
+}
+
+#[test]
+fn convert_from_use_with_trailing_comma_and_label() {
+    let src = r#"
+pub fn main() {
+  use a, b <- wibble(
+    1,
+    wibble: 2,
+  )
+  todo
+}
+
+fn wibble(n, wibble m, wobble f) {
+    f(todo, todo)
+}
+"#;
+    assert_code_action!(
+        CONVERT_FROM_USE,
+        TestProject::for_source(src),
+        find_position_of("<-").select_until(find_position_of("wibble")),
+    );
+}
+
+#[test]
+fn convert_from_use_multiline_with_no_trailing_comma() {
+    let src = r#"
+pub fn main() {
+  use a, b <- wibble(
+    1,
+    2
+  )
+  todo
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
+"#;
+    assert_code_action!(
+        CONVERT_FROM_USE,
+        TestProject::for_source(src),
+        find_position_of("<-").select_until(find_position_of("wibble")),
+    );
+}
+
 #[test]
 fn turn_call_into_use_with_single_line_body() {
     let src = r#"
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_empty_parens.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_empty_parens.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_empty_parens.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_empty_parens.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_multiple_patterns.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_multiple_patterns.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_multiple_patterns.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_multiple_patterns.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_no_parens.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_no_parens.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_no_parens.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_no_parens.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_parens_and_other_args.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_parens_and_other_args.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_parens_and_other_args.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_parens_and_other_args.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_single_pattern.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_single_pattern.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_single_pattern.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_single_pattern.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_type_annotations.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_type_annotations.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_expression_with_type_annotations.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_expression_with_type_annotations.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_multiline_with_no_trailing_comma.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_multiline_with_no_trailing_comma.snap
new file mode 100644
index 000000000..64f533754
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_multiline_with_no_trailing_comma.snap
@@ -0,0 +1,34 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  use a, b <- wibble(\n    1,\n    2\n  )\n  todo\n}\n\nfn wibble(n, m, f) {\n    f(todo, todo)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  use a, b <- wibble(
+           ▔▔▔↑      
+    1,
+    2
+  )
+  todo
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  wibble(
+    1,
+    2
+  , fn(a, b) {
+    todo
+  })
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_with_labels.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_labels.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_with_labels.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_labels.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_with_labels_2.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_labels_2.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_with_labels_2.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_labels_2.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_with_labels_3.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_labels_3.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_with_labels_3.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_labels_3.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_with_labels_4.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_labels_4.snap
similarity index 100%
rename from compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__desugar_use_with_labels_4.snap
rename to compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_labels_4.snap
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma.snap
new file mode 100644
index 000000000..aa657bafc
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma.snap
@@ -0,0 +1,28 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  use a, b <- wibble(1, 2,)\n  todo\n}\n\nfn wibble(n, m, f) {\n    f(todo, todo)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  use a, b <- wibble(1, 2,)
+           ▔▔▔↑            
+  todo
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  wibble(1, 2, fn(a, b) {
+    todo
+  })
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma_2.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma_2.snap
new file mode 100644
index 000000000..2fb80506b
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma_2.snap
@@ -0,0 +1,34 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  use a, b <- wibble(\n    1,\n    2,\n  )\n  todo\n}\n\nfn wibble(n, m, f) {\n    f(todo, todo)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  use a, b <- wibble(
+           ▔▔▔↑      
+    1,
+    2,
+  )
+  todo
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  wibble(
+    1,
+    2,
+  fn(a, b) {
+    todo
+  })
+}
+
+fn wibble(n, m, f) {
+    f(todo, todo)
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma_and_label.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma_and_label.snap
new file mode 100644
index 000000000..5f139162e
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_from_use_with_trailing_comma_and_label.snap
@@ -0,0 +1,34 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  use a, b <- wibble(\n    1,\n    wibble: 2,\n  )\n  todo\n}\n\nfn wibble(n, wibble m, wobble f) {\n    f(todo, todo)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  use a, b <- wibble(
+           ▔▔▔↑      
+    1,
+    wibble: 2,
+  )
+  todo
+}
+
+fn wibble(n, wibble m, wobble f) {
+    f(todo, todo)
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  wibble(
+    1,
+    wibble: 2,
+  wobble: fn(a, b) {
+    todo
+  })
+}
+
+fn wibble(n, wibble m, wobble f) {
+    f(todo, todo)
+}