Allow attached macros to not suppress the memberwise initializer

Continuing the discussion from SE-0389: Attached Macros:

Hello, well I'm very happy that macros have shipped, and I can't wait for GRDB to remove its own boilerplate code so that it doesn't look too bad in front of SwiftData.

And I'd really like to add initializers, but not to suppress the default memberwise one (just like Codable does). In the specific case of GRDB, removing the Codable requirement for database records would immensely help performance (see this discussion).

@Douglas_Gregor, do you think I should just create a GitHub issue, or that this would require a evolution process of its own?

1 Like

In my understanding, this new attribute would decorate initializers. This would work.

Another option would be to make @attached macros able to wrap their added members in an extension (since initializers declared in extensions do not remove the memberwise init).

Actually, it might be interesting for macros that combine the member and conformance roles to be able to wrap the new members inside the added conformances.

So instead of having:

@MyMacro
struct MyStruct {
    // Generated by the `member` role inside MyStruct
    func myConformance() { }
}

// Generated by the `conformance` role as a peer to MyStruct
extension MyStruct: MyProtocol { }

We'd have:

@MyMacro
struct MyStruct { }

// Generated by the `member + conformance` roles
extension MyStruct: MyProtocol {
    func myConformance() { }
}

(I can see how difficult it is to build this on top of the existing ConformanceMacro and MemberMacro protocols.)

Yet this would improve the legibility of the generated code, and that's important. And I know one well-appreciated pitch that would profit for this kind of generation (conformance extensions that wrap the matching requirements). And several members of the steering group have expressed in this pitch that they love to wrap protocol requirements in dedicated extensions :-)

Now this would be a rather big change… and it is not my interest to wait too long. Destroying the memberwise init is not acceptable for GRDB users. I would be so disappointed to see the lib excluded from the macros game for months, years, just because of this "little" issue that was glossed over during the review of attached macros. :confounded:

Attempting at generating the memberwise init myself looks like fighting the language, spilling a lot of energy, and just an opportunity to create bugs or odd mismatches.

What can I do?

1 Like

I think this is an interesting suggestion. It made me wonder whether the conformance macro role should instead be an extension macro role, based on two observations:

  1. The conformance macro role is not useful on its own; it's pretty much always used in combination with member.
  2. The extension capability that the conformance macro role has today is useful independent of conformances.

We deliberately don't want macros to be able to add arbitrary extensions as peers due to the way extension binding has to work, but conformance macros are a restricted form of this because they specifically add an extension to the annotated type, so we can bind the extension immediately after macro expansion. This seems more generally useful, and I could imagine the ConformanceMacro protocol being generalized to ExtensionMacro with 2 requirements: the existing requirement to gather the declared conformance and where clause (which could each be optional), and another to gather the members that are added to the extension. This would solve your initializer problem, because it would allow you to generate initializers in an extension which would then not suppress the compiler-synthesized initializer...

7 Likes

Perhaps reworking the memberwise init into a standard macro would be better than adding a special attribute; we have too many of those already. Then the current behavior of the memberwise init getting synthesized by default would just become a special case where the compiler invokes the macro.

9 Likes

... and other macros could invoque it as well, is that your idea? This should work at first glance.

Now, there is evolution pressure on the memberwise init: it is regularly the topic of feature requests, evolution discussions, or pitches. Strictly locking the memberwise init inside the feature set of macros would seriously limit its evolution space, don't you think? For example, some "issues" listed in this old thread State of the Memberwise Initializer are still relevant (access control and classes).

The memberwise initializer is one of the most crucial user-facing features of the language. Its ergonomics are paramount, and should be enhanced, not restricted. I understand that macros were introduced in order to address the need and desire to send inner compiler features to userland (among other reasons). But it's important to protect a few key areas - including the memberwise init IMHO. That's the idea I suggest the Language Steering Group to contemplate.

2 Likes