swift-memberwise-init-macro
                                
                                 swift-memberwise-init-macro copied to clipboard
                                
                                    swift-memberwise-init-macro copied to clipboard
                            
                            
                            
                        The generated initializer doesn't work inside the #Preview macro
Description
Thanks for making this macro. I'd like to use it unfortunately the generated initializer doesn't work inside the #Preview macro.
Example:
import SwiftUI
import MemberwiseInit
@MemberwiseInit struct Test {
  let test: Int
}
struct ContentView: View {
  init(test: Test) {}
  var body: some View {
    EmptyView()
  }
}
#Preview {
  ContentView(test: Test(test: 1)) // π 'Test' cannot be constructed because it has no accessible initializers
}
Checklist
- [X] If possible, I've reproduced the issue using the mainbranch of this package.
- [X] This issue hasn't been addressed in an existing GitHub issue or discussion.
Expected behavior
I've expected the code above to work.
Actual behavior
The code above doesn't compile: 'Test' cannot be constructed because it has no accessible initializers
Steps to reproduce
No response
swift-memberwise-init-macro version information
main
Destination operating system
macOS 14.1.1
Xcode version information
15.0 (15A240d)
Swift Compiler version information
swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Thanks for the report! This seems to be a rather unfortunate upstream bug; feedback filed: FB13443736. I also reproduced this bug in the latest Xcode 15.1 beta 3 (15C5059c). I think the best we can do for now is ~~fall back to the old PreviewProvider syntax:~~ wrap the view within another view (see below).
If you inline the #Preview macro in your example using Xcode's "Refactor β Inline Macro" and strip the leading "$" from the generated name, it compiles as expected. That's not a workaround, tho: Xcode won't recognize it as a preview (and the expanded code is unpleasant).
Furthermore, this can be generalized to any closure argument given to a macro:
@MemberwiseInit
public struct ClosureTest {
  @Init(default: { Test(test: 1) }) let name: () -> Test
//                 β¬ββββββββββββ
//                 β°β π 'Test' cannot be constructed because it has no accessible initializers
}
The compiler type-checks the closure argument without expanding macros first. This presents a complex situation where the dependency between macro expansion and type checking isn't properly resolved by the compiler.
Note I found a related issue that's since been resolved where
#Previewfailed similarly when members were added to a type via an extension in the same file. See: #Preview macro canβt find member which is added via an extension and FB 110671628.
Thanks you for your answer π
This issue persists with Xcode 16.0 (16A242d) and Swift 6:
% swift --version      
swift-driver version: 1.115 Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2)
Target: arm64-apple-macosx15.0
While this issue persists, I came across a much better workaround, which I confirmed works in Xcode 16.2 (16C5032a):
import SwiftUI
import MemberwiseInit
@MemberwiseInit
struct Test {
  let test: Int
}
struct ContentView: View {
  init(test: Test) {}
  var body: some View {
    EmptyView()
  }
}
//#Preview {
//  ContentView(test: Test(test: 1)) // π 'Test' cannot be constructed because it has no accessible initializers
//}
// Wrap ContentView in another view to prevent #Preview from directly evaluating the
// macro-generated initializer within its closure context
struct Wrapper: View {
  var body: some View {
    ContentView(test: Test(test: 1))
  }
}
#Preview {
  Wrapper()
}
code inside #Preview macro, it will not be counted into coverage report, but if wrap into a separate view, it will
I'm using the wrapper way a long time since this issue exist, but currently there is no better way to solve it