Repository: gleam-lang/gleam (rust)

Issue:
BUG: LSP convert-to-pipe action produces invalid code when it pulls out an expression
<!--
Please include the following:
- Steps and code to reproduce the problem
- Versions for Gleam, OS, the runtime and any other software used
- Logs from running Gleam with the `GLEAM_LOG` environment variable set to `trace`
-->

## Generating Invalid code

Given the following code: 
```gleam
import gleam/bool

pub fn bug() {
  bool.guard(1 != 0, Nil, fn() { Nil })
}
```

when running the "convert to pipe" lsp action, the following is generated:
```gleam
pub fn bug() {
  1 != 0 |> bool.guard(Nil, fn() { Nil })
}
```

which results in a compiler error, the `1!=0` should be wrapped in a block for it to be valid as follows: 

```gleam
pub fn bug() {
  {1 != 0} |> bool.guard(Nil, fn() { Nil })
}
```

## Creating incorrect Behaviour

This also occurs for non-boolean expressions, for example string concatenation where the output compiles but results in incorrect behaviour
```gleam
string.repeat("a" <> "b", 2) // outputs "abab"
```
is incorrectly transformed to
```gleam
"a" <> "b" |> string.repeat(2) // outputs "abb"
```

## Edge case that succeeds because of operator precedence rules

This bug does not manifest for arithmetic expressions because arithmetic operators have a higher precedence than pipe, where
```gleam
int.add(2*2,1)
```
and 
```gleam
2 * 2 |> int.add(1)
```
both correctly result in an output of 5



Pull request description:
Wrap binops in curly braces in the "Rewrite to pipe code action"
This PR closes #4428 

As I was writing the tests I realised that they were failing since binary operations are not fault tolerant! So this PR also updates the typer to be fault tolerant when typing binary operations.
There a test that started failing as a result of this, I have highlighted it with a comment in this PR as it might need more attention and to be special cased if we want to keep that old behaviour, I'd love if you had a look at that

Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 30b3f516e..09d6c05f1 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -480,7 +480,7 @@ jobs:
         working-directory: ./test/hextarball
 
       - name: Test running modules
-        run: make test
+        run: make test-all
         working-directory: ./test/running_modules
 
       - name: test/multi_namespace
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c50bcb0e..60396f0d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -90,6 +90,10 @@
 
   ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
 
+- Compilation of binary operators is now fault tolerant and won't stop at the
+  first type error.
+  ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
+
 ### Build tool
 
 - Include a type annotation for the `main` function generated by `gleam new`.
@@ -299,7 +303,9 @@
     }
   }
   ```
+
   Becomes
+
   ```gleam
   pub fn f(pokemon_type: PokemonType) {
     case pokemon_type {
@@ -367,6 +373,9 @@
   directory was deleted e.g. as a result of `gleam clean`.
   ([Sakari Bergen](https://github.com/sbergen))
 
+- Fixed a bug where the "Rewrite to pipe" code action could generate invalid
+  code when the piped argument was a binary operation.
+  ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
 
 ## v1.9.1 - 2025-03-10
 
diff --git a/compiler-cli/src/dependencies.rs b/compiler-cli/src/dependencies.rs
index d8bb9d77c..b9728547b 100644
--- a/compiler-cli/src/dependencies.rs
+++ b/compiler-cli/src/dependencies.rs
@@ -1014,7 +1014,7 @@ fn provide_package(
     if parents.contains(&package_name) {
         let mut last_cycle = parents
             .split(|p| p == &package_name)
-            .last()
+            .next_back()
             .unwrap_or_default()
             .to_vec();
         last_cycle.push(package_name);
diff --git a/compiler-core/src/ast.rs b/compiler-core/src/ast.rs
index 89f4ba126..c4e98da51 100644
--- a/compiler-core/src/ast.rs
+++ b/compiler-core/src/ast.rs
@@ -726,7 +726,7 @@ impl<T> Import<T> {
         match self.as_name.as_ref() {
             Some((AssignName::Variable(name), _)) => Some(name.clone()),
             Some((AssignName::Discard(_), _)) => None,
-            None => self.module.split('/').last().map(EcoString::from),
+            None => self.module.split('/').next_back().map(EcoString::from),
         }
     }
 
@@ -1165,6 +1165,8 @@ pub enum OperatorKind {
     StringConcatenation,
 }
 
+pub const PIPE_PRECEDENCE: u8 = 6;
+
 impl BinOp {
     pub fn precedence(&self) -> u8 {
         // Ensure that this matches the other precedence function for guards
diff --git a/compiler-core/src/ast/typed.rs b/compiler-core/src/ast/typed.rs
index 4c8594ecd..4877bfab3 100644
--- a/compiler-core/src/ast/typed.rs
+++ b/compiler-core/src/ast/typed.rs
@@ -90,6 +90,7 @@ pub enum TypedExpr {
 
     RecordAccess {
         location: SrcSpan,
+        field_start: u32,
         type_: Arc<Type>,
         label: EcoString,
         index: u64,
diff --git a/compiler-core/src/ast/visit.rs b/compiler-core/src/ast/visit.rs
index 3daf6c180..d8990d5e6 100644
--- a/compiler-core/src/ast/visit.rs
+++ b/compiler-core/src/ast/visit.rs
@@ -213,12 +213,13 @@ pub trait Visit<'ast> {
     fn visit_typed_expr_record_access(
         &mut self,
         location: &'ast SrcSpan,
+        field_start: &'ast u32,
         type_: &'ast Arc<Type>,
         label: &'ast EcoString,
         index: &'ast u64,
         record: &'ast TypedExpr,
     ) {
-        visit_typed_expr_record_access(self, location, type_, label, index, record);
+        visit_typed_expr_record_access(self, location, field_start, type_, label, index, record);
     }
 
     #[allow(clippy::too_many_arguments)]
@@ -770,11 +771,12 @@ where
         } => v.visit_typed_expr_case(location, type_, subjects, clauses),
         TypedExpr::RecordAccess {
             location,
+            field_start,
             type_,
             label,
             index,
             record,
-        } => v.visit_typed_expr_record_access(location, type_, label, index, record),
+        } => v.visit_typed_expr_record_access(location, field_start, type_, label, index, record),
         TypedExpr::ModuleSelect {
             location,
             field_start,
@@ -1001,6 +1003,7 @@ pub fn visit_typed_expr_case<'a, V>(
 pub fn visit_typed_expr_record_access<'a, V>(
     v: &mut V,
     _location: &'a SrcSpan,
+    _field_start: &'a u32,
     _type_: &'a Arc<Type>,
     _label: &'a EcoString,
     _index: &'a u64,
diff --git a/compiler-core/src/dependency.rs b/compiler-core/src/dependency.rs
index 3d2f810fd..c6b58cd3b 100644
--- a/compiler-core/src/dependency.rs
+++ b/compiler-core/src/dependency.rs
@@ -72,11 +72,7 @@ fn parse_exact_version(ver: &str) -> Option<Version> {
     if version.starts_with("==") || first_byte.is_some_and(|v| v.is_ascii_digit()) {
         let version = version.replace("==", "");
         let version = version.as_str().trim();
-        if let Ok(v) = Version::parse(version) {
-            Some(v)
-        } else {
-            None
-        }
+        Version::parse(version).ok()
     } else {
         None
     }
diff --git a/compiler-core/src/error.rs b/compiler-core/src/error.rs
index cad477f6a..eceaa0c3c 100644
--- a/compiler-core/src/error.rs
+++ b/compiler-core/src/error.rs
@@ -789,7 +789,7 @@ fn edit_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option<usize
         1 // Exact substring match, but not a total word match so return non-zero
     } else if !big_len_diff {
         // Not a big difference in length, discount cost of length difference
-        score + (len_diff + 1) / 2
+        score + len_diff.div_ceil(2)
     } else {
         // A big difference in length, add back the difference in length to the score
         score + len_diff
diff --git a/compiler-core/src/exhaustiveness.rs b/compiler-core/src/exhaustiveness.rs
index e7c63677e..93d601f4f 100644
--- a/compiler-core/src/exhaustiveness.rs
+++ b/compiler-core/src/exhaustiveness.rs
@@ -235,7 +235,6 @@ impl Branch {
                     Pattern::Assign { name, pattern } => {
                         self.body.assign(name.clone(), check.var.clone());
                         check.pattern = *pattern;
-                        continue;
                     }
                     // There's a special case of assignments when it comes to string
                     // prefix patterns. We can give a name to a literal prefix like this:
diff --git a/compiler-core/src/exhaustiveness/printer.rs b/compiler-core/src/exhaustiveness/printer.rs
index 07b952e02..13631023c 100644
--- a/compiler-core/src/exhaustiveness/printer.rs
+++ b/compiler-core/src/exhaustiveness/printer.rs
@@ -57,7 +57,7 @@ impl<'a> Printer<'a> {
                     NameContextInformation::Qualified(m, n) => (Some(m), n),
                     NameContextInformation::Unqualified(n) => (None, n),
                     NameContextInformation::Unimported(n) => {
-                        (Some(module.split('/').last().unwrap_or(module)), n)
+                        (Some(module.split('/').next_back().unwrap_or(module)), n)
                     }
                 };
 
diff --git a/compiler-core/src/format.rs b/compiler-core/src/format.rs
index f11b08c33..6a79db474 100644
--- a/compiler-core/src/format.rs
+++ b/compiler-core/src/format.rs
@@ -412,7 +412,7 @@ impl<'comments> Formatter<'comments> {
                 };
 
                 let doc = docvec!["import ", module.as_str(), second];
-                let default_module_access_name = module.split('/').last().map(EcoString::from);
+                let default_module_access_name = module.split('/').next_back().map(EcoString::from);
                 match (default_module_access_name, as_name) {
                     // If the `as name` is the same as the module name that would be
                     // used anyways we won't render it. For example:
diff --git a/compiler-core/src/javascript.rs b/compiler-core/src/javascript.rs
index 26488fd22..d41e3aa9c 100644
--- a/compiler-core/src/javascript.rs
+++ b/compiler-core/src/javascript.rs
@@ -90,7 +90,7 @@ impl<'a> Generator<'a> {
             .name
             .as_str()
             .split('/')
-            .last()
+            .next_back()
             .expect("JavaScript generator could not identify imported module name.");
 
         docvec!["/// <reference types=\"./", module, ".d.mts\" />", line()]
@@ -453,7 +453,7 @@ impl<'a> Generator<'a> {
         let get_name = |module: &'a str| {
             module
                 .split('/')
-                .last()
+                .next_back()
                 .expect("JavaScript generator could not identify imported module name.")
         };
 
diff --git a/compiler-core/src/javascript/typescript.rs b/compiler-core/src/javascript/typescript.rs
index b8a0230b8..76eb3d6f4 100644
--- a/compiler-core/src/javascript/typescript.rs
+++ b/compiler-core/src/javascript/typescript.rs
@@ -626,7 +626,7 @@ impl<'a> TypeScriptGenerator<'a> {
 
         let name = match self.aliased_module_names.get(name) {
             Some(name) => name,
-            None => name.split('/').last().expect("Non empty module path"),
+            None => name.split('/').next_back().expect("Non empty module path"),
         };
 
         eco_format!("${name}")
diff --git a/compiler-core/src/language_server/code_action.rs b/compiler-core/src/language_server/code_action.rs
index 9b3df744c..e072903c4 100644
--- a/compiler-core/src/language_server/code_action.rs
+++ b/compiler-core/src/language_server/code_action.rs
@@ -4,10 +4,10 @@ use crate::{
     Error, STDLIB_PACKAGE_NAME,
     ast::{
         self, AssignName, AssignmentKind, CallArg, CustomType, FunctionLiteralKind,
-        ImplicitCallArgOrigin, Pattern, PatternUnusedArguments, PipelineAssignmentKind,
-        RecordConstructor, SrcSpan, TodoKind, TypedArg, TypedAssignment, TypedExpr,
-        TypedModuleConstant, TypedPattern, TypedPipelineAssignment, TypedRecordConstructor,
-        TypedStatement, TypedUse,
+        ImplicitCallArgOrigin, PIPE_PRECEDENCE, Pattern, PatternUnusedArguments,
+        PipelineAssignmentKind, RecordConstructor, SrcSpan, TodoKind, TypedArg, TypedAssignment,
+        TypedExpr, TypedModuleConstant, TypedPattern, TypedPipelineAssignment,
+        TypedRecordConstructor, TypedStatement, TypedUse,
         visit::{Visit as _, visit_typed_call_arg, visit_typed_pattern_call_arg},
     },
     build::{Located, Module},
@@ -275,7 +275,7 @@ impl<'a> RedundantTupleInCaseSubject<'a> {
         // tuple items.
         self.edits.replace(
             discard_location,
-            itertools::intersperse(iter::repeat("_").take(tuple_items), ", ").collect(),
+            itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(),
         )
     }
 }
@@ -724,7 +724,7 @@ impl<'a> FillInMissingLabelledArgs<'a> {
             //
             let label_insertion_start = call_location.end - 1;
             let has_comma_after_last_argument =
-                if let Some(last_arg) = args.iter().filter(|arg| !arg.is_implicit()).last() {
+                if let Some(last_arg) = args.iter().filter(|arg| !arg.is_implicit()).next_back() {
                     self.module
                         .code
                         .get(last_arg.location.end as usize..=label_insertion_start as usize)
@@ -2310,7 +2310,7 @@ impl<'a> ConvertFromUse<'a> {
             .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_explicit_arg = args.iter().filter(|arg| !arg.is_implicit()).next_back();
         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
@@ -3023,8 +3023,8 @@ fn can_be_constant(
         }
 
         // Extract record types as long as arguments can be constant
-        TypedExpr::Call { args, type_, .. } => {
-            matches!(type_.as_ref(), Type::Named { .. })
+        TypedExpr::Call { args, fun, .. } => {
+            fun.is_record_builder()
                 && args
                     .iter()
                     .all(|arg| can_be_constant(module, &arg.value, module_constants))
@@ -4868,7 +4868,7 @@ fn labels_are_correct(args: &[TypedCallArg]) -> bool {
                 labelled_arg_found = true;
                 let _ = used_labels.insert(label);
             }
-            None => continue,
+            None => {}
         }
     }
 
@@ -5369,6 +5369,15 @@ impl<'a> ConvertToPipe<'a> {
         };
 
         let arg_text = self.module.code.get(arg_range).expect("invalid srcspan");
+        let arg_text = match arg.value {
+            // If the expression being piped is a binary operation with
+            // precedence lower than pipes then we have to wrap it in curly
+            // braces to not mess with the order of operations.
+            TypedExpr::BinOp { name, .. } if name.precedence() < PIPE_PRECEDENCE => {
+                &format!("{{ {arg_text} }}")
+            }
+            _ => arg_text,
+        };
 
         match next_arg {
             // When extracting an argument we never want to remove any explicit
@@ -5849,7 +5858,7 @@ impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> {
                 let last_argument_end = arguments
                     .iter()
                     .filter(|arg| !arg.is_implicit())
-                    .last()
+                    .next_back()
                     .map(|arg| arg.location.end);
 
                 self.data = Some(FillUnusedFieldsData {
@@ -6100,15 +6109,8 @@ impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> {
         match *assignment.to_owned().value {
             // To avoid wrapping the same expression in multiple, nested blocks.
             TypedExpr::Block { .. } => {}
-            TypedExpr::RecordAccess {
-                record, location, ..
-            } => {
-                self.selected_expression = Some(SrcSpan {
-                    start: record.location().start,
-                    end: location.end,
-                });
-            }
-            TypedExpr::Int { .. }
+            TypedExpr::RecordAccess { .. }
+            | TypedExpr::Int { .. }
             | TypedExpr::Float { .. }
             | TypedExpr::String { .. }
             | TypedExpr::Pipeline { .. }
diff --git a/compiler-core/src/language_server/completer.rs b/compiler-core/src/language_server/completer.rs
index 4b606411e..9b194c7ad 100644
--- a/compiler-core/src/language_server/completer.rs
+++ b/compiler-core/src/language_server/completer.rs
@@ -469,16 +469,15 @@ where
             // e.x. when the user has typed mymodule.| we know unqualified module types are no longer relevant.
             if module_select.is_none() {
                 for unqualified in &import.unqualified_types {
-                    match module.get_public_type(&unqualified.name) {
-                        Some(type_) => completions.push(type_completion(
+                    if let Some(type_) = module.get_public_type(&unqualified.name) {
+                        completions.push(type_completion(
                             None,
                             unqualified.used_name(),
                             type_,
                             insert_range,
                             TypeCompletionForm::Default,
                             CompletionKind::ImportedModule,
-                        )),
-                        None => continue,
+                        ))
                     }
                 }
             }
@@ -504,7 +503,7 @@ where
 
             let qualifier = module_full_name
                 .split('/')
-                .last()
+                .next_back()
                 .unwrap_or(module_full_name);
 
             // If the user has already started a module select then don't show irrelevant modules.
@@ -657,19 +656,16 @@ where
             // e.x. when the user has typed mymodule.| we know unqualified module values are no longer relevant.
             if module_select.is_none() {
                 for unqualified in &import.unqualified_values {
-                    match module.get_public_value(&unqualified.name) {
-                        Some(value) => {
-                            let name = unqualified.used_name();
-                            completions.push(value_completion(
-                                None,
-                                mod_name,
-                                name,
-                                value,
-                                insert_range,
-                                CompletionKind::ImportedModule,
-                            ))
-                        }
-                        None => continue,
+                    if let Some(value) = module.get_public_value(&unqualified.name) {
+                        let name = unqualified.used_name();
+                        completions.push(value_completion(
+                            None,
+                            mod_name,
+                            name,
+                            value,
+                            insert_range,
+                            CompletionKind::ImportedModule,
+                        ))
                     }
                 }
             }
@@ -693,7 +689,7 @@ where
             }
             let qualifier = module_full_name
                 .split('/')
-                .last()
+                .next_back()
                 .unwrap_or(module_full_name);
 
             // If the user has already started a module select then don't show irrelevant modules.
diff --git a/compiler-core/src/language_server/messages.rs b/compiler-core/src/language_server/messages.rs
index eef0849ef..2d2b7318c 100644
--- a/compiler-core/src/language_server/messages.rs
+++ b/compiler-core/src/language_server/messages.rs
@@ -114,7 +114,7 @@ impl Notification {
                 let params = cast_notification::<DidChangeTextDocument>(notification);
                 let notification = Notification::SourceFileChangedInMemory {
                     path: super::path(&params.text_document.uri),
-                    text: params.content_changes.into_iter().last()?.text,
+                    text: params.content_changes.into_iter().next_back()?.text,
                 };
                 Some(Message::Notification(notification))
             }
@@ -137,7 +137,7 @@ impl Notification {
             "workspace/didChangeWatchedFiles" => {
                 let params = cast_notification::<DidChangeWatchedFiles>(notification);
                 let notification = Notification::ConfigFileChanged {
-                    path: super::path(&params.changes.into_iter().last()?.uri),
+                    path: super::path(&params.changes.into_iter().next_back()?.uri),
                 };
                 Some(Message::Notification(notification))
             }
diff --git a/compiler-core/src/parse.rs b/compiler-core/src/parse.rs
index 7b53f922b..9c59b1d77 100644
--- a/compiler-core/src/parse.rs
+++ b/compiler-core/src/parse.rs
@@ -3388,8 +3388,7 @@ where
                         Token::Name { name } => name,
                         token => {
                             let hint = match (&token, self.tok0.take()) {
-                                (&Token::Fn { .. }, _)
-                                | (&Token::Pub, Some((_, Token::Fn { .. }, _))) => {
+                                (&Token::Fn, _) | (&Token::Pub, Some((_, Token::Fn, _))) => {
                                     let text =
                                         "Gleam is not an object oriented programming language so
 functions are declared separately from types.";
diff --git a/compiler-core/src/type_/environment.rs b/compiler-core/src/type_/environment.rs
index 3fda1ad6f..c68a1727f 100644
--- a/compiler-core/src/type_/environment.rs
+++ b/compiler-core/src/type_/environment.rs
@@ -702,7 +702,7 @@ impl Environment<'_> {
                     // Don't suggest importing modules if they are already imported
                     _ if self
                         .imported_modules
-                        .contains_key(importable.split('/').last().unwrap_or(importable)) =>
+                        .contains_key(importable.split('/').next_back().unwrap_or(importable)) =>
                     {
                         None
                     }
diff --git a/compiler-core/src/type_/error.rs b/compiler-core/src/type_/error.rs
index e0ea90c58..6f4bee455 100644
--- a/compiler-core/src/type_/error.rs
+++ b/compiler-core/src/type_/error.rs
@@ -137,7 +137,7 @@ impl ModuleSuggestion {
     pub fn last_name_component(&self) -> &str {
         match self {
             ModuleSuggestion::Imported(name) | ModuleSuggestion::Importable(name) => {
-                name.split('/').last().unwrap_or(name)
+                name.split('/').next_back().unwrap_or(name)
             }
         }
     }
diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs
index 09c8abd73..50e2e267b 100644
--- a/compiler-core/src/type_/expression.rs
+++ b/compiler-core/src/type_/expression.rs
@@ -385,7 +385,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                 left,
                 right,
                 ..
-            } => self.infer_binop(name, *left, *right, location),
+            } => Ok(self.infer_binop(name, *left, *right, location)),
 
             UntypedExpr::FieldAccess {
                 label_location,
@@ -1032,12 +1032,18 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
         label_location: SrcSpan,
         usage: FieldAccessUsage,
     ) -> TypedExpr {
+        let container_location = container.location();
+
         // Computes a potential module access. This will be used if a record access can't be used.
         // Computes both the inferred access and if it shadows a variable.
         let module_access = match &container {
-            UntypedExpr::Var { location, name } => {
-                let module_access =
-                    self.infer_module_access(name, label.clone(), location, label_location);
+            UntypedExpr::Var { name, .. } => {
+                let module_access = self.infer_module_access(
+                    name,
+                    label.clone(),
+                    &container_location,
+                    label_location,
+                );
                 // Returns the result and if it shadows an existing variable in scope
                 Some((module_access, self.environment.scope.contains_key(name)))
             }
@@ -1060,7 +1066,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
             Ok(record) => self.infer_known_record_expression_access(
                 record,
                 label.clone(),
+                location,
                 label_location,
+                container_location.start,
                 usage,
             ),
             Err(e) => Err(e),
@@ -1139,7 +1147,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                     // This allows autocomplete to know a record access is being attempted
                     // Even if the access is not valid
                     Ok(record) => TypedExpr::RecordAccess {
-                        location: label_location,
+                        location,
+                        field_start: container_location.start,
                         type_: self.new_unbound_var(),
                         label: "".into(),
                         index: u64::MAX,
@@ -1377,29 +1386,45 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
         })
     }
 
+    /// Same as `self.infer` but instead of returning a `Result` with an error,
+    /// records the error and returns an invalid expression.
+    ///
+    fn infer_no_error(&mut self, expression: UntypedExpr) -> TypedExpr {
+        let location = expression.location();
+        match self.infer(expression) {
+            Ok(result) => result,
+            Err(error) => {
+                self.problems.error(error);
+                self.error_expr(location)
+            }
+        }
+    }
+
     fn infer_binop(
         &mut self,
         name: BinOp,
         left: UntypedExpr,
         right: UntypedExpr,
         location: SrcSpan,
-    ) -> Result<TypedExpr, Error> {
+    ) -> TypedExpr {
         let (input_type, output_type) = match &name {
             BinOp::Eq | BinOp::NotEq => {
-                let left = self.infer(left)?;
-                let right = self.infer(right)?;
-                unify(left.type_(), right.type_())
-                    .map_err(|e| convert_unify_error(e, right.location()))?;
+                let left = self.infer_no_error(left);
+                let right = self.infer_no_error(right);
+                if let Err(error) = unify(left.type_(), right.type_()) {
+                    self.problems
+                        .error(convert_unify_error(error, right.location()));
+                }
 
                 self.check_for_inefficient_empty_list_check(name, &left, &right, location);
 
-                return Ok(TypedExpr::BinOp {
+                return TypedExpr::BinOp {
                     location,
                     name,
                     type_: bool(),
                     left: Box::new(left),
                     right: Box::new(right),
-                });
+                };
             }
             BinOp::And => (bool(), bool()),
             BinOp::Or => (bool(), bool()),
@@ -1423,26 +1448,33 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
             BinOp::Concatenate => (string(), string()),
         };
 
-        let left = self.infer(left)?;
-        unify(input_type.clone(), left.type_()).map_err(|e| {
-            e.operator_situation(name)
-                .into_error(left.type_defining_location())
-        })?;
-        let right = self.infer(right)?;
-        unify(input_type, right.type_()).map_err(|e| {
-            e.operator_situation(name)
-                .into_error(right.type_defining_location())
-        })?;
+        let left = self.infer_no_error(left);
+        if let Err(error) = unify(input_type.clone(), left.type_()) {
+            self.problems.error(
+                error
+                    .operator_situation(name)
+                    .into_error(left.type_defining_location()),
+            )
+        }
+
+        let right = self.infer_no_error(right);
+        if let Err(error) = unify(input_type.clone(), right.type_()) {
+            self.problems.error(
+                error
+                    .operator_situation(name)
+                    .into_error(right.type_defining_location()),
+            )
+        }
 
         self.check_for_inefficient_empty_list_check(name, &left, &right, location);
 
-        Ok(TypedExpr::BinOp {
+        TypedExpr::BinOp {
             location,
             name,
             type_: output_type,
             left: Box::new(left),
             right: Box::new(right),
-        })
+        }
     }
 
     /// Checks for inefficient usage of `list.length` for checking for the empty list.
@@ -2528,15 +2560,23 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
         record: TypedExpr,
         label: EcoString,
         location: SrcSpan,
+        label_location: SrcSpan,
+        field_start: u32,
         usage: FieldAccessUsage,
     ) -> Result<TypedExpr, Error> {
         let record = Box::new(record);
         let record_type = record.type_();
-        let (index, label, type_) =
-            self.infer_known_record_access(record_type, record.location(), usage, location, label)?;
+        let (index, label, type_) = self.infer_known_record_access(
+            record_type,
+            record.location(),
+            usage,
+            label_location,
+            label,
+        )?;
         Ok(TypedExpr::RecordAccess {
             record,
             label,
+            field_start,
             index,
             location,
             type_,
@@ -2810,6 +2850,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                     record.clone(),
                     label.clone(),
                     record_location,
+                    record_location,
+                    record_location.start,
                     FieldAccessUsage::RecordUpdate,
                 )?;
 
diff --git a/compiler-core/src/type_/printer.rs b/compiler-core/src/type_/printer.rs
index 425a93976..9464ef489 100644
--- a/compiler-core/src/type_/printer.rs
+++ b/compiler-core/src/type_/printer.rs
@@ -344,7 +344,7 @@ impl<'a> Printer<'a> {
                     // TODO: indicate that the module is not import and as such
                     // needs to be, as well as how.
                     NameContextInformation::Unimported(n) => {
-                        (Some(module.split('/').last().unwrap_or(module)), n)
+                        (Some(module.split('/').next_back().unwrap_or(module)), n)
                     }
                 };
 
@@ -388,7 +388,7 @@ impl<'a> Printer<'a> {
             NameContextInformation::Qualified(module, name) => (Some(module), name),
             NameContextInformation::Unqualified(name) => (None, name),
             NameContextInformation::Unimported(name) => {
-                (Some(module.split('/').last().unwrap_or(module)), name)
+                (Some(module.split('/').next_back().unwrap_or(module)), name)
             }
         };
 


Test patch:
diff --git a/compiler-core/src/language_server/tests/action.rs b/compiler-core/src/language_server/tests/action.rs
index 48a3f7b0e..66397eb35 100644
--- a/compiler-core/src/language_server/tests/action.rs
+++ b/compiler-core/src/language_server/tests/action.rs
@@ -7160,6 +7160,71 @@ pub fn main() {
     );
 }
 
+#[test]
+fn convert_to_pipe_with_string_concat_adds_braces() {
+    assert_code_action!(
+        CONVERT_TO_PIPE,
+        "
+pub fn main() {
+  wibble(wobble <> woo, waa)
+}
+",
+        find_position_of("wibble").to_selection()
+    );
+}
+
+#[test]
+fn convert_to_pipe_with_bool_operator_adds_braces() {
+    assert_code_action!(
+        CONVERT_TO_PIPE,
+        "
+pub fn main() {
+  wibble(wobble != woo, waa)
+}
+",
+        find_position_of("wibble").to_selection()
+    );
+}
+
+#[test]
+fn convert_to_pipe_with_sum_adds_no_braces() {
+    assert_code_action!(
+        CONVERT_TO_PIPE,
+        "
+pub fn main() {
+  wibble(1 + 1, waa)
+}
+",
+        find_position_of("wibble").to_selection()
+    );
+}
+
+#[test]
+fn convert_to_pipe_with_comparison_adds_braces() {
+    assert_code_action!(
+        CONVERT_TO_PIPE,
+        "
+pub fn main() {
+  wibble(1.0 >=. 0.0, waa)
+}
+",
+        find_position_of("wibble").to_selection()
+    );
+}
+
+#[test]
+fn convert_to_pipe_with_complex_binop_adds_braces() {
+    assert_code_action!(
+        CONVERT_TO_PIPE,
+        "
+fn bug() {
+    bool.guard(1 == 2 || 2 == 3, Nil, fn() { Nil })
+}
+",
+        find_position_of("||").to_selection()
+    );
+}
+
 // https://github.com/gleam-lang/gleam/issues/4342
 #[test]
 fn inline_variable_in_record_update() {
@@ -7179,6 +7244,26 @@ pub fn main() {
     );
 }
 
+// https://github.com/gleam-lang/gleam/issues/4430
+#[test]
+fn inline_variable_with_record_field() {
+    assert_code_action!(
+        INLINE_VARIABLE,
+        "
+type Couple {
+  Couple(l: Int, r: Int)
+}
+
+pub fn main() {
+  let c1 = Couple(l: 1, r: 1)
+  let c2 = c1.l
+  echo c2
+}
+",
+        find_position_of("c2").nth_occurrence(2).to_selection()
+    );
+}
+
 #[test]
 fn wrap_case_clause_in_block() {
     assert_code_action!(
@@ -7239,7 +7324,7 @@ fn wrap_case_clause_with_multiple_patterns_in_block() {
   Water
   Fire
 }
-        
+
   pub fn f(pokemon_type: PokemonType) {
     case pokemon_type {
       Water | Air -> soak()
@@ -7259,7 +7344,7 @@ fn wrap_case_clause_inside_assignment_in_block() {
   Water
   Fire
 }
-        
+
   pub fn f(pokemon_type: PokemonType) {
     let damage = case pokemon_type {
       Water -> soak()
@@ -7366,3 +7451,21 @@ fn do_not_wrap_assignment_value_in_block() {
         find_position_of("var").to_selection()
     );
 }
+
+// https://github.com/gleam-lang/gleam/issues/4427
+#[test]
+fn extract_constant_function() {
+    assert_no_code_actions!(
+        EXTRACT_CONSTANT,
+        r#"
+fn print(x) {
+  Nil
+}
+
+pub fn main() {
+  print("Hello")
+}
+"#,
+        find_position_of("print").nth_occurrence(2).to_selection()
+    );
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_bool_operator_adds_braces.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_bool_operator_adds_braces.snap
new file mode 100644
index 000000000..30e4a6b04
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_bool_operator_adds_braces.snap
@@ -0,0 +1,17 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  wibble(wobble != woo, waa)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  wibble(wobble != woo, waa)
+  ↑                         
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  { wobble != woo } |> wibble(waa)
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_comparison_adds_braces.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_comparison_adds_braces.snap
new file mode 100644
index 000000000..6095d8cbf
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_comparison_adds_braces.snap
@@ -0,0 +1,17 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  wibble(1.0 >=. 0.0, waa)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  wibble(1.0 >=. 0.0, waa)
+  ↑                       
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  { 1.0 >=. 0.0 } |> wibble(waa)
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_complex_binop_adds_braces.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_complex_binop_adds_braces.snap
new file mode 100644
index 000000000..176582502
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_complex_binop_adds_braces.snap
@@ -0,0 +1,17 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\nfn bug() {\n    bool.guard(1 == 2 || 2 == 3, Nil, fn() { Nil })\n}\n"
+---
+----- BEFORE ACTION
+
+fn bug() {
+    bool.guard(1 == 2 || 2 == 3, Nil, fn() { Nil })
+                      ↑                            
+}
+
+
+----- AFTER ACTION
+
+fn bug() {
+    { 1 == 2 || 2 == 3 } |> bool.guard(Nil, fn() { Nil })
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_string_concat_adds_braces.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_string_concat_adds_braces.snap
new file mode 100644
index 000000000..35ef94ba2
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_string_concat_adds_braces.snap
@@ -0,0 +1,17 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  wibble(wobble <> woo, waa)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  wibble(wobble <> woo, waa)
+  ↑                         
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  { wobble <> woo } |> wibble(waa)
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_sum_adds_no_braces.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_sum_adds_no_braces.snap
new file mode 100644
index 000000000..973cd7471
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__convert_to_pipe_with_sum_adds_no_braces.snap
@@ -0,0 +1,17 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\npub fn main() {\n  wibble(1 + 1, waa)\n}\n"
+---
+----- BEFORE ACTION
+
+pub fn main() {
+  wibble(1 + 1, waa)
+  ↑                 
+}
+
+
+----- AFTER ACTION
+
+pub fn main() {
+  1 + 1 |> wibble(waa)
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__inline_variable_with_record_field.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__inline_variable_with_record_field.snap
new file mode 100644
index 000000000..d59e3498d
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__inline_variable_with_record_field.snap
@@ -0,0 +1,28 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\ntype Couple {\n  Couple(l: Int, r: Int)\n}\n\npub fn main() {\n  let c1 = Couple(l: 1, r: 1)\n  let c2 = c1.l\n  echo c2\n}\n"
+---
+----- BEFORE ACTION
+
+type Couple {
+  Couple(l: Int, r: Int)
+}
+
+pub fn main() {
+  let c1 = Couple(l: 1, r: 1)
+  let c2 = c1.l
+  echo c2
+       ↑ 
+}
+
+
+----- AFTER ACTION
+
+type Couple {
+  Couple(l: Int, r: Int)
+}
+
+pub fn main() {
+  let c1 = Couple(l: 1, r: 1)
+  echo c1.l
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__wrap_case_clause_inside_assignment_in_block.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__wrap_case_clause_inside_assignment_in_block.snap
index 0ad56dbb5..16e735ef9 100644
--- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__wrap_case_clause_inside_assignment_in_block.snap
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__wrap_case_clause_inside_assignment_in_block.snap
@@ -1,6 +1,6 @@
 ---
 source: compiler-core/src/language_server/tests/action.rs
-expression: "pub type PokemonType {\n  Air\n  Water\n  Fire\n}\n        \n  pub fn f(pokemon_type: PokemonType) {\n    let damage = case pokemon_type {\n      Water -> soak()\n      Fire -> burn()\n    }\n\n    \"Pokemon did \" <> damage\n  }"
+expression: "pub type PokemonType {\n  Air\n  Water\n  Fire\n}\n\n  pub fn f(pokemon_type: PokemonType) {\n    let damage = case pokemon_type {\n      Water -> soak()\n      Fire -> burn()\n    }\n\n    \"Pokemon did \" <> damage\n  }"
 ---
 ----- BEFORE ACTION
 pub type PokemonType {
@@ -8,7 +8,7 @@ pub type PokemonType {
   Water
   Fire
 }
-        
+
   pub fn f(pokemon_type: PokemonType) {
     let damage = case pokemon_type {
       Water -> soak()
@@ -26,7 +26,7 @@ pub type PokemonType {
   Water
   Fire
 }
-        
+
   pub fn f(pokemon_type: PokemonType) {
     let damage = case pokemon_type {
       Water -> soak()
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__wrap_case_clause_with_multiple_patterns_in_block.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__wrap_case_clause_with_multiple_patterns_in_block.snap
index 815f3fbb8..28574783f 100644
--- a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__wrap_case_clause_with_multiple_patterns_in_block.snap
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__wrap_case_clause_with_multiple_patterns_in_block.snap
@@ -1,6 +1,6 @@
 ---
 source: compiler-core/src/language_server/tests/action.rs
-expression: "pub type PokemonType {\n  Air\n  Water\n  Fire\n}\n        \n  pub fn f(pokemon_type: PokemonType) {\n    case pokemon_type {\n      Water | Air -> soak()\n      Fire -> burn()\n    }\n  }"
+expression: "pub type PokemonType {\n  Air\n  Water\n  Fire\n}\n\n  pub fn f(pokemon_type: PokemonType) {\n    case pokemon_type {\n      Water | Air -> soak()\n      Fire -> burn()\n    }\n  }"
 ---
 ----- BEFORE ACTION
 pub type PokemonType {
@@ -8,7 +8,7 @@ pub type PokemonType {
   Water
   Fire
 }
-        
+
   pub fn f(pokemon_type: PokemonType) {
     case pokemon_type {
       Water | Air -> soak()
@@ -24,7 +24,7 @@ pub type PokemonType {
   Water
   Fire
 }
-        
+
   pub fn f(pokemon_type: PokemonType) {
     case pokemon_type {
       Water | Air -> {
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_invalid_operands.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_invalid_operands.snap
index b8baa14a7..8dc0fe9dd 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_invalid_operands.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_invalid_operands.snap
@@ -28,3 +28,18 @@ The + operator expects arguments of this type:
 But this argument has this type:
 
     math.Vector
+
+
+error: Type mismatch
+  ┌─ /src/one/two.gleam:4:7
+  │
+4 │   a + b
+  │       ^
+
+The + operator expects arguments of this type:
+
+    Int
+
+But this argument has this type:
+
+    math.Vector
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__bad_body_function_fault_tolerance.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__bad_body_function_fault_tolerance.snap
index 796514b3b..2d3b572f2 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__bad_body_function_fault_tolerance.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__bad_body_function_fault_tolerance.snap
@@ -40,6 +40,40 @@ But this argument has this type:
 Hint: Strings can be joined using the `append` or `concat` functions from the
 `gleam/string` module.
 
+error: Type mismatch
+  ┌─ /src/one/two.gleam:4:3
+  │
+4 │   "" + ""
+  │   ^^^^^^^
+
+The type of this returned value doesn't match the return type
+annotation of this function.
+
+Expected type:
+
+    Float
+
+Found type:
+
+    Int
+
+error: Type mismatch
+  ┌─ /src/one/two.gleam:4:8
+  │
+4 │   "" + ""
+  │        ^^
+
+The + operator expects arguments of this type:
+
+    Int
+
+But this argument has this type:
+
+    String
+
+Hint: Strings can be joined using the `append` or `concat` functions from the
+`gleam/string` module.
+
 error: Type mismatch
    ┌─ /src/one/two.gleam:16:7
    │
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__case_clause_then_fault_tolerance.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__case_clause_then_fault_tolerance.snap
index 2edca186e..7b2abe80c 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__case_clause_then_fault_tolerance.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__case_clause_then_fault_tolerance.snap
@@ -35,6 +35,23 @@ But this argument has this type:
 Hint: the +. operator can be used with Floats
 
 
+error: Type mismatch
+  ┌─ /src/one/two.gleam:6:13
+  │
+6 │       1.0 + 1.0
+  │             ^^^
+
+The + operator expects arguments of this type:
+
+    Int
+
+But this argument has this type:
+
+    Float
+
+Hint: the +. operator can be used with Floats
+
+
 error: Type mismatch
   ┌─ /src/one/two.gleam:9:7
   │
@@ -50,3 +67,20 @@ But this argument has this type:
     Float
 
 Hint: the +. operator can be used with Floats
+
+
+error: Type mismatch
+  ┌─ /src/one/two.gleam:9:13
+  │
+9 │       1.0 + 1.0
+  │             ^^^
+
+The + operator expects arguments of this type:
+
+    Int
+
+But this argument has this type:
+
+    Float
+
+Hint: the +. operator can be used with Floats
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_pattern_fault_tolerance2.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_pattern_fault_tolerance2.snap
index 8192d3786..5181f7e00 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_pattern_fault_tolerance2.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_pattern_fault_tolerance2.snap
@@ -34,7 +34,7 @@ Expected type:
 
 Found type:
 
-    Result(a, b)
+    Result(Int, a)
 
 error: Unknown variable
   ┌─ /src/one/two.gleam:7:11
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_one_arg_type_to_two_args_fn.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_one_arg_type_to_two_args_fn.snap
index 323cd023f..f88a3ed8d 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_one_arg_type_to_two_args_fn.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_one_arg_type_to_two_args_fn.snap
@@ -26,3 +26,12 @@ error: Type mismatch
 
 To index into a tuple we need to know its size, but we don't know anything
 about this type yet. Please add some type annotations so we can continue.
+
+error: Type mismatch
+  ┌─ /src/one/two.gleam:4:21
+  │
+4 │    fn(x, y) { x.0 + y.1 }(a)
+  │                     ^ What type is this?
+
+To index into a tuple we need to know its size, but we don't know anything
+about this type yet. Please add some type annotations so we can continue.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_record_access_raises_a_warning.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_record_access_raises_a_warning.snap
index d6cbbc0d3..57697d291 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_record_access_raises_a_warning.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_record_access_raises_a_warning.snap
@@ -17,7 +17,7 @@ pub fn main() {
 
 ----- WARNING
 warning: Unused value
-  ┌─ /src/warning/wrn.gleam:8:9
+  ┌─ /src/warning/wrn.gleam:8:3
   │
 8 │   thing.value
-  │         ^^^^^ This value is never used
+  │   ^^^^^^^^^^^ This value is never used
diff --git a/compiler-core/src/type_/tests/warnings.rs b/compiler-core/src/type_/tests/warnings.rs
index 434450410..c8b4ada7e 100644
--- a/compiler-core/src/type_/tests/warnings.rs
+++ b/compiler-core/src/type_/tests/warnings.rs
@@ -781,19 +781,6 @@ pub const x = some_module.x
     );
 }
 
-#[test]
-fn no_unused_warnings_for_broken_code() {
-    let src = r#"
-pub fn main() {
-  let x = 1
-  1 + ""
-  x
-}"#;
-    let warnings = VectorWarningEmitterIO::default();
-    _ = compile_module("test_module", src, Some(Rc::new(warnings.clone())), vec![]).unwrap_err();
-    assert!(warnings.take().is_empty());
-}
-
 #[test]
 fn deprecated_constant() {
     assert_warning!(
diff --git a/test/running_modules/gleam.toml b/test/running_modules/gleam.toml
index 1ca816ca4..11709fab5 100644
--- a/test/running_modules/gleam.toml
+++ b/test/running_modules/gleam.toml
@@ -3,11 +3,8 @@ version = "0.1.0"
 description = "A Gleam project"
 
 [dependencies]
-gleam_stdlib = "~> 0.27"
-gleeunit = "~> 0.10"
-# Project used to test if javascript target is detected.
-# https://hex.pm/packages/gleam_module_javascript_test
-gleam_module_javascript_test = "~> 0.2.0"
+gleam_stdlib = ">= 0.58.0 and < 2.0.0"
+gleeunit = ">= 1.0.0 and < 2.0.0"
 
 [javascript.deno]
 allow_read = true
diff --git a/test/running_modules/manifest.toml b/test/running_modules/manifest.toml
index b5ea31984..dca89c33a 100644
--- a/test/running_modules/manifest.toml
+++ b/test/running_modules/manifest.toml
@@ -2,12 +2,10 @@
 # You typically do not need to edit this file
 
 packages = [
-  { name = "gleam_module_javascript_test", version = "0.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_module_javascript_test", source = "hex", outer_checksum = "EB67B6423078E8A37FCB172EC5342BAA1CF45A235F3A9C890532E7487E606E91" },
-  { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" },
-  { name = "gleeunit", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "1397E5C4AC4108769EE979939AC39BF7870659C5AFB714630DEEEE16B8272AD5" },
+  { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" },
+  { name = "gleeunit", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "0E6C83834BA65EDCAAF4FE4FB94AC697D9262D83E6F58A750D63C9F6C8A9D9FF" },
 ]
 
 [requirements]
-gleam_module_javascript_test = { version = "~> 0.2.0" }
-gleam_stdlib = { version = "~> 0.27" }
-gleeunit = { version = "~> 0.10" }
+gleam_stdlib = { version = ">= 0.58.0 and < 2.0.0" }
+gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
diff --git a/test/running_modules/run_tests.sh b/test/running_modules/run_tests.sh
index 8fc82dd03..3e3bab300 100755
--- a/test/running_modules/run_tests.sh
+++ b/test/running_modules/run_tests.sh
@@ -61,10 +61,3 @@ should_fail run --module module/no_main_function
 
 # Main function with wrong arity
 should_fail run --module module/wrong_arity
-
-# try running gleam_module_javascript_test, which will crash if target is erlang
-should_succeed run --module gleam_module_javascript_test
-
-# verify that javascript target doesn't crash and erlang target does crash
-should_succeed run --module gleam_module_javascript_test --target=javascript
-should_fail run --module gleam_module_javascript_test --target=erlang