swift-memberwise-init-macro icon indicating copy to clipboard operation
swift-memberwise-init-macro copied to clipboard

The generated initializer doesn't work inside the #Preview macro

Open kuglee opened this issue 1 year ago β€’ 5 comments

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 main branch 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)

kuglee avatar Dec 04 '23 11:12 kuglee

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 #Preview failed 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.

gohanlon avatar Dec 04 '23 20:12 gohanlon

Thanks you for your answer πŸ™

kuglee avatar Dec 05 '23 12:12 kuglee

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

gohanlon avatar Jul 06 '24 00:07 gohanlon

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()
}

gohanlon avatar Jan 20 '25 19:01 gohanlon

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

vvisionnn avatar Jan 27 '25 11:01 vvisionnn