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:
I have something very similar as requirement. I got to the end of my Macro implementation just to find out this wasnt possible . It would have been a clean error free thing to implement for my dev team.
class Type1ActionHandler { }
extension MainActionHandler {
var type1Handler: Type1ActionHandler? {
self as? Type1ActionHandler?
}
}
This is exactly what I wanted to do as well. Bummer! Hopefully this limitation is lifted eventually. Looks like you can't even do extensions with freestanding macros.
This isnβt supported because itβs not a good fit for incremental compilation. If any macro expansion could introduce an extension of an arbitrary nominal type, the compiler would have to expand all macros before it could perform one name lookup β as you can imagine, this might be a problem if your module has 700 files, but only one needs to be recompiled.
The macro definition could specify the type being extended, along with the conformances and names. This would allow the compiler to only expand it when necessary (although it would have to look through all of the macro definitions to find the information, instead of just macros attached to that type).