Repository: gleam-lang/gleam (rust)

Issue:
"Generate function" code action behaves weirdly with function captures
For example, if you trigger it on the following code:
```gleam
pub fn main() {
  something(_, 1)
}
```
It will generate:
```gleam
fn something(_capture: a, int: Int) -> b {
  todo
}
```
Here, it should probably generate a generic name rather than `_capture`

Pull request description:
Fix function generation for function captures
Fixes #4653

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2585ea215..003ffc8a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,11 @@
   unnecessary because it only asserted literal values, when that was not the case.
   ([Surya Rose](https://github.com/GearsDatapacks))
 
+- Fixed a bug where using the "generate function" code action on a function
+  capture would generate an argument named `_capture`, using the internal
+  variable names of the compiler.
+  ([Surya Rose](https://github.com/GearsDatapacks))
+
 ## v1.11.0-rc2 - 2025-05-29
 
 ### Compiler
diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs
index 35c9841c4..d7cd9c1e6 100644
--- a/compiler-core/src/type_/expression.rs
+++ b/compiler-core/src/type_/expression.rs
@@ -3,15 +3,15 @@ use crate::{
     STDLIB_PACKAGE_NAME,
     analyse::{infer_bit_array_option, name::check_argument_names},
     ast::{
-        Arg, Assert, Assignment, AssignmentKind, BinOp, BitArrayOption, BitArraySegment, CallArg,
-        Clause, ClauseGuard, Constant, FunctionLiteralKind, HasLocation, ImplicitCallArgOrigin,
-        Layer, RECORD_UPDATE_VARIABLE, RecordBeingUpdated, SrcSpan, Statement, TodoKind, TypeAst,
-        TypedArg, TypedAssert, TypedAssignment, TypedClause, TypedClauseGuard, TypedConstant,
-        TypedExpr, TypedMultiPattern, TypedStatement, USE_ASSIGNMENT_VARIABLE, UntypedArg,
-        UntypedAssert, UntypedAssignment, UntypedClause, UntypedClauseGuard, UntypedConstant,
-        UntypedConstantBitArraySegment, UntypedExpr, UntypedExprBitArraySegment,
-        UntypedMultiPattern, UntypedStatement, UntypedUse, UntypedUseAssignment, Use,
-        UseAssignment,
+        Arg, Assert, Assignment, AssignmentKind, BinOp, BitArrayOption, BitArraySegment,
+        CAPTURE_VARIABLE, CallArg, Clause, ClauseGuard, Constant, FunctionLiteralKind, HasLocation,
+        ImplicitCallArgOrigin, Layer, RECORD_UPDATE_VARIABLE, RecordBeingUpdated, SrcSpan,
+        Statement, TodoKind, TypeAst, TypedArg, TypedAssert, TypedAssignment, TypedClause,
+        TypedClauseGuard, TypedConstant, TypedExpr, TypedMultiPattern, TypedStatement,
+        USE_ASSIGNMENT_VARIABLE, UntypedArg, UntypedAssert, UntypedAssignment, UntypedClause,
+        UntypedClauseGuard, UntypedConstant, UntypedConstantBitArraySegment, UntypedExpr,
+        UntypedExprBitArraySegment, UntypedMultiPattern, UntypedStatement, UntypedUse,
+        UntypedUseAssignment, Use, UseAssignment,
     },
     build::Target,
     exhaustiveness::{self, CompileCaseResult, CompiledCase, Reachability},
@@ -4266,11 +4266,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                             });
                         }
 
+                        let origin = if name == CAPTURE_VARIABLE {
+                            VariableOrigin::Generated
+                        } else {
+                            VariableOrigin::Variable(name.clone())
+                        };
+
                         // Insert a variable for the argument into the environment
                         body_typer.environment.insert_local_variable(
                             name.clone(),
                             *location,
-                            VariableOrigin::Variable(name.clone()),
+                            origin.clone(),
                             arg.type_.clone(),
                         );
 
@@ -4279,7 +4285,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                             // can identify if it is unused
                             body_typer.environment.init_usage(
                                 name.clone(),
-                                VariableOrigin::Variable(name.clone()),
+                                origin,
                                 arg.location,
                                 body_typer.problems,
                             );


Test patch:
diff --git a/compiler-core/src/language_server/tests/action.rs b/compiler-core/src/language_server/tests/action.rs
index 4c677f0b1..65d9aaa22 100644
--- a/compiler-core/src/language_server/tests/action.rs
+++ b/compiler-core/src/language_server/tests/action.rs
@@ -8627,3 +8627,21 @@ pub fn main(w: mod.Wibble) {
         find_position_of("{}").to_selection(),
     );
 }
+
+// https://github.com/gleam-lang/gleam/issues/4653
+#[test]
+fn generate_function_capture() {
+    assert_code_action!(
+        GENERATE_FUNCTION,
+        "
+fn map(list: List(a), f: fn(a) -> b) -> List(b) {
+  todo
+}
+
+pub fn main() {
+  map([1, 2, 3], add(_, 1))
+}
+",
+        find_position_of("add").to_selection()
+    );
+}
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__generate_function_capture.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__generate_function_capture.snap
new file mode 100644
index 000000000..4c75f7515
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__generate_function_capture.snap
@@ -0,0 +1,29 @@
+---
+source: compiler-core/src/language_server/tests/action.rs
+expression: "\nfn map(list: List(a), f: fn(a) -> b) -> List(b) {\n  todo\n}\n\npub fn main() {\n  map([1, 2, 3], add(_, 1))\n}\n"
+---
+----- BEFORE ACTION
+
+fn map(list: List(a), f: fn(a) -> b) -> List(b) {
+  todo
+}
+
+pub fn main() {
+  map([1, 2, 3], add(_, 1))
+                 ↑         
+}
+
+
+----- AFTER ACTION
+
+fn map(list: List(a), f: fn(a) -> b) -> List(b) {
+  todo
+}
+
+pub fn main() {
+  map([1, 2, 3], add(_, 1))
+}
+
+fn add(int: Int, int_2: Int) -> c {
+  todo
+}