Is there a way for an attached macro to extend a library type? I tried defining an extension in a peer macro but quickly hit the error:
Macro expansion cannot introduce extension
An example of what would be nice to be possible:
// In library:
public protocol Foo { … }
// In user land:
@Fooable struct Bar { … }
// Generates conformance:
extension Bar: Foo { … }
// Generates peer:
extension Foo where Self == Bar { // 🛑 Not possible?
static let bar = Self(…)
}
Or even more simply:
// In library:
public struct Foo<A> { … }
// In user land:
@Fooable struct Bar { … }
// Generates peer:
extension Foo<Bar> { … }
Is this error just in how I'm holding things? A limitation of macros? If it's a limitation, is it temporary and could the limitation be lifted?
extension declarations can never be produced by a macro. The effect of an extension declaration is wide-ranging, with the ability to add conformances, members, and so on. These capabilities are meant to be introduced in a more fine-grained manner.
I think it's understandable to prohibit arbitrary extensions, but it seems perfectly reasonable to allow extensions for predefined types that ship with the same library that ships the macro. Does another macro provide the functionality for adding members to a library type? Should there be one?
An important consideration for macros is that their effect on name lookup is constrained, so we don't need to run all of the macros in the program in order to serve code completion requests, track incremental compilation updates, resolve member references, and so on. When a macro can only attach members to the type it's applied to, then we only have to run the macro if we're looking for members of the annotated type. If we were going to lift that restriction, I think there'd still have to be a way for the macro declaration to specify what other types it adds members to.
That seems to be completely reasonable to me. In this case the package provides both the macro and the type it wants to extend, so it would be easy to provide this type to the macro.
Something else I've noticed is that default arguments don't appear to be provided to macros...is this a bug?
I'm wondering if there are any temporary workarounds for adding functions from one type to another via an extension.
For example, I'd like to take the functions from a protocol and add them to an existing type (provided by my library) as such:
protocol Foo {
func one()
func two() -> String
func three() -> Int
}
@Provides<Foo>
extension Provider {
// `@Provides<Foo>` would synthesize default implementations for `one()`, `two()`, and `three()`.
}
However, in the macro expansion, I can't access the actual functions on Foo, since they are defined outside the macro context (though correct me if I'm wrong here!).
Ideally I could just add the macro to the protocol itself, however that would require generating extension Provider.
Another option would be to generate a new type struct FooProvider for each protocol, but I'd like to try to keep the logic isolated to that single, library-provided type Provider.
Just wanted to mention that I'm hitting this limitation as well and hope that it gets lifted in the future. My use case is basically this, where each implementation of a protocol needs a corresponding ID declaration: