Repository: gleam-lang/gleam (rust)

Issue:
"Convert to pipe" code action interacts weirdly with `use`
The new "Convert to pipe" code action doesn't properly take into account `use` expressions. For example, given this code:
```gleam
import gleam/result

pub fn main() {
  use x <- result.try(todo)
  Ok(x + 1)
}
```

The Language Server offers a code action to transform it into this:

```gleam
import gleam/result

pub fn main() {
  Ok(x + 1) |> use x <- result.try(todo)
  _
}
```

Which is not how `use` works.

Pull request:
fix bug with convert to pipe used on a use function
This PR closes #4268 

Patch:
diff --git a/README.md b/README.md
index accd7be31..6dfec67a2 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,6 @@ Thank you to our sponsors! Gleam would not be possible without you.
 
 <!-- Below this line this file is autogenerated -->
 <p align="center">
-  <a href="https://github.com/00bpa">00bpa</a> -
   <a href="https://github.com/agundy">Aaron Gunderson</a> -
   <a href="https://github.com/zeroows">Abdulrhman Alkhodiry</a> -
   <a href="https://github.com/abeljim">Abel Jimenez</a> -
@@ -49,7 +48,6 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/anthony-khong">Anthony Khong</a> -
   <a href="https://github.com/Illbjorn">Anthony Maxwell</a> -
   <a href="https://github.com/amscotti">Anthony Scotti</a> -
-  <a href="https://github.com/vereym">Antoni</a> -
   <a href="https://github.com/aweagel">Arthur Weagel</a> -
   <a href="https://github.com/aryairani">Arya Irani</a> -
   <a href="https://github.com/azureflash">Azure Flash</a> -
@@ -62,6 +60,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/bcpeinhardt">Benjamin Peinhardt</a> -
   <a href="https://github.com/bgwdotdev">bgw</a> -
   <a href="https://github.com/bjartelund">Bjarte Aarmo Lund</a> -
+  <a href="https://github.com/00bpa">Bjoern Paschen</a> -
   <a href="https://github.com/bmehder">Brad Mehder</a> -
   <a href="https://github.com/brendisurfs">Brendan P. </a> -
   <a href="https://github.com/brettkolodny">brettkolodny</a> -
@@ -77,7 +76,6 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/chadselph">Chad Selph</a> -
   <a href="https://github.com/ctdio">Charlie Duong</a> -
   <a href="https://github.com/charlie-n01r">Charlie Govea</a> -
-  <a href="https://github.com/chazwatkins">Chaz Watkins</a> -
   <a href="https://github.com/choonkeat">Chew Choon Keat</a> -
   <a href="https://github.com/ceedon">Chris Donnelly</a> -
   <a href="https://github.com/Morzaram">Chris King</a> -
@@ -151,6 +149,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/gahjelle">Geir Arne Hjelle</a> -
   <a href="https://github.com/brasilikum">Georg Hartmann</a> -
   <a href="https://github.com/george-grec">George</a> -
+  <a href="https://github.com/gmartsenkov">Georgi Martsenkov</a> -
   <a href="https://github.com/ggobbe">ggobbe</a> -
   <a href="https://github.com/giacomocavalieri">Giacomo Cavalieri</a> -
   <a href="https://github.com/giovannibonetti">Giovanni Kock Bonetti</a> -
@@ -169,9 +168,9 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/hibachrach">Hazel Bachrach</a> -
   <a href="https://github.com/hdahlheim">Henning Dahlheim</a> -
   <a href="https://github.com/tudborg">Henrik Tudborg</a> -
-  <a href="https://github.com/h14h">Henry Firth</a> -
   <a href="https://github.com/henrysdev">Henry Warren</a> -
   <a href="https://github.com/losfair">Heyang Zhou</a> -
+  <a href="https://github.com/hubertmalkowski">Hubert Małkowski</a> -
   <a href="https://github.com/human154">human154</a> -
   <a href="https://github.com/hpiaia">Humberto Piaia</a> -
   <a href="https://github.com/iainh">Iain H</a> -
@@ -180,6 +179,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/igordsm">Igor Montagner</a> -
   <a href="https://github.com/irumiha">Igor Rumiha</a> -
   <a href="https://github.com/nilliax">ILLIA NEGOVORA</a> -
+  <a href="https://github.com/imnebbish">Imnebbish</a> -
   <a href="https://github.com/intarga">Ingrid</a> -
   <a href="https://github.com/inoas">inoas</a> -
   <a href="https://github.com/graphiteisaac">Isaac</a> -
@@ -190,6 +190,8 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/ivarvong">Ivar Vong</a> -
   <a href="https://github.com/jacobdalamb">Jacob Lamb</a> -
   <a href="https://github.com/jakecleary">Jake Cleary</a> -
+  <a href="https://github.com/jzwood">Jake Wood</a> -
+  <a href="https://github.com/jakob753951">Jakob Ladegaard Møller</a> -
   <a href="https://github.com/jamesbirtles">James Birtles</a> -
   <a href="https://github.com/jamesmacaulay">James MacAulay</a> -
   <a href="https://github.com/janpieper">Jan Pieper</a> -
@@ -212,6 +214,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/jooaf">jooaf</a> -
   <a href="https://github.com/joseph-lozano">Joseph Lozano</a> -
   <a href="https://github.com/joshocalico">Joshua Steele</a> -
+  <a href="https://github.com/Nineluj">Julian Hirn</a> -
   <a href="https://liberapay.com/d2quadra/">Julian Lukwata</a> -
   <a href="https://github.com/schurhammer">Julian Schurhammer</a> -
   <a href="https://github.com/justinlubin">Justin Lubin</a> -
@@ -229,6 +232,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/LeartS">Leonardo Donelli</a> -
   <a href="https://github.com/defp">lidashuang</a> -
   <a href="https://github.com/LilyRose2798">Lily Rose</a> -
+  <a href="https://github.com/nnuuvv">liv</a> -
   <a href="https://github.com/wowi42">Loïc Tosser</a> -
   <a href="https://github.com/lucaspellegrinelli">Lucas Pellegrinelli</a> -
   <a href="https://github.com/lbjarre">Lukas Bjarre</a> -
@@ -248,7 +252,6 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/poelstra">Martin Poelstra</a> -
   <a href="https://github.com/rechsteiner">Martin Rechsteiner </a> -
   <a href="https://github.com/martonkaufmann">martonkaufmann</a> -
-  <a href="https://github.com/klemola">Matias Klemola</a> -
   <a href="https://github.com/han-tyumi">Matt Champagne</a> -
   <a href="https://github.com/mhheise">Matt Heise</a> -
   <a href="https://github.com/m">Matt Mullenweg</a> -
@@ -288,7 +291,6 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/oceanlewis">Ocean</a> -
   <a href="https://github.com/osebelin">Olaf Sebelin</a> -
   <a href="https://github.com/OldhamMade">OldhamMade</a> -
-  <a href="https://github.com/spaghettiguru">Oleg Yuzvik</a> -
   <a href="https://github.com/CanadaHonk">Oliver Medhurst</a> -
   <a href="https://github.com/otosky">Oliver Tosky</a> -
   <a href="https://github.com/optizio">optizio</a> -
@@ -302,7 +304,6 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/pvsr">Peter Rice</a> -
   <a href="https://github.com/philpax">Philpax</a> -
   <a href="https://github.com/pierrot-lc">Pierrot</a> -
-  <a href="https://github.com/sz-piotr">Piotr Szlachciak</a> -
   <a href="https://github.com/qdentity">Qdentity</a> -
   <a href="https://github.com/raquentin">Race Williams</a> -
   <a href="https://github.com/stoft">Rasmus</a> -
@@ -331,6 +332,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/metruzanca">Sam Zanca</a> -
   <a href="https://github.com/soulsam480">sambit</a> -
   <a href="https://github.com/bkspace">Sammy Isseyegh</a> -
+  <a href="https://github.com/mrgleam">Santi Lertsumran</a> -
   <a href="https://github.com/castletaste">Savva</a> -
   <a href="https://github.com/sasa1977">Saša Jurić</a> -
   <a href="https://github.com/scotttrinh">Scott Trinh</a> -
@@ -367,7 +369,6 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/TristanCacqueray">Tristan de Cacqueray</a> -
   <a href="https://github.com/tsloughter">Tristan Sloughter</a> -
   <a href="https://github.com/tymak">tymak</a> -
-  <a href="https://github.com/UniH">Uni Huang</a> -
   <a href="https://github.com/upsidedownsweetfood">upsidedowncake</a> -
   <a href="https://github.com/vvzen">Valerio Viperino</a> -
   <a href="https://github.com/sandsower">Vic Valenzuela</a> -
@@ -382,8 +383,9 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/HymanZHAN">Xucong Zhan</a> -
   <a href="https://github.com/yamen">Yamen Sader</a> -
   <a href="https://github.com/Yasuo-Higano">Yasuo Higano</a> -
-  <a href="https://github.com/joshi-monster">yoshi~ </a> -
+  <a href="https://github.com/yoshi-monster">yoshi~ </a> -
   <a href="https://github.com/gasparinzsombor">Zsombor Gasparin</a> -
+  <a href="https://github.com/zwubs">ZWubs</a> -
   <a href="https://liberapay.com/~1814730/">~1814730</a> -
   <a href="https://liberapay.com/~1847917/">~1847917</a> -
   <a href="https://liberapay.com/~1867501/">~1867501</a> -
diff --git a/compiler-core/src/language_server/code_action.rs b/compiler-core/src/language_server/code_action.rs
index 9826c2fd4..714661330 100644
--- a/compiler-core/src/language_server/code_action.rs
+++ b/compiler-core/src/language_server/code_action.rs
@@ -4728,6 +4728,10 @@ pub struct ConvertToPipe<'a> {
     params: &'a CodeActionParams,
     edits: TextEdits<'a>,
     argument_to_pipe: Option<ConvertToPipeArg<'a>>,
+    /// this will be true if we're visiting the call on the right hand side of a
+    /// use expression. So we can skip it and not try to turn it into a
+    /// function.
+    visiting_use_call: bool,
 }
 
 /// Holds all the data needed by the "convert to pipe" code action to properly
@@ -4768,6 +4772,7 @@ impl<'a> ConvertToPipe<'a> {
             module,
             params,
             edits: TextEdits::new(line_numbers),
+            visiting_use_call: false,
             argument_to_pipe: None,
         }
     }
@@ -4846,6 +4851,16 @@ impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
         fun: &'ast TypedExpr,
         args: &'ast [TypedCallArg],
     ) {
+        // If we're visiting the typed function produced by typing a use, we
+        // skip the thing itself and only visit its arguments and called
+        // function, that is the body of the use.
+        if self.visiting_use_call {
+            self.visiting_use_call = false;
+            ast::visit::visit_typed_expr(self, fun);
+            args.iter().for_each(|arg| visit_typed_call_arg(self, arg));
+            return;
+        }
+
         // We only visit a call if the cursor is somewhere within its location,
         // otherwise we skip it entirely.
         let call_range = self.edits.src_span_to_lsp_range(*location);
@@ -4905,4 +4920,9 @@ impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
         // visit just that one and skip all the others.
         ast::visit::visit_typed_pipeline_assignment(self, first_value);
     }
+
+    fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
+        self.visiting_use_call = true;
+        ast::visit::visit_typed_use(self, use_);
+    }
 }
diff --git a/compiler-core/src/parse.rs b/compiler-core/src/parse.rs
index f99aeb067..aaab8fe55 100644
--- a/compiler-core/src/parse.rs
+++ b/compiler-core/src/parse.rs
@@ -612,7 +612,7 @@ where
                 }
                 if tail.is_some()
                     && elements.is_empty()
-                    && elements_after_tail.as_ref().map_or(true, |e| e.is_empty())
+                    && elements_after_tail.as_ref().is_none_or(|e| e.is_empty())
                 {
                     return parse_error(
                         ParseErrorType::ListSpreadWithoutElements,


Test patch:
diff --git a/compiler-core/src/language_server/tests/action.rs b/compiler-core/src/language_server/tests/action.rs
index 6dd598895..c5a10d9ff 100644
--- a/compiler-core/src/language_server/tests/action.rs
+++ b/compiler-core/src/language_server/tests/action.rs
@@ -5564,6 +5564,48 @@ pub fn main() {
     );
 }
 
+#[test]
+fn convert_to_pipe_does_not_work_on_function_on_the_right_hand_side_of_use() {
+    assert_no_code_actions!(
+        CONVERT_TO_PIPE,
+        "
+pub fn main() {
+  use <- wibble(wobble)
+  todo
+}
+",
+        find_position_of("wibble").to_selection()
+    );
+}
+
+#[test]
+fn convert_to_pipe_does_not_work_on_function_on_the_right_hand_side_of_use_2() {
+    assert_no_code_actions!(
+        CONVERT_TO_PIPE,
+        "
+pub fn main() {
+  use <- wibble(wobble)
+  todo
+}
+",
+        find_position_of("todo").to_selection()
+    );
+}
+
+#[test]
+fn convert_to_pipe_works_inside_body_of_use() {
+    assert_code_action!(
+        CONVERT_TO_PIPE,
+        "
+pub fn main() {
+  use <- wibble(wobble)
+  woo(123)
+}
+",
+        find_position_of("woo").to_selection()
+    );
+}
+
 #[test]
 fn convert_to_pipe_pipes_the_outermost_argument() {
     assert_code_action!(
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_works_inside_body_of_use.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_works_inside_body_of_use.snap
new file mode 100644
index 000000000..35055f157
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_works_inside_body_of_use.snap
@@ -0,0 +1,19 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  use <- wibble(wobble)\n  woo(123)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  use <- wibble(wobble)
+  woo(123)
+  ↑       
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  use <- wibble(wobble)
+  123 |> woo
+}