Repository: gleam-lang/gleam (rust)

Issue:
No error generated for duplicate labels in record updates
When supplying a labeled argument to a function or a record constructor twice, you get a duplicate label error:

```gleam
type Wibble { Wibble(thing: Int, other: Int) }
```

```
error: Duplicate argument
   ┌─ /home/arkan/Projects/oss/gleam/probe/src/probe.gleam:19:29
   │
19 │   let b = Wibble(thing: 10, thing: 20)
   │                             ^^^^^^^^^

The labelled argument `thing` has already been supplied.
```

when using the record update syntax, no such error is produced. Instead, the compiler will emit a "reduntant record update" warning if you repeat a labelled argument often enough:

```
warning: Redundant record update
   ┌─ /home/arkan/Projects/oss/gleam/probe/src/probe.gleam:19:11
   │
19 │   let b = Wibble(..a, thing: 10, thing: 20)
   │           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This record update specifies all fields
```

It just so happens that because of the way codegen works for both targets, the last specified value will be kept.

I'd fix that as part of implementing #745 in #3773 since I need to keep track of all the labels I've seen already anyways, but I wanted to make this issue to document and to ask if emitting an error is actually the desired behaviour? Making this an error could also be seen as a breaking change.

What should I do here? 

Pull request description:
Report duplicate label error in record updates
Fixes #3783
It sounds like this will be fixed when record updates are reworked anyway, but I wanted to fix it as soon as possible in case we release before that change occurs. 

Patch:
diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls
new file mode 100644
index 000000000..d889c7d7f
--- /dev/null
+++ b/.well-known/funding-manifest-urls
@@ -0,0 +1,1 @@
+https://gleam.run/funding.json
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8785830db..d8d2da3cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -394,6 +394,10 @@
   wouldn't emit a warning when the minimum specified Gleam version was < 1.4.0.
   ([yoshi](https://github.com/joshi-monster))
 
+- Fixed a bug where no error would be reported when duplicate labelled
+  arguments were supplied in a record update.
+  ([Surya Rose](https://github.com/GearsDatapacks))
+
 ## v1.5.1 - 2024-09-26
 
 ### Bug Fixes
diff --git a/README.md b/README.md
index 772f23d14..88875b05e 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/Acepie">Ameen Radwan</a> -
   <a href="https://github.com/AndreHogberg">AndreHogberg</a> -
   <a href="https://github.com/ajkachnic">andrew</a> -
-  <a href="https://github.com/aaylward">Andy Aylward</a> -
+  <a href="https://github.com/andriishupta">Andrii Shupta</a> -
   <a href="https://github.com/antharuu">Antharuu</a> -
   <a href="https://github.com/anthony-khong">Anthony Khong</a> -
   <a href="https://github.com/Illbjorn">Anthony Maxwell</a> -
@@ -63,6 +63,8 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/bentomas">Benjamin Thomas</a> -
   <a href="https://github.com/bgwdotdev">bgw</a> -
   <a href="https://github.com/bigtallbill">Bill Nunney</a> -
+  <a href="https://github.com/bjartelund">Bjarte Aarmo Lund</a> -
+  <a href="https://github.com/bmehder">Brad Mehder</a> -
   <a href="https://github.com/brettkolodny">brettkolodny</a> -
   <a href="https://github.com/brian-dawn">Brian Dawn</a> -
   <a href="https://github.com/bglusman">Brian Glusman</a> -
@@ -140,8 +142,10 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/nandofarias">Fernando Farias</a> -
   <a href="https://github.com/ffigiel">Filip Figiel</a> -
   <a href="https://github.com/floriank">Florian Kraft</a> -
+  <a href="https://github.com/francishamel">Francis Hamel</a> -
   <a href="https://github.com/Frank-III">frankwang</a> -
   <a href="https://github.com/gvrooyen">G-J van Rooyen</a> -
+  <a href="https://github.com/gabrielvincent">Gabriel Vincent</a> -
   <a href="https://github.com/hagenek">Georg H. Ekeberg</a> -
   <a href="https://github.com/ggobbe">ggobbe</a> -
   <a href="https://github.com/giacomocavalieri">Giacomo Cavalieri</a> -
@@ -149,6 +153,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/obmarg">Graeme Coupar</a> -
   <a href="https://github.com/grottohub">grotto</a> -
   <a href="https://github.com/nirev">Guilherme de Maio</a> -
+  <a href="https://github.com/guillheu">Guillaume Heu</a> -
   <a href="https://github.com/ghivert">Guillaume Hivert</a> -
   <a href="https://github.com/hammad-r-javed">Hammad Javed</a> -
   <a href="https://github.com/kwando">Hannes Nevalainen</a> -
@@ -188,6 +193,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/jiangplus">jiangplus</a> -
   <a href="https://github.com/hunkyjimpjorps">Jimpjorps™</a> -
   <a href="https://github.com/joeykilpatrick">Joey Kilpatrick</a> -
+  <a href="https://github.com/joeytrapp">Joey Trapp</a> -
   <a href="https://github.com/johan-st">Johan Strand</a> -
   <a href="https://github.com/JohnBjrk">John Björk</a> -
   <a href="https://github.com/johngallagher">John Gallagher</a> -
@@ -242,6 +248,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/KtorZ">Matthias Benkort</a> -
   <a href="https://github.com/maxmcd">Max McDonnell</a> -
   <a href="https://github.com/max-tern">max-tern</a> -
+  <a href="https://github.com/metame">metame</a> -
   <a href="https://github.com/stunthamster">Michael Duffy</a> -
   <a href="https://github.com/michaeljones">Michael Jones</a> -
   <a href="https://github.com/monocursive">Michael Mazurczak</a> -
@@ -252,7 +259,6 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/MoeDevelops">MoeDev</a> -
   <a href="https://github.com/MoritzBoehme">Moritz Böhme</a> -
   <a href="https://github.com/rykawamu">MzRyuKa</a> -
-  <a href="https://github.com/cheesemans">Måns Östman</a> -
   <a href="https://github.com/n8nio">n8n - Workflow Automation</a> -
   <a href="https://github.com/natanaelsirqueira">Natanael Sirqueira</a> -
   <a href="https://github.com/nathanielknight">Nathaniel Knight</a> -
@@ -269,6 +275,7 @@ 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/OldhamMade">OldhamMade</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> -
   <a href="https://github.com/daslaf">Osman Cea</a> -
   <a href="https://github.com/PastMoments">PastMoments</a> -
@@ -306,6 +313,7 @@ Thank you to our sponsors! Gleam would not be possible without you.
   <a href="https://github.com/ustitc">Ruslan Ustitc</a> -
   <a href="https://github.com/samaaron">Sam Aaron</a> -
   <a href="https://github.com/metruzanca">Sam Zanca</a> -
+  <a href="https://github.com/soulsam480">sambit</a> -
   <a href="https://github.com/samifouad">Sami Fouad</a> -
   <a href="https://github.com/bkspace">Sammy Isseyegh</a> -
   <a href="https://github.com/mrgleam">Santi Lertsumran</a> -
diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs
index c92079729..72343688c 100644
--- a/compiler-core/src/type_/expression.rs
+++ b/compiler-core/src/type_/expression.rs
@@ -2470,6 +2470,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
             }
         }
 
+        let mut seen_labels = HashSet::new();
+
         let args: Vec<TypedRecordUpdateArg> = args
             .iter()
             .map(
@@ -2490,6 +2492,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                         self.track_feature_usage(FeatureKind::LabelShorthandSyntax, *location);
                     }
 
+                    if seen_labels.contains(label) {
+                        return Err(Error::DuplicateArgument {
+                            location: *location,
+                            label: label.clone(),
+                        });
+                    }
+                    _ = seen_labels.insert(label.clone());
+
                     // Check that the update argument unifies with the corresponding
                     // field in the record contained within the record variable. We
                     // need to check the record, and not the constructor, in order


Test patch:
diff --git a/compiler-core/src/type_/tests/errors.rs b/compiler-core/src/type_/tests/errors.rs
index b8b0b1be3..43a438458 100644
--- a/compiler-core/src/type_/tests/errors.rs
+++ b/compiler-core/src/type_/tests/errors.rs
@@ -2624,3 +2624,18 @@ pub fn main() {
 "#
     );
 }
+
+#[test]
+// https://github.com/gleam-lang/gleam/issues/3783
+fn duplicate_fields_in_record_update_reports_error() {
+    assert_module_error!(
+        "
+pub type Wibble { Wibble(thing: Int, other: Int) }
+
+pub fn main() {
+  let wibble = Wibble(1, 2)
+  let wobble = Wibble(..wibble, thing: 1, thing: 2)
+}
+"
+    );
+}
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_fields_in_record_update_reports_error.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_fields_in_record_update_reports_error.snap
new file mode 100644
index 000000000..50eae5de2
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_fields_in_record_update_reports_error.snap
@@ -0,0 +1,22 @@
+---
+source: compiler-core/src/type_/tests/errors.rs
+expression: "\npub type Wibble { Wibble(thing: Int, other: Int) }\n\npub fn main() {\n  let wibble = Wibble(1, 2)\n  let wobble = Wibble(..wibble, thing: 1, thing: 2)\n}\n"
+---
+----- SOURCE CODE
+
+pub type Wibble { Wibble(thing: Int, other: Int) }
+
+pub fn main() {
+  let wibble = Wibble(1, 2)
+  let wobble = Wibble(..wibble, thing: 1, thing: 2)
+}
+
+
+----- ERROR
+error: Duplicate argument
+  ┌─ /src/one/two.gleam:6:43
+  │
+6 │   let wobble = Wibble(..wibble, thing: 1, thing: 2)
+  │                                           ^^^^^^^^
+
+The labelled argument `thing` has already been supplied.