Repository: gohanlon/swift-memberwise-init-macro (swift)

Issue:
Crash when given invalid redeclaration
### Description

The following invalid Swift will cause MemberwiseInit to crash with a fatal error:

```swift
@MemberwiseInit
struct S {
  let x: Int
  let x: Int
}
```

This will also crash: `let x, x: Int`.

### Checklist

- [X] If possible, I've reproduced the issue using the `main` branch of this package.
- [X] This issue hasn't been addressed in an [existing GitHub issue](https://github.com/gohanlon/swift-memberwise-init-macro/issues) or [discussion](https://github.com/gohanlon/swift-memberwise-init-macro/discussions).

### Expected behavior

MemberwiseInit should never crash. It should either emit invalid Swift code or fail silently.

### swift-memberwise-init-macro version information

main, as of db35b64.

Pull request:
Fixes #27.

Crash was when encountering invalid Swift code with redeclarations of properties, e.g., `let x: Int; let x: Int` or `let x, x: Int`.

Patch:
diff --git a/Sources/MemberwiseInitMacros/Macros/Support/Diagnostics.swift b/Sources/MemberwiseInitMacros/Macros/Support/Diagnostics.swift
index 029284d..9343c67 100644
--- a/Sources/MemberwiseInitMacros/Macros/Support/Diagnostics.swift
+++ b/Sources/MemberwiseInitMacros/Macros/Support/Diagnostics.swift
@@ -142,15 +142,15 @@ func customInitLabelDiagnosticsFor(bindings: [PropertyBinding]) -> [Diagnostic]
 func customInitLabelDiagnosticsFor(properties: [MemberProperty]) -> [Diagnostic] {
   var diagnostics: [Diagnostic] = []
 
-  let propertiesByName = Dictionary(uniqueKeysWithValues: properties.map { ($0.name, $0) })
+  let propertiesByName = Dictionary(grouping: properties, by: { $0.name })
 
   // Diagnose custom label conflicts with a property
   for property in properties {
     guard
       let propertyCustomSettings = property.customSettings,
       let label = propertyCustomSettings.label,
-      let duplicated = propertiesByName[label],
-      duplicated != property
+      let duplicates = propertiesByName[label],
+      duplicates.contains(where: { $0 != property })
     else { continue }
 
     diagnostics.append(


Test patch:
diff --git a/Tests/MemberwiseInitTests/InvalidSyntaxTests.swift b/Tests/MemberwiseInitTests/InvalidSyntaxTests.swift
new file mode 100644
index 0000000..6f30e95
--- /dev/null
+++ b/Tests/MemberwiseInitTests/InvalidSyntaxTests.swift
@@ -0,0 +1,92 @@
+import MacroTesting
+import MemberwiseInitMacros
+import XCTest
+
+final class InvalidSyntaxTests: XCTestCase {
+  override func invokeTest() {
+    // NB: Waiting for swift-macro-testing PR to support explicit indentationWidth: https://github.com/pointfreeco/swift-macro-testing/pull/8
+    withMacroTesting(
+      //indentationWidth: .spaces(2),
+      macros: [
+        "MemberwiseInit": MemberwiseInitMacro.self,
+        "InitRaw": InitMacro.self,
+      ]
+    ) {
+      super.invokeTest()
+    }
+  }
+
+  func testInvalidRedeclaration_SucceedsWithInvalidCode() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        let x: T
+        let x: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        let x: T
+        let x: T
+
+        internal init(
+          x: T,
+          x: T
+        ) {
+          self.x = x
+          self.x = x
+        }
+      }
+      """
+    }
+  }
+
+  func testInvalidRedeclaration2_SucceedsWithInvalidCode() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        let x, x: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        let x, x: T
+
+        internal init(
+          x: T,
+          x: T
+        ) {
+          self.x = x
+          self.x = x
+        }
+      }
+      """
+    }
+  }
+
+  func testInvalidRedeclarationAndCustomLabelConflictsWithPropertyName_FailsWithDiagnostic() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(label: "x") let x: T
+        let x: T
+      }
+      """
+    } diagnostics: {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(label: "x") let x: T
+                     ┬──
+                     ╰─ 🛑 Label 'x' conflicts with a property name
+        let x: T
+      }
+      """
+    }
+  }
+}