I think this proposal fills an important gap in the macro system. Member macros are already being used as a tool to generate witnesses to fulfill a protocol conformance, and I believe the member macro should have access to the same information that the extension macro has when generating the code that states the conformance itself. I also believe that this proposal together with extension macros subsumes the need for witness macros as described by the macro vision document. There are two implications of this approach versus witness macros that I can think of:
- A conformance in the original source can be inherited from a super class, written explicitly, or implied by another protocol conformance. Because the macro cannot differentiate inherited and implied conformances from explicitly written ones, it means the programmer is expected to implement the entire protocol conformance if they explicitly state the conformance themselves, e.g.
@attached(member, conformances: Codable)
@attached(extension, conformances: Codable)
macro ImplementCodable = ...
@ImplementCodable
struct S { ... }
extension S: Codable {} // presumably this will prevent '@ImplementCodable' from generating any of the witnesses
In other words, whether or not to generate witnesses is determined at the level of stating the protocol conformance instead of fulfilling each witness individually. I think this is the right level of granularity for macros that can generate conformances because it's straightforward to reason about, and it runs a much lower risk of circularity due to needing to compute semantic information to pass to the macro. By stating the conformances in the attached macro attribute, we can reason about the witnesses that will be generated prior to macro expansion. I fear that needing more semantic information that comes out of conformance checking (including associated type inference!) will lead to either lots of circularity errors, or a very difficult-to-understand conformance checking behavior. If a programmer wants a macro to generate an implementation for a specific protocol witness, that use-case could be satisfied by explicitly writing the function declaration and using a function-body macro.
- There must always be an explicit macro attribute to get "conformance synthesis" via macros. IMO, this is a good thing! The attribute serves as a handle for documentation about what exactly the macro generates, and if you're in an IDE, to view the expanded code. And because the macro can also add the extension that states the conformance, writing
@Equatable
is not more verbose than writing: Equatable
.
Yes, having the ability to conditionally generate protocol witnesses inside a type's primary declaration is critical for supporting conformance synthesis via macros.
Yes. I thoroughly considered the alternative solution presented by the proposal, because I have definitely wanted something like @implementation extension
in my own Swift code in the past due to the pervasive stylistic preference of implementing protocol conformances inside extensions. It's harder to reason about protocol witnesses when they're scattered about your source file because some of them have to be in the primary declaration while others can be in the extension. HOWEVER, this line of reasoning in the proposal has fully convinced me that @implementaiton extension
is not the right direction for Swift:
The primary drawback to this notion of implementation extensions is that it would no longer be possible to look at the primary definition of a type to find its full "shape": its stored properties, designated/required initializers, overridable methods, and so on. Instead, that information could be scattered amongst the original type definition and any implementation extensions, requiring readers to stitch together a view of the whole type. This would have a particularly negative effect on macros that want to reason about the shape of the type, because macros only see a single entity (such as a class or struct definition) and not extensions to that entity.
Something like @implementation extension
is very tempting to reach for, but we'd just be trading one category of scattered declarations for another, and the problem this would create for macros is a nonstarter. Keeping storage, overridable methods, etc, in the same declaration is much more crucial than keeping all witnesses for a protocol conformance in the same declaration. For example, it would be extremely difficult to reason about what your type's member-wise initializer looks like if stored properties are scattered about a source file across many different extensions.
I have not used other languages or libraries with a macro system similar to Swift's attached macros.
An in-depth study with lots of fretting over how attractive @implementation extension
seemed to be initially.