Running into unfortunate limitations of macros (can't introduce extensions, and more)

I recently added focused commands to my macOS SwiftUI app. It requires a lot of boilerplate code, and I need to implement many commands. I was hoping to reduce the burden of this with macros, but after spending a few hours implementing my first macro (via unit test), I went to try it out and ran into designed limitations.

I have a more detailed post about what needs to be written to support a single focused command. I'll try to summarize:

  1. Declare a struct conforming FocusedValueKey
  2. Extend FocusedValues to add a computed property based on that key.
  3. Extend View to add a convenience method for responding to the command
  4. Create the command itself with a call to the FocusedValue property’s command closure, and to reference it to get the enabled state of the command. This requires adding a @FocusedValue property-wrapped property to the Scene or Command in which it’s used.

I had written a macro to do 1, 2, and 3, and had hoped to find a way to have a macro do at least part of #4, although adding the property to the enclosing struct from a standalone macro within a method is not possible, afaik.

I am absolutely crushed by the wasted effort tonight. I was developing my macro using unit tests, and they did not indicate this was an error.

If anyone cares, here’s the macro I tried to write: https://github.com/latencyzero/LZSwiftUIMacros.

In my ideal world, I could write a macro that introduces a menu command in SwiftUI that expands locally to the SwiftUI Button expression needed for a menu command, adds a property to the enclosing struct, and adds the set of structs and extensions to the file.

2 Likes

Yes, you can't use a macro to introduce an extension to an arbitrary type. I've seen this discussed before; it would mean the compiler would have to always expand every macro it sees just in case it contains an extension it needs to know about. Under the current system, the relatively strict categorization of macros helps the compiler to be more efficient.

I think you could do it so the invocation looks like this, which isn't as clean but it's still an improvement:

#focusedCommand("Duplicate")
extension FocusedCommandValues { #focusedCommandValue("Duplicate") }
extension View { #focusedCommandMethod("Duplicate") }
1 Like

This feels a bit of like the compiler doesn't want to do work, so now all the users have to.

Surely the brilliant compiler engineers can find a way to cache the expansion so it's no different than me copying and pasting the expanded code (which is what I'm doing now)?

2 Likes