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

Issue:
Add fluent property wrapper initialization
With #13's addition of `@Init(assignee:type:)`, MemberwiseInit's `@Init` became expressive enough to support property-wrapped members[^1].

But, MemberwiseInit should provide a cleaner way to initialize properties wrapped by property wrappers. Further, `@Init` has become burdened by some seldomly needed parameters and is now Xcode autocomplete-unfriendly. We can improve the situation somewhat.

Here are the new macro definitions:

* `@Init` for standard property initialization behavior.
* `@InitWrapper` to initialize the wrapper of a property-wrapped property.
* `@InitRaw` directly exposes the full configurability of the macro.

```swift
// An escape hatch that embraces the "template" nature of the macro and directly exposes it's configuration.
// 6 arguments
public macro InitRaw(
  _ accessLevel: AccessLevelConfig? = nil,
  assignee: String? = nil,
  default: Any? = nil,  // forward looking
  escaping: Bool? = nil,
  label: String? = nil,
  type: Any.Type? = nil
) = …

// To simplify common usage, forgo 'assignee' and 'type'.
// 4 arguments
public macro Init(
  _ accessLevel: AccessLevelConfig? = nil,
  default: Any? = nil,  // forward looking
  escaping: Bool? = nil,
  label: String? = nil
) = …

// Use the 'assignee' of 'self._\(#propertyName)'.
// 5 arguments
public macro InitWrapper(
  _ accessLevel: AccessLevelConfig? = nil,
  default: Any? = nil,  // forward looking
  escaping: Bool? = nil,,
  label: String? = nil,
  type: Any.Type  // NB: 'type' is required because it can't be inferred, and will always be different than that of the wrapped property
) = …
```

Example:

```swift
import SwiftUI

@propertyWrapper
struct Logged<Value> {
  var wrappedValue: Value {
    didSet {
      print("Logged: \(wrappedValue)")
    }
  }

  init(wrappedValue: Value) {
    self.wrappedValue = wrappedValue
  }
}

// NB: Some property wrappers require initialization of the property
// wrapper itself, hence `@InitWrapper`. Here, we want Logged to be
// initialized without triggering its side effects (logging).

@MemberwiseInit(.public)
public struct CounterView: View {
  // @Logged: InitWrapper
  @InitWrapper(.public, default: "Blob", type: Logged<String>)
  @Logged
  var name1: String

  // @Binding: InitWrapper
  @InitWrapper(.public, type: Binding<Int>)
  @Binding
  var count1: Int


  // @Logged: InitRaw
  @InitRaw(.public, assignee: "self._name1", default: "Blob", type: Logged<String>)
  @Logged
  var name2: String

  // @Binding: InitRaw
  @InitRaw(.public, assignee: "self._count1", type: Binding<Int>)
  @Binding
  var count2: Int


  // @State
  @Init(.public)
  @State var isOn = false

  var body: some View { … }
}
```

[^1]: I *think* `@Init(assignee:type:)` provides complete (if awkward) support for initializing property wrappers. If I'm wrong, I'd love to hear about it.

### swift-memberwise-init-macro version information

0.2.0

Pull request:
* Add `@InitWrapper(type:)` macro.

* Revise `@Init` macro to streamline common usage: Removed 'assignee' and 'type' parameters. This change is non-breaking, as these parameters were added recently and never released (see #13).

* Add `@InitRaw` macro: Provides full configurability over 'accessLevel', 'assignee', 'default', 'escaping', 'label', and 'type'.

* Added diagnostic when multiple `@Init` configurations are applied to a single property.

Resolves #17.

Patch:
diff --git a/Sources/MemberwiseInit/MemberwiseInit.swift b/Sources/MemberwiseInit/MemberwiseInit.swift
index abd9b03..ac43ff9 100644
--- a/Sources/MemberwiseInit/MemberwiseInit.swift
+++ b/Sources/MemberwiseInit/MemberwiseInit.swift
@@ -38,6 +38,29 @@ public enum IgnoreConfig {
 
 @attached(peer)
 public macro Init(
+  _ accessLevel: AccessLevelConfig? = nil,
+  escaping: Bool? = nil,
+  label: String? = nil
+) =
+  #externalMacro(
+    module: "MemberwiseInitMacros",
+    type: "InitMacro"
+  )
+
+@attached(peer)
+public macro InitWrapper(
+  _ accessLevel: AccessLevelConfig? = nil,
+  escaping: Bool? = nil,
+  label: String? = nil,
+  type: Any.Type
+) =
+  #externalMacro(
+    module: "MemberwiseInitMacros",
+    type: "InitMacro"
+  )
+
+@attached(peer)
+public macro InitRaw(
   _ accessLevel: AccessLevelConfig? = nil,
   assignee: String? = nil,
   escaping: Bool? = nil,
diff --git a/Sources/MemberwiseInitClient/main.swift b/Sources/MemberwiseInitClient/main.swift
index 4618c01..b614ef1 100644
--- a/Sources/MemberwiseInitClient/main.swift
+++ b/Sources/MemberwiseInitClient/main.swift
@@ -122,6 +122,62 @@ public struct InferType<T: CaseIterable> {
   var dictionaryAs = ["foo": 1, 3: "bar"] as [AnyHashable: Any]
 }
 
+// - MARK: Usage tour
+
+public typealias SimpleClosure = () -> Void
+
+@propertyWrapper
+public struct Logged<Value> {
+  public var wrappedValue: Value {
+    didSet {
+      print("Logged: \(wrappedValue)")
+    }
+  }
+
+  public init(wrappedValue: Value) {
+    self.wrappedValue = wrappedValue
+  }
+}
+
+@MemberwiseInit(.public)
+public struct Usage<T> {
+  @Init(.ignore) var ignored: T? = nil
+  @Init(.public) var initPublic: T
+  @Init(.public, escaping: true) var initPublicEscaping: SimpleClosure
+  @Init(.public, escaping: true, label: "_") var initPublicEscapingLabel: SimpleClosure
+
+  // Some property wrappers require initialization of the property wrapper
+  // itself, hence `@InitWrapper`.
+  @InitWrapper(type: Logged<String>)
+  @Logged
+  public var nameWithWrapper: String
+
+  // `@InitRaw` enables direct specification of initialization values.
+  @InitRaw(
+    .public,
+    assignee: "self._nameWithWrapperRaw",
+    escaping: false,
+    label: "_",
+    type: Logged<String>
+  )
+  @Logged
+  var nameWithWrapperRaw: String
+}
+
+// - MARK: Diagnostics
+
+//@MemberwiseInit
+//struct Diagnostics<T> {
+//  @Init @InitWrapper @InitRaw
+////                   ┬───────
+////      │            ╰─ 🛑 Multiple @Init configurations are not supported by @MemberwiseInit
+////      ┬───────────
+////      ╰─ 🛑 Multiple @Init configurations are not supported by @MemberwiseInit
+//  let value: T
+//}
+
+// - MARK: @Init(label:)
+
 @MemberwiseInit
 struct S1 {
   @Init(label: "b") let _a: String
diff --git a/Sources/MemberwiseInitMacros/Macros/MemberwiseInitMacro.swift b/Sources/MemberwiseInitMacros/Macros/MemberwiseInitMacro.swift
index 95c0a93..c219b3e 100644
--- a/Sources/MemberwiseInitMacros/Macros/MemberwiseInitMacro.swift
+++ b/Sources/MemberwiseInitMacros/Macros/MemberwiseInitMacro.swift
@@ -118,11 +118,11 @@ public struct MemberwiseInitMacro: MemberMacro {
   }
 
   private static func collectMemberPropertiesAndDiagnostics(
-    from decl: MemberBlockItemListSyntax,
+    from memberBlockItemList: MemberBlockItemListSyntax,
     targetAccessLevel: AccessLevelModifier
   ) throws -> ([MemberProperty], [Diagnostic]) {
     let (variables, variableDiagnostics) = collectMemberVariables(
-      from: decl,
+      from: memberBlockItemList,
       targetAccessLevel: targetAccessLevel
     )
 
@@ -136,10 +136,10 @@ public struct MemberwiseInitMacro: MemberMacro {
   }
 
   private static func collectMemberVariables(
-    from decl: MemberBlockItemListSyntax,
+    from memberBlockItemList: MemberBlockItemListSyntax,
     targetAccessLevel: AccessLevelModifier
   ) -> ([MemberVariable], [Diagnostic]) {
-    decl
+    memberBlockItemList
       .reduce(
         into: (
           variables: [MemberVariable](),
@@ -148,17 +148,31 @@ public struct MemberwiseInitMacro: MemberMacro {
       ) { acc, member in
         guard
           let variable = member.decl.as(VariableDeclSyntax.self),
-          variable.attributes.isEmpty || variable.attributes.contains(attributeNamed: "Init"),
+          variable.attributes.isEmpty || variable.hasCustomConfigurationAttribute,
           variable.modifiersExclude([.static, .lazy])
         else { return }
 
-        var diagnostics = [Diagnostic]()
+        if variable.customConfigurationAttributes.count > 1 {
+          acc.diagnostics += variable.customConfigurationAttributes.dropFirst().map { attribute in
+            Diagnostic(
+              node: attribute,
+              message: MacroExpansionErrorMessage(
+                """
+                Multiple @Init configurations are not supported by @MemberwiseInit
+                """
+              )
+            )
+          }
+          return
+        }
 
         let customSettings = extractVariableCustomSettings(from: variable)
         if let customSettings, customSettings.ignore {
           return
         }
 
+        var diagnostics = [Diagnostic]()
+
         if let customSettings, customSettings.label?.isInvalidSwiftLabel ?? false {
           diagnostics.append(customSettings.diagnosticOnLabelValue(message: .invalidSwiftLabel))
         } else if let customSettings,
@@ -166,7 +180,8 @@ public struct MemberwiseInitMacro: MemberMacro {
           variable.bindings.count > 1
         {
           diagnostics.append(
-            customSettings.diagnosticOnLabel(message: .labelAppliedToMultipleBindings))
+            customSettings.diagnosticOnLabel(message: .labelAppliedToMultipleBindings)
+          )
         }
 
         // TODO: repetition of logic for custom configuration logic
@@ -174,7 +189,7 @@ public struct MemberwiseInitMacro: MemberMacro {
         if targetAccessLevel > effectiveAccessLevel,
           !variable.isFullyInitializedLet
         {
-          let customAccess = variable.customConfiguration?
+          let customAccess = variable.customConfigurationArguments?
             .first?
             .expression
             .as(MemberAccessExprSyntax.self)
@@ -327,50 +342,57 @@ public struct MemberwiseInitMacro: MemberMacro {
   private static func extractVariableCustomSettings(
     from variable: VariableDeclSyntax
   ) -> VariableCustomSettings? {
-    guard let customConfiguration = variable.customConfiguration else { return nil }
+    guard let customConfigurationAttribute = variable.customConfigurationAttribute else {
+      return nil
+    }
+
+    let customConfiguration = variable.customConfigurationArguments
 
     let configuredValues =
-      customConfiguration.compactMap {
+      customConfiguration?.compactMap {
         $0.expression.as(MemberAccessExprSyntax.self)?.declName.baseName.trimmedDescription
       }
 
-    let configuredIgnore = configuredValues.contains("ignore")
+    let configuredIgnore = configuredValues?.contains("ignore") ?? false
 
     let configuredAccessLevel =
-      configuredValues
+      configuredValues?
       .compactMap(AccessLevelModifier.init(rawValue:))
       .first
 
     let configuredForceEscaping =
-      (customConfiguration
+      (customConfiguration?
         .firstWhereLabel("escaping")?
         .expression
         .as(BooleanLiteralExprSyntax.self)?
         .literal
         .text == "true")
-      || configuredValues.contains("escaping")  // Deprecated; remove in 1.0
+      || configuredValues?.contains("escaping") ?? false  // Deprecated; remove in 1.0
 
     let configuredLabel =
-      customConfiguration
+      customConfiguration?
       .firstWhereLabel("label")?
       .expression
       .trimmedStringLiteral
 
     let configuredType =
-      customConfiguration
+      customConfiguration?
       .firstWhereLabel("type")?
       .expression
+      .trimmedDescription
 
     // TODO: Is it possible for invalid type syntax to be provided for an `Any.Type` parameter?
     // NB: All expressions satisfying the `Any.Type` parameter type are parsable to TypeSyntax.
     let configuredTypeSyntax =
-      configuredType.map { TypeSyntax(stringLiteral: $0.trimmedDescription) }
+      configuredType.map(TypeSyntax.init(stringLiteral:))
 
-    let configuredAssignee =
-      customConfiguration
+    let configuredAssignee: VariableCustomSettings.Assignee? =
+      (customConfigurationAttribute.isInitWrapper ? .wrapper : nil)
+      ?? customConfiguration?
       .firstWhereLabel("assignee")?
       .expression
       .trimmedStringLiteral
+      .map(VariableCustomSettings.Assignee.raw)
 
     return VariableCustomSettings(
       accessLevel: configuredAccessLevel,
@@ -379,7 +401,7 @@ public struct MemberwiseInitMacro: MemberMacro {
       ignore: configuredIgnore,
       label: configuredLabel,
       type: configuredTypeSyntax,
-      _syntaxNode: customConfiguration
+      _syntaxNode: customConfigurationAttribute
     )
   }
 
@@ -421,7 +443,16 @@ public struct MemberwiseInitMacro: MemberMacro {
     considering allProperties: [MemberProperty],
     deunderscoreParameters: Bool
   ) -> String {
-    let assignee = property.customSettings?.assignee ?? "self.\(property.name)"
+    let assignee =
+      switch property.customSettings?.assignee {
+      case .none:
+        "self.\(property.name)"
+      case .wrapper:
+        "self._\(property.name)"
+      case let .raw(assignee):
+        assignee
+      }
+
     let parameterName = property.initParameterName(
       considering: allProperties,
       deunderscoreParameters: deunderscoreParameters
@@ -431,16 +462,23 @@ public struct MemberwiseInitMacro: MemberMacro {
 }
 
 private struct VariableCustomSettings: Equatable {
+  enum Assignee: Equatable {
+    case wrapper
+    case raw(String)
+  }
+
   let accessLevel: AccessLevelModifier?
-  let assignee: String?
+  let assignee: Assignee?
   let forceEscaping: Bool
   let ignore: Bool
   let label: String?
   let type: TypeSyntax?
-  let _syntaxNode: LabeledExprListSyntax
+  let _syntaxNode: AttributeSyntax
 
   func diagnosticOnLabel(message: MemberwiseInitMacroDiagnostic) -> Diagnostic {
     let labelNode = self._syntaxNode
+      .arguments?
+      .as(LabeledExprListSyntax.self)?
       .firstWhereLabel("label")
 
     return diagnostic(node: labelNode ?? self._syntaxNode, message: message)
@@ -448,6 +486,8 @@ private struct VariableCustomSettings: Equatable {
 
   func diagnosticOnLabelValue(message: MemberwiseInitMacroDiagnostic) -> Diagnostic {
     let labelValueNode = self._syntaxNode
+      .arguments?
+      .as(LabeledExprListSyntax.self)?
       .firstWhereLabel("label")?
       .expression
 
diff --git a/Sources/MemberwiseInitMacros/Macros/Support/CustomConfiguration.swift b/Sources/MemberwiseInitMacros/Macros/Support/CustomConfiguration.swift
index 753c864..eada2d2 100644
--- a/Sources/MemberwiseInitMacros/Macros/Support/CustomConfiguration.swift
+++ b/Sources/MemberwiseInitMacros/Macros/Support/CustomConfiguration.swift
@@ -1,12 +1,24 @@
 import SwiftSyntax
 
 extension VariableDeclSyntax {
-  var customConfiguration: LabeledExprListSyntax? {
+  var customConfigurationAttributes: [AttributeSyntax] {
     self.attributes
-      .first(where: {
-        $0.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "Init"
-      })?
-      .as(AttributeSyntax.self)?
+      .compactMap { $0.as(AttributeSyntax.self) }
+      .filter {
+        ["Init", "InitWrapper", "InitRaw"].contains($0.attributeName.trimmedDescription)
+      }
+  }
+
+  var customConfigurationAttribute: AttributeSyntax? {
+    self.customConfigurationAttributes.first
+  }
+
+  var hasCustomConfigurationAttribute: Bool {
+    !self.customConfigurationAttributes.isEmpty
+  }
+
+  var customConfigurationArguments: LabeledExprListSyntax? {
+    self.customConfigurationAttribute?
       .arguments?
       .as(LabeledExprListSyntax.self)
   }
@@ -17,3 +29,9 @@ extension LabeledExprListSyntax {
     first(where: { $0.label?.text == label })
   }
 }
+
+extension AttributeSyntax {
+  var isInitWrapper: Bool {
+    self.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "InitWrapper"
+  }
+}
diff --git a/Sources/MemberwiseInitMacros/Macros/Support/DeprecationDiagnostics.swift b/Sources/MemberwiseInitMacros/Macros/Support/DeprecationDiagnostics.swift
index 384830b..4a7b95f 100644
--- a/Sources/MemberwiseInitMacros/Macros/Support/DeprecationDiagnostics.swift
+++ b/Sources/MemberwiseInitMacros/Macros/Support/DeprecationDiagnostics.swift
@@ -16,7 +16,7 @@ private func diagnoseDotEscaping<D: DeclGroupSyntax>(_ decl: D) -> [Diagnostic]
     guard
       let configuration = element.decl
         .as(VariableDeclSyntax.self)?
-        .customConfiguration
+        .customConfigurationArguments
     else { return nil }
 
     let dotEscapingIndex = configuration.firstIndex(


Test patch:
diff --git a/Tests/MemberwiseInitTests/CustomInitRawTests.swift b/Tests/MemberwiseInitTests/CustomInitRawTests.swift
new file mode 100644
index 0000000..31da2ee
--- /dev/null
+++ b/Tests/MemberwiseInitTests/CustomInitRawTests.swift
@@ -0,0 +1,134 @@
+import MacroTesting
+import MemberwiseInitMacros
+import SwiftSyntaxMacros
+import XCTest
+
+final class CustomInitRawTests: 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 testInitRaw() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @InitRaw var type: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        var type: T
+
+        internal init(
+          type: T
+        ) {
+          self.type = type
+        }
+      }
+      """
+    }
+  }
+
+  func testType() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @InitRaw(type: Q) var type: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        var type: T
+
+        internal init(
+          type: Q
+        ) {
+          self.type = type
+        }
+      }
+      """
+    }
+  }
+
+  func testTypeAsGenericExpression() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @InitRaw(type: Q<R>) var type: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        var type: T
+
+        internal init(
+          type: Q<R>
+        ) {
+          self.type = type
+        }
+      }
+      """
+    }
+  }
+
+  func testEverything() {
+    assertMacro {
+      """
+      @MemberwiseInit(.public)
+      public struct S {
+        @InitRaw(.public, assignee: "self.foo", escaping: true, label: "_", type: Q<T>)
+        var initRaw: T
+      }
+      """
+    } expansion: {
+      """
+      public struct S {
+        var initRaw: T
+
+        public init(
+          _ initRaw: @escaping Q<T>
+        ) {
+          self.foo = initRaw
+        }
+      }
+      """
+    }
+  }
+
+  // TODO: Add fix-it diagnostic when provided type is a Metatype
+  //  func testTypeAsMetatype_FailsWithDiagnostic() {
+  //    assertMacro(record: true) {
+  //      """
+  //      @MemberwiseInit
+  //      struct S {
+  //        @Init(type: Q.self) var type: T
+  //      }
+  //      """
+  //    } diagnostics: {
+  //      """
+  //      @MemberwiseInit
+  //      struct S {
+  //        @Init(type: Q.self) var type: T
+  //            ┬─────────────────
+  //            ╰─ 🛑 Invalid use of metatype 'Q.self'. Expected a type, not its metatype.
+  //            ╰─ 🛑 Remove '.self'; type is expected, not a metatype.
+  //      }
+  //      """
+  //    }
+  //  }
+}
diff --git a/Tests/MemberwiseInitTests/CustomInitWrapperTests.swift b/Tests/MemberwiseInitTests/CustomInitWrapperTests.swift
new file mode 100644
index 0000000..19d85c8
--- /dev/null
+++ b/Tests/MemberwiseInitTests/CustomInitWrapperTests.swift
@@ -0,0 +1,116 @@
+import MacroTesting
+import MemberwiseInitMacros
+import SwiftSyntaxMacros
+import XCTest
+
+final class CustomInitWrapperTests: 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,
+        "Init": InitMacro.self,
+        "InitWrapper": InitMacro.self,
+      ]
+    ) {
+      super.invokeTest()
+    }
+  }
+
+  func testType() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @InitWrapper(type: Q<T>)
+        var initWrapper: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        var initWrapper: T
+
+        internal init(
+          initWrapper: Q<T>
+        ) {
+          self._initWrapper = initWrapper
+        }
+      }
+      """
+    }
+  }
+
+  func testEscaping() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @InitWrapper(escaping: true, type: Q<T>)
+        var initWrapper: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        var initWrapper: T
+
+        internal init(
+          initWrapper: @escaping Q<T>
+        ) {
+          self._initWrapper = initWrapper
+        }
+      }
+      """
+    }
+  }
+
+  func testLabel() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @InitWrapper(label: "_", type: Q<T>)
+        var initWrapper: T
+      }
+      """
+    } expansion: {
+      """
+      struct S {
+        var initWrapper: T
+
+        internal init(
+          _ initWrapper: Q<T>
+        ) {
+          self._initWrapper = initWrapper
+        }
+      }
+      """
+    }
+  }
+
+  func testEverything() {
+    assertMacro {
+      """
+      @MemberwiseInit(.public)
+      public struct S {
+        @InitWrapper(.public, escaping: true, label: "_", type: Q<T>)
+        var initWrapper: T
+      }
+      """
+    } expansion: {
+      """
+      public struct S {
+        var initWrapper: T
+
+        public init(
+          _ initWrapper: @escaping Q<T>
+        ) {
+          self._initWrapper = initWrapper
+        }
+      }
+      """
+    }
+  }
+}
diff --git a/Tests/MemberwiseInitTests/MemberwiseInitTests.swift b/Tests/MemberwiseInitTests/MemberwiseInitTests.swift
index f438da0..2848210 100644
--- a/Tests/MemberwiseInitTests/MemberwiseInitTests.swift
+++ b/Tests/MemberwiseInitTests/MemberwiseInitTests.swift
@@ -20,101 +20,6 @@ final class MemberwiseInitTests: XCTestCase {
     }
   }
 
-  // MARK: - Test custom type
-
-  func testCustomType() {
-    assertMacro {
-      """
-      @MemberwiseInit
-      struct S {
-        @Init(type: Q) var type: T
-      }
-      """
-    } expansion: {
-      """
-      struct S {
-        var type: T
-
-        internal init(
-          type: Q
-        ) {
-          self.type = type
-        }
-      }
-      """
-    }
-  }
-
-  func testCustomType_GenericExpression() {
-    assertMacro {
-      """
-      @MemberwiseInit
-      struct S {
-        @Init(type: Q<R>) var type: T
-      }
-      """
-    } expansion: {
-      """
-      struct S {
-        var type: T
-
-        internal init(
-          type: Q<R>
-        ) {
-          self.type = type
-        }
-      }
-      """
-    }
-  }
-
-  // TODO: Add fix-it diagnostic when provided type is a Metatype
-  //  func testCustomType_MetatypeFailsWithDiagnostic() {
-  //    assertMacro(record: true) {
-  //      """
-  //      @MemberwiseInit
-  //      struct S {
-  //        @Init(type: Q.self) var type: T
-  //      }
-  //      """
-  //    } diagnostics: {
-  //      """
-  //      @MemberwiseInit
-  //      struct S {
-  //        @Init(type: Q.self) var type: T
-  //            ┬─────────────────
-  //            ╰─ 🛑 Invalid use of metatype 'Q.self'. Expected a type, not its metatype.
-  //            ╰─ 🛑 Remove '.self'; type is expected, not a metatype.
-  //      }
-  //      """
-  //    }
-  //  }
-
-  // MARK: - Test custom assign
-
-  func testCustomAssign() {
-    assertMacro {
-      """
-      @MemberwiseInit
-      struct S {
-        @Init(assignee: "self._type") var type: T
-      }
-      """
-    } expansion: {
-      """
-      struct S {
-        var type: T
-
-        internal init(
-          type: T
-        ) {
-          self._type = type
-        }
-      }
-      """
-    }
-  }
-
   // MARK: - Test simple usage
 
   // NB: Redundant to AccessLevelTests but handy to have here, too.
@@ -917,6 +822,52 @@ final class MemberwiseInitTests: XCTestCase {
     }
   }
 
+  func testInitAndInit_FailsWithDiagnostic() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init @Init
+        let value: T
+      }
+      """
+    } diagnostics: {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init @Init
+              ┬────
+              ╰─ 🛑 Multiple @Init configurations are not supported by @MemberwiseInit
+        let value: T
+      }
+      """
+    }
+  }
+
+  func testInitInitWrapperInitRaw_FailsWithDiagnostics() {
+    assertMacro {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init @InitWrapper @InitRaw
+        let value: T
+      }
+      """
+    } diagnostics: {
+      """
+      @MemberwiseInit
+      struct S {
+        @Init @InitWrapper @InitRaw
+                           ┬───────
+              │            ╰─ 🛑 Multiple @Init configurations are not supported by @MemberwiseInit
+              ┬───────────
+              ╰─ 🛑 Multiple @Init configurations are not supported by @MemberwiseInit
+        let value: T
+      }
+      """
+    }
+  }
+
   // MARK: - Test invalid syntax
 
   func testInvalidLetProperty_NoExcessiveDiagnostic() {