Repository: gleam-lang/gleam (rust)

Issue:
Cannot use `x0` as record field label with positional field on JavaScript target
<!--
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`
-->

I just randomly tried this for fun after seeing the JavaScript generated code for positional fields:

```gleam
type Test {
  Test(Int, x0: Int)
}
```

The generated code has duplicate constructor arguments:

```javascript
import { CustomType as $CustomType } from "./gleam.mjs";

class Test extends $CustomType {
  constructor(x0, x0) {
    super();
    this[0] = x0;
    this.x0 = x0;
  }
}
```

Running with `gleam run --target javascript` yields the following error:

```
<file path>:
  constructor(x0, x0) {
                  ^^

SyntaxError: Duplicate parameter name not allowed in this context
    at compileSourceTextModule (node:internal/modules/esm/utils:338:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:102:18)
    at #translate (node:internal/modules/esm/loader:468:12)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:515:27)

Node.js v22.14.0
```

I'm using Gleam 1.10.0 on NixOS.

Pull request description:
Fix duplicate field bug
Fixes #4603

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67edae5d9..62480afd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,10 @@
   properly in the generated documentation.
   ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
 
+- Fixed a bug where fields name `x0` in records could cause invalid code to be
+  generated on the JavaScript target.
+  ([Surya Rose](https://github.com/GearsDatapacks))
+
 ## v1.11.0-rc1 - 2025-05-15
 
 ### Compiler
diff --git a/compiler-core/src/javascript.rs b/compiler-core/src/javascript.rs
index 47aa5e31a..2c59724af 100644
--- a/compiler-core/src/javascript.rs
+++ b/compiler-core/src/javascript.rs
@@ -340,7 +340,7 @@ impl<'a> Generator<'a> {
             arg.label
                 .as_ref()
                 .map(|(_, s)| maybe_escape_identifier(s))
-                .unwrap_or_else(|| eco_format!("x{i}"))
+                .unwrap_or_else(|| eco_format!("${i}"))
                 .to_doc()
         }
 
diff --git a/compiler-core/src/language_server/code_action.rs b/compiler-core/src/language_server/code_action.rs
index d11787ba3..b843af8b5 100644
--- a/compiler-core/src/language_server/code_action.rs
+++ b/compiler-core/src/language_server/code_action.rs
@@ -6655,7 +6655,9 @@ impl<'a> RemoveEchos<'a> {
 
     fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) {
         for i in 0..statements.len() {
-            let statement = statements.get(i).unwrap();
+            let statement = statements
+                .get(i)
+                .expect("Statement must exist in iteration");
             let next_statement = statements.get(i + 1);
             let is_last = i == statements.len() - 1;
 
diff --git a/compiler-core/src/parse/extra.rs b/compiler-core/src/parse/extra.rs
index dbf64c7b2..085383bd0 100644
--- a/compiler-core/src/parse/extra.rs
+++ b/compiler-core/src/parse/extra.rs
@@ -50,8 +50,8 @@ impl ModuleExtra {
                     Ordering::Equal
                 }
             })
-            .map(|index| self.comments.get(index).unwrap().clone())
             .ok()
+            .and_then(|index| self.comments.get(index).copied())
     }
 }
 


Test patch:
diff --git a/compiler-core/src/javascript/tests/records.rs b/compiler-core/src/javascript/tests/records.rs
index b8a610fae..6be5a324d 100644
--- a/compiler-core/src/javascript/tests/records.rs
+++ b/compiler-core/src/javascript/tests/records.rs
@@ -68,3 +68,15 @@ pub fn get_name(person: Person) { person.name }
 pub fn get_age(person: Person) { person.age }"
     );
 }
+
+// https://github.com/gleam-lang/gleam/issues/4603
+#[test]
+fn field_named_x0() {
+    assert_js!(
+        "
+pub type Wibble {
+  Wibble(Int, x0: String)
+}
+"
+    );
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_fails.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_fails.snap
index b4c3d6641..9634d5df8 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_fails.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_fails.snap
@@ -19,16 +19,16 @@ fn go() {
 import { CustomType as $CustomType, makeError } from "../gleam.mjs";
 
 class Wibble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
 class Wobble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_succeeds.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_succeeds.snap
index 4a1b1979a..f2407ca74 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_succeeds.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_succeeds.snap
@@ -18,9 +18,9 @@ fn go() {
 import { CustomType as $CustomType } from "../gleam.mjs";
 
 class Wibble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_with_multiple_variants.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_with_multiple_variants.snap
index 07f305151..f201baa8d 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_with_multiple_variants.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_with_multiple_variants.snap
@@ -20,23 +20,23 @@ fn go() {
 import { CustomType as $CustomType, makeError } from "../gleam.mjs";
 
 class Wibble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
 class Wobble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
 class Woo extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__catch_all_assert.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__catch_all_assert.snap
index 610903b84..081881ad9 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__catch_all_assert.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__catch_all_assert.snap
@@ -19,16 +19,16 @@ fn go() {
 import { CustomType as $CustomType } from "../gleam.mjs";
 
 class Wibble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
 class Wobble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__use_discard_assignment.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__use_discard_assignment.snap
index 5a48909f8..57d2d8721 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__use_discard_assignment.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__use_discard_assignment.snap
@@ -22,23 +22,23 @@ fn go() {
 import { CustomType as $CustomType } from "../gleam.mjs";
 
 class Wibble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
 class Wobble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
 class Woo extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap
index d652841db..7e9a32e30 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap
@@ -27,9 +27,9 @@ pub fn main() {
 import { CustomType as $CustomType, makeError } from "../gleam.mjs";
 
 class Wibble extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_constructor_gets_pure_annotation.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_constructor_gets_pure_annotation.snap
index e495bcd25..cbab776d5 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_constructor_gets_pure_annotation.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_constructor_gets_pure_annotation.snap
@@ -16,10 +16,10 @@ const y = X(1, [])
 import { toList, CustomType as $CustomType } from "../gleam.mjs";
 
 export class X extends $CustomType {
-  constructor(x0, x1) {
+  constructor($0, $1) {
     super();
-    this[0] = x0;
-    this[1] = x1;
+    this[0] = $0;
+    this[1] = $1;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_list_with_constructors_gets_pure_annotation.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_list_with_constructors_gets_pure_annotation.snap
index 502e988bf..173575262 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_list_with_constructors_gets_pure_annotation.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_list_with_constructors_gets_pure_annotation.snap
@@ -16,10 +16,10 @@ const y = [X(1, ["1"])]
 import { toList, CustomType as $CustomType } from "../gleam.mjs";
 
 export class X extends $CustomType {
-  constructor(x0, x1) {
+  constructor($0, $1) {
     super();
-    this[0] = x0;
-    this[1] = x1;
+    this[0] = $0;
+    this[1] = $1;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_tuple_with_constructors_gets_pure_annotation.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_tuple_with_constructors_gets_pure_annotation.snap
index ac7891a19..7774329fb 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_tuple_with_constructors_gets_pure_annotation.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_tuple_with_constructors_gets_pure_annotation.snap
@@ -16,10 +16,10 @@ const y = #(X(1, ["1"]))
 import { toList, CustomType as $CustomType } from "../gleam.mjs";
 
 export class X extends $CustomType {
-  constructor(x0, x1) {
+  constructor($0, $1) {
     super();
-    this[0] = x0;
-    this[1] = x1;
+    this[0] = $0;
+    this[1] = $1;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__destructure_custom_type_with_mixed_fields_first_unlabelled.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__destructure_custom_type_with_mixed_fields_first_unlabelled.snap
index 0a7a5d91a..2ead1f8e1 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__destructure_custom_type_with_mixed_fields_first_unlabelled.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__destructure_custom_type_with_mixed_fields_first_unlabelled.snap
@@ -21,9 +21,9 @@ fn go(cat) {
 import { CustomType as $CustomType } from "../gleam.mjs";
 
 class Cat extends $CustomType {
-  constructor(x0, cuteness) {
+  constructor($0, cuteness) {
     super();
-    this[0] = x0;
+    this[0] = $0;
     this.cuteness = cuteness;
   }
 }
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__long_name_variant_without_labels.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__long_name_variant_without_labels.snap
index 5b99169fb..206819e22 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__long_name_variant_without_labels.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__long_name_variant_without_labels.snap
@@ -18,13 +18,13 @@ fn go() {
 import { CustomType as $CustomType } from "../gleam.mjs";
 
 class TypeWithALongNameAndSeveralArguments extends $CustomType {
-  constructor(x0, x1, x2, x3, x4) {
+  constructor($0, $1, $2, $3, $4) {
     super();
-    this[0] = x0;
-    this[1] = x1;
-    this[2] = x2;
-    this[3] = x3;
-    this[4] = x4;
+    this[0] = $0;
+    this[1] = $1;
+    this[2] = $2;
+    this[3] = $3;
+    this[4] = $4;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unnamed_fields.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unnamed_fields.snap
index b25264d1d..a06b01c26 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unnamed_fields.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unnamed_fields.snap
@@ -29,9 +29,9 @@ fn destructure(x) {
 import { CustomType as $CustomType } from "../gleam.mjs";
 
 class Ip extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__two_pipes_in_a_row.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__two_pipes_in_a_row.snap
index 93133b695..5e759eaf8 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__two_pipes_in_a_row.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__two_pipes_in_a_row.snap
@@ -17,9 +17,9 @@ pub fn main() {
 import { toList, CustomType as $CustomType } from "../gleam.mjs";
 
 export class Function extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }
 
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__field_named_x0.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__field_named_x0.snap
new file mode 100644
index 000000000..0a7627df5
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__field_named_x0.snap
@@ -0,0 +1,21 @@
+---
+source: compiler-core/src/javascript/tests/records.rs
+expression: "\npub type Wibble {\n  Wibble(Int, x0: String)\n}\n"
+---
+----- SOURCE CODE
+
+pub type Wibble {
+  Wibble(Int, x0: String)
+}
+
+
+----- COMPILED JAVASCRIPT
+import { CustomType as $CustomType } from "../gleam.mjs";
+
+export class Wibble extends $CustomType {
+  constructor($0, x0) {
+    super();
+    this[0] = $0;
+    this.x0 = x0;
+  }
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___patterns.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___patterns.snap
index c5217c888..663c08917 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___patterns.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___patterns.snap
@@ -22,9 +22,9 @@ fn apply(arg, fun) {
 import { CustomType as $CustomType } from "../gleam.mjs";
 
 class Box extends $CustomType {
-  constructor(x0) {
+  constructor($0) {
     super();
-    this[0] = x0;
+    this[0] = $0;
   }
 }