Repository: gohanlon/swift-memberwise-init-macro
Pull request #24 (2023-12-03 19:52:30)

Fix precedence of initializers over `@Init(default:)`
* Ensure that initializer values for both 'let' and 'var' properties take precedence over `@Init(default:)`. This aligns with behavior of ignoreing initialized 'let' properties, maintaining consistency across property types. (And, doesn't "override" Swift behavior.)

* Add tests to verify correct precedence of initial values and more.


Patch:
diff --git a/Sources/MemberwiseInitMacros/Macros/MemberwiseInitMacro.swift b/Sources/MemberwiseInitMacros/Macros/MemberwiseInitMacro.swift
index cf341aa..0d88807 100644
--- a/Sources/MemberwiseInitMacros/Macros/MemberwiseInitMacro.swift
+++ b/Sources/MemberwiseInitMacros/Macros/MemberwiseInitMacro.swift
@@ -434,8 +434,8 @@ public struct MemberwiseInitMacro: MemberMacro {
     optionalsDefaultNil: Bool
   ) -> String {
     let defaultValue =
-      property.customSettings?.defaultValue.map { " = \($0.description)" }
-      ?? property.initializerValue.map { " = \($0.description)" }
+      property.initializerValue.map { " = \($0.description)" }
+      ?? property.customSettings?.defaultValue.map { " = \($0.description)" }
       ?? (optionalsDefaultNil && property.type.isOptionalType ? " = nil" : "")
 
     let escaping =


Test patch:
diff --git a/Tests/MemberwiseInitTests/CustomInitDefaultTests.swift b/Tests/MemberwiseInitTests/CustomInitDefaultTests.swift
new file mode 100644
index 0000000..bb7afdb
--- /dev/null
+++ b/Tests/MemberwiseInitTests/CustomInitDefaultTests.swift
@@ -0,0 +1,231 @@
+import MacroTesting
+import MemberwiseInitMacros
+import XCTest
+
+final class CustomInitDefaultTests: 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 testLet() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) let number: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) let number: T
+
+        internal init(
+          number: T = 42
+        ) {
+          self.number = number
+        }
+      }
+      """
+    }
+  }
+
+  func testVar() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) var number: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) var number: T
+
+        internal init(
+          number: T = 42
+        ) {
+          self.number = number
+        }
+      }
+      """
+    }
+  }
+
+  // TODO: For 1.0, diagnostic on nonsensical @Init(default:)?
+  func testInitializedLet() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) let number = 0
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) let number = 0
+
+        internal init() {
+        }
+      }
+      """
+    }
+  }
+
+  func testInitializedVar_InitializerWins() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) var number = 0
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) var number = 0
+
+        internal init(
+          number: Int = 0
+        ) {
+          self.number = number
+        }
+      }
+      """
+    }
+  }
+
+  func testLetWithMultipleBindings() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) let x, y: Int
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) let x, y: Int
+
+        internal init(
+          x: Int = 42,
+          y: Int = 42
+        ) {
+          self.x = x
+          self.y = y
+        }
+      }
+      """
+    }
+  }
+
+  func testVarWithMultipleBindings() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) var x, y: Int
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) var x, y: Int
+
+        internal init(
+          x: Int = 42,
+          y: Int = 42
+        ) {
+          self.x = x
+          self.y = y
+        }
+      }
+      """
+    }
+  }
+
+  func testLetWithFirstBindingInitialized() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) let x = 0, y: Int
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) let x = 0, y: Int
+
+        internal init(
+          y: Int = 42
+        ) {
+          self.y = y
+        }
+      }
+      """
+    }
+  }
+
+  func testVarWithFirstBindingInitialized() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) var x = 0, y: Int
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) var x = 0, y: Int
+
+        internal init(
+          x: Int = 0,
+          y: Int = 42
+        ) {
+          self.x = x
+          self.y = y
+        }
+      }
+      """
+    }
+  }
+
+  func testLetWithRaggedBindings_SucceedsWithInvalidCode() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init(default: 42) let x: Int, isOn: Bool
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        @Init(default: 42) let x: Int, isOn: Bool
+
+        internal init(
+          x: Int = 42,
+          isOn: Bool = 42
+        ) {
+          self.x = x
+          self.isOn = isOn
+        }
+      }
+      """
+    }
+  }
+}
diff --git a/Tests/MemberwiseInitTests/MemberwiseInitTests.swift b/Tests/MemberwiseInitTests/MemberwiseInitTests.swift
index 42fee7a..bc348f6 100644
--- a/Tests/MemberwiseInitTests/MemberwiseInitTests.swift
+++ b/Tests/MemberwiseInitTests/MemberwiseInitTests.swift
@@ -2016,31 +2016,6 @@ final class MemberwiseInitTests: XCTestCase {
     }
   }
 
-  // MARK: - Test default value
-
-  func testInitDefault() {
-    assertMacro {
-      """
-      @MemberwiseInit
-      public struct S<T: Numeric> {
-        @Init(default: 0) let number: T
-      }
-      """
-    } expansion: {
-      """
-      public struct S<T: Numeric> {
-        let number: T
-
-        internal init(
-          number: T = 0
-        ) {
-          self.number = number
-        }
-      }
-      """
-    }
-  }
-
   // MARK: - Test _optionalsDefaultNil (experimental)
 
   func testOptionalLetProperty_InternalInitNoDefault() {