Monday, July 17, 2023

Macros in Swift 5.9

Platforms State of the Union:

But some APIs can be hard to use, requiring you to write a lot of boilerplate code just to get started. That’s why Swift is unlocking a new kind of API that’s easier to use and easier to get right with the introduction of macros. And these macros are done the Swift way. A macro is an annotation that uses the structure of your code to generate new code that’s built with your project. Macros can either be attached as attributes to your code, or they can be freestanding, spelled with the hash sign. Macros make APIs feel like they’re part of the language, and there are so many ways to start using a new API with just an annotation. Macros come to life in Xcode, where the generated code feels like it’s part of your project.

What’s new in Swift:

In Swift, macros are APIs, just like types or functions, so you access them by importing the module that defines them. Like many other APIs, macros are distributed as packages.

[…]

Uses of the macro will be type checked against the parameters. That means, if you were to make a mistake in using the macro, such as forgetting to compare the maximum value against something, you’ll get a useful error message immediately, before the macro is ever expanded. This allows Swift to provide a great development experience when using macros because macros operate on well-typed inputs and produce code that augments your program in predictable ways. Most macros are defined as “external macros,” specifying the module and type for a macro implementation via strings. The external macro types are defined in separate programs that act as compiler plugins. The Swift compiler passes the source code for the use of the macro to the plugin. The plugin produces new source code, which is then integrated back into the Swift program.

Write Swift macros:

Code along as we explore how macros can help you avoid writing repetitive code and find out how to use them in your app. We’ll share the building blocks of a macro, show you how to test it, and take you through how you can emit compilation errors from macros.

Expand on Swift macros:

Learn how macros can analyze code, emit rich compiler errors to guide developers towards correct usage, and generate new code that is automatically incorporated back into your project. We’ll also take you through important concepts like macro roles, compiler plugins, and syntax trees.

Antoine Van Der Lee:

In this example, we’re using a so-called freestanding expression macro. This is just one of the currently seven available different roles[…]

[…]

Xcode automatically generates a test target for you, including an example unit test for the stringify implementation.

Daniel Steinberg:

Sadly, Xcode looked at the generated code and told me that although the expanded macro was symbol for symbol what I would have typed in myself, it had no idea what those variables represented.

So I decided that what I really needed was a MemberMacro.

And by decided, I mean that based on no information and guided by no intuition, I figured that I’m trying to add new members to the type so maybe this was it.

It turned out to be the right decision.

Joseph Heck:

One of the most amazing (to me) things about this year - if you want to know HOW that fancy Observable macro works, you can just read through it.

Helge Heß:

When using the new Observation features in Swiftlang, be careful with the naming of your properties. It creates a stored property w/ an underscore in front, so don’t do the same, surprises may happen.

Also didSet doesn’t work, in case you didn’t notice yet[…]. Though the generated code actually moves the didSet to the fresh new property (not sure this is just a bug or actually impossible to do right w/ macros).

Nick Lockwood:

I’d be a lot more into Swift macros if they didn’t have to be defined in an external module (which itself depends on the external SwiftSyntax module).

To anyone used to ecosystems where apps routinely import hundreds of 3rd party packages, this probably sounds insane, but after years as an iOS dev without any sort of standard package manager, I’ve really come to value code having no dependencies outside of what ships with the platform.

Swift macros feel no more more “built-in” than Sourcery.

Ole Begemann:

If you’re writing Swift macros, you have to check out the fantastic Swift AST Explorer by @kishikawakatsumi. It makes it really easy to make sense of the syntax tree your macro operates on.

Tanner Bennett:

I’ve been experimenting with Swift macros all day trying to make “key path” decoding work. I.E., being able to supply a string like “config.meta.flag” to a macro and somehow using it to pull a value out of one or more nested dictionaries and insert it into a top-level property, without declaring nested Codable types for the nested dics.

It is not possible.

[Update (2023-07-26): See below.]

Soumya Ranjan Mahunt:

I’m excited to introduce my latest project, MetaCodable, a powerful macro library that will help you with all your Codable needs. Using MetaCodable, you can get rid of repetitive boilerplate code that you often have to write.

Dave Verwer:

The package index is already filling up with packages that contain macros, and it makes me glad we added 5.9 support so quickly. Some of the packages I’m linking to below will become essential parts of the Swift package ecosystem, and some will remain experiments. It’s impossible to know which yet!

There’s everything from full-featured packages like SwiftRequest and papyrus that let you define a type-safe HTTP client with function annotations to smaller utility packages like AssociatedObject, which allows variable storage in extensions. There are many, many more though. Here’s a list of others I saw this week:

Previously:

Update (2023-07-26): Amy Worrall:

My first foray into Swift macros.

It removes the need to write a whole bunch of boilerplate when making new Lexical nodes.

Rob Napier:

All of these are possible in a certain sense (with a custom JSON type and/or a custom AnyCodingKey type)[…]

[…]

If you start here, it’s pretty close to where I talk about stuff that I think lines up with what you’re doing.

Update (2023-07-31): See also: Daniel Steinberg’s videos.

Update (2023-08-15): Krzysztof Zabłocki:

If use Swift Macros you have to be careful how you structure your code, unlike Sourcery the Macro’s don’t concatenate whole project AST and they won’t see anything you added in Extensions to your original type…

In the example below x variable won’t be visible as a member in your macro code, this can lead to some hard to find bugs…

Comments RSS · Twitter · Mastodon

Leave a Comment