Can you apply macros to types defined in other modules?

I’m excited for the official release of macros in a couple of weeks, but one thing that was unclear to me was whether or not they can be applied to types defined in other modules.

Specifically I’m interested in autogenerating mocks and protocols from some existing concrete types. Today if I want to mock CLLocationManager I need to make a LocationManager protocol, conform CLLocationManager to it, and create a MockLocationManager type that also conforms to this protocol.

I would love to create an @Mockable attached macro which generates all of that from the concrete class. I believe I can do this for types I’m building from source, but I’m not sure if this works for types in other modules.

I understand that macros which modify the AST of the type they apply to would not work, but would it be possible to use the attached(peer) or freestanding macro types? I would understand if the only AST visible to the macro is the “stub” you see when you command-click these types.

1 Like

I have the same ask, but my use case is diffrerent from yours. I also want to generate mock implementation from the protocol declarations, but within a large codebase, protocol lives in the public module but I only want to generate the mock within Test modules, to avoid leaking mock to the concrete implementations and bloat binary sizes.

@GenerateTestMock
protocol MyProtocol {
  func requirement(...)
}

Generated conformances: I want this in the test module only

final class MyProtocolMock: MyProtocol {
  func requirement(...)
  func stub_requirement(..., andReturn value: ...)
  ...
}

Hello DJBen,
did you managed how to implement your mock idea ?
I have the same need...

@Les after researching it a bit more, I'm pretty sure you can't apply them to external modules, though for @DJBen 's use-case, we figured out a workaround. You can have a Peer macro that generates code wrapped in a conditional compilation block to exclude those mocks from your production application.

E.g. in his example:

@GenerateTestMock
protocol MyProtocol {
  func requirement(...)
}

Could resolve to:

#if DEBUG

final class MyProtocolMock: MyProtocol {
  func requirement(...)
  func stub_requirement(..., andReturn value: ...)
  ...
}

#endif

protocol MyProtocol {
  func requirement(...)
}

This has the benefit of being available wherever the mocked protocol is, and it doesn't get compiled or linked into your production app.

Hello @Logan_Shire,
Thanks for reply, and ideas you shared. If test module if generated by xcodegen, and only it has ability to import Spy than it's impossible with your example to implement Mock.
Anyway - thank for answer, hope it would be possible in future

In the context of Introducing Spyable: A Swift Macro for Automatic Spies Generation, we encountered a similar requirement—generating peers in other targets. As per the current state of the language feature, achieving this is not feasible. Our approach aligns with that of @Logan_Shire , with the distinction that we offer greater flexibility. Rather than solely relying on the DEBUG flag, we allow folks to define their own flags. If anyone is interested in the implementation, you can find the corresponding pull request here: Provide a flag parameter to allow limiting spyable availability by dafurman · Pull Request #64 · Matejkob/swift-spyable · GitHub