Repository: gleam-lang/gleam (rust)

Issue:
Code action for "Inline variable" not always working on record update arguments
The following snippet offers "Inline variable" 3 times in the code actions list

```gleam
type Couple {
  Couple(l: Int, r: Int)
}

pub fn main() {
  let c1 = Couple(l: 1, r: 1)
  let c2 = Couple(..c1, r: 1)
                    ^
}
```

The first option is correct.

Some of the options (the second and third in my case) offer this replacement
```gleam
pub fn main() {
  let c1 = Couple(lCouple(..c1, r: 1)1, r: 1)
}
```

or this if the selection is this
```gleam
  let c2 = Couple(..c1, r: 1)
                     ^
```

the replacement is 
```gleam
  let c1 = Couple(l:Couple(..c1, r: 1), r: 1)
```

Pull request:
Fix bug with "inline variable" and record updates
Fixes #4342 
As always, I forgot to check for generated assignments/variables.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index db39aeec9..c8c84fea6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -174,6 +174,10 @@
   record capture produces invalid code.
   ([Matias Carlander](https://github.com/matiascr))
 
+- Fixed a bug where the "Inline variable" code action would not work properly
+  if used inside a record update.
+  ([Surya Rose](https://github.com/GearsDatapacks))
+
 ## v1.9.1 - 2025-03-10
 
 ### Formatter
diff --git a/compiler-core/src/language_server/code_action.rs b/compiler-core/src/language_server/code_action.rs
index a5436308b..d55c726e2 100644
--- a/compiler-core/src/language_server/code_action.rs
+++ b/compiler-core/src/language_server/code_action.rs
@@ -4744,6 +4744,12 @@ impl<'a> InlineVariable<'a> {
             return;
         }
 
+        // If the assignment was generated by the compiler, it doesn't have a
+        // syntactical representation, so we can't inline it.
+        if matches!(assignment.kind, AssignmentKind::Generated) {
+            return;
+        }
+
         let value_location = assignment.value.location();
         let value = self
             .module
@@ -4788,11 +4794,21 @@ impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
             return;
         }
 
-        let type_::ValueConstructorVariant::LocalVariable { location, .. } = &constructor.variant
+        let type_::ValueConstructorVariant::LocalVariable { location, origin } =
+            &constructor.variant
         else {
             return;
         };
 
+        // We can only inline it if it comes from a regular variable pattern,
+        // not a complex pattern or generated assignment.
+        match origin {
+            VariableOrigin::Variable(_) => {}
+            VariableOrigin::LabelShorthand(_)
+            | VariableOrigin::AssignmentPattern
+            | VariableOrigin::Generated => return,
+        }
+
         self.maybe_inline(*location);
     }
 
@@ -4801,8 +4817,16 @@ impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
         location: &'ast SrcSpan,
         _name: &'ast EcoString,
         _type: &'ast Arc<Type>,
-        _origin: &'ast VariableOrigin,
+        origin: &'ast VariableOrigin,
     ) {
+        // We can only inline it if it is a regular variable pattern
+        match origin {
+            VariableOrigin::Variable(_) => {}
+            VariableOrigin::LabelShorthand(_)
+            | VariableOrigin::AssignmentPattern
+            | VariableOrigin::Generated => return,
+        }
+
         let range = self.edits.src_span_to_lsp_range(*location);
 
         if !overlaps(self.params.range, range) {


Test patch:
diff --git a/compiler-core/src/language_server/tests/action.rs b/compiler-core/src/language_server/tests/action.rs
index 72351bb21..711cbbadd 100644
--- a/compiler-core/src/language_server/tests/action.rs
+++ b/compiler-core/src/language_server/tests/action.rs
@@ -5940,3 +5940,22 @@ pub fn main() {
         find_position_of("wibble").to_selection()
     );
 }
+
+// https://github.com/gleam-lang/gleam/issues/4342
+#[test]
+fn inline_variable_in_record_update() {
+    assert_code_action!(
+        INLINE_VARIABLE,
+        "
+type Couple {
+  Couple(l: Int, r: Int)
+}
+
+pub fn main() {
+  let c1 = Couple(l: 1, r: 1)
+  let c2 = Couple(..c1, r: 1)
+}
+",
+        find_position_of("c1,").to_selection()
+    );
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__inline_variable_in_record_update.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__inline_variable_in_record_update.snap
new file mode 100644
index 000000000..9fa9b6cfa
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__inline_variable_in_record_update.snap
@@ -0,0 +1,26 @@
+---
+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 = Couple(..c1, r: 1)\n}\n"
+---
+----- BEFORE ACTION
+
+type Couple {
+  Couple(l: Int, r: Int)
+}
+
+pub fn main() {
+  let c1 = Couple(l: 1, r: 1)
+  let c2 = Couple(..c1, r: 1)
+                    ↑        
+}
+
+
+----- AFTER ACTION
+
+type Couple {
+  Couple(l: Int, r: Int)
+}
+
+pub fn main() {
+  let c2 = Couple(..Couple(l: 1, r: 1), r: 1)
+}