# Swift Macro

With the release of Swift 5.9, Apple introduced a new feature called Macros. Macros allow you to generate code at compile time, helping you avoid repetitive code. The code is generated before your project is built, which may raise concerns about debugging. However, both the input and output (the code generated by the macro) are checked to ensure they are syntactically valid Swift code.

In the upcoming sections, we will discuss the different types of Macros, provide an example of how to implement your first Macro, explore cases where Macros can enhance our code, and demonstrate how we used them at Klivvr to eliminate boilerplate code.

# Types of Macros

* **Freestanding Macros:** Appear on their own without being attached to any declaration and start with `#`, such as `#Error()` and `#warning()`.
    
* **Attached Macros:** Modify the declaration they are attached to, using the `@` symbol before the name of the Macro.
    

## Freestanding Macros

There are two types of freestanding Macros:

* **Expression Freestanding Macro:** Used to create a piece of code that returns a value.
    
* **Declaration Freestanding Macro:** Used to create a new declaration.
    

## Attached Macros

There are five types of attached Macros:

* **Attached Peer:** Adds a new declaration alongside the declaration it’s applied to.
    
* **Attached Accessor:** Adds accessors (like `willSet`, `didSet`, `get`, `set`) to the property it’s applied to.
    
* **Attached Member Attribute:** Adds attributes to the declarations in the type or extension it’s applied to.
    
* **Attached Member:** Adds a new declaration in the type or extension it’s applied to.
    
* **Attached Conformance:** Adds conformances to the type or extension it’s applied to.
    

Now that we’ve explored the available types of Macros, let’s dive into creating a Macro and how it can enhance our codebase.

# Macro Implementation Example

In this section we will explore how to create a Macro that generates a non-optional url from a string. The problem is that we need to check the url value using a guard statement every time we create a url from a string, like so:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567129007/53966d5f-baad-4d0b-8d47-61427df7524a.png align="center")

So we need to avoid this boilerplate code, plus the `URL(string: )` will always generate a value—the only case where it will generate nil is if the string is empty. We can go one step further by making the macro generates a compile time error if the given string doesn’t follow a certain formate, this will give us the ability to check all the `URL` in our code during compile time.

1. ## Create a SPM
    

The first step is to open Xcode 😂, then go to *file &gt; new &gt; package*. Then you will have a pop up like so:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567148956/3b7970bf-2368-423c-b37f-74466b179e74.png align="center")

You will choose Swift Macro and this will open a new Swift Package Manger (SPM) that contains a simple macro implementation called `stringify`.

**Note:** To create a macro, it must be contained in SPM. You can add as many macros as you need in the same SPM. Probably by now you have noticed that the SPM you have created depends on [swift-syntax](https://github.com/swiftlang/swift-syntax), this is the only requirement to create swift macro.

You will end up having something like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567184446/be4677ab-2295-44ab-863e-bba64e9950ea.png align="center")

2. ## Declare a Macro
    

To declare a macro, we need to specify the type. The goal is to be able to call this macro in any point in our code base without having to add it to a declaration. With that in mind, we will choose the freestanding type for the macro. Going a step further, we’ll need to specify which type of freestanding macro we are going to use. We need the output of the macro to be a `URL` and this output is not a new declaration so we will use the freestanding expression macro. To write that in code it will be as follows:

```swift
@freestanding(expression)
public macro safeUrlFrom(_ urlString: String) -> URL = 
#externalMacro(module: "MyMacroMacros", type: "URLMacro")
```

The notation `@freestanding(expression)` specifies the type of macro, then we add the `public` keyword to expose the macro for use outside the SPM. Then you use the `macro` keyword to declare a new macro then you write the name of the macro and the parameters for it and the return type of the macro. After the `=` sign you basically tell the compiler where is the implementation of the macro in the SPM using the `#externalMacro(module: "MyMacroMacros", type: "URLMacro")` , you give it the name of the module that contains the implementation of the macro and the name of type that contains the implementation. This is because the implementation of the macros are contained in `struct` . Now you have declared the macro in the next step we will see how it will be implemented.

**Note:** The declaration of macro is in this file `MyMacro`:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567268956/8616f41f-232b-4814-a05e-8480fde2e849.png align="center")

3. ## Implementation of the Macro
    

So as we declared in the `#externalMacro(module: "MyMacroMacros", type: "URLMacro")` the implementation will be in the `MyMacroMacros` module, you will find a file that already exits in the module, navigate to this file and let’s implement our first macro.

First we will declare a `struct` called `URLMacro` as we wrote in the `#externalMacro` and we will make it conform to the `ExpressionMacro` protocol since we declared our macro as freestanding expression, it will look like that :

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567327153/0fdec269-a4ea-43ea-a823-338311e6496c.png align="center")

As you see this protocol have only one requirement which is the `expansion` method which will contains the implementation of the macro, this method is called when the macro is called.

Since our macro takes parameter as an input so we must first check if the arguments where passed and if they are the right type or not other wise we will throw a compile time error. it looks like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567343590/b8cdc141-b111-448f-a501-01bc8e7cbad3.png align="center")

All arguments passed to the macro are accessed through the `node.argumentList` this will gives us an array of the arguments passed to the macro since we only have one parameter we took only the first element, we made a guard statement to throw a compile time error if we found the first element to be nil (which means the arguments weren’t passed). The `argument` is of type `LabeledExprSyntax` which contains the passed value to the macro, if you tried to pass "[www.klivvr.com](http://www.klivvr.com/)" as argument and tried to print the value it will look something like this in the console:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567360127/de61c602-5af0-48a9-bcbc-b96097f58b6c.png align="center")

**Note:** We’ll get to the details of how we created the `URLMacroErrors` enum to throw compile time errors at the end of the article.

The second thing we need to do is to check if the type of the argument is of type `String`. In the swift-syntax package, string is represented by the `StringLiteralExprSyntax` so basically what we need to do is to type case the argument from `LabeledExprSyntax` to `StringLiteralExprSyntax`, if it succeed this means a `String` was passed, if not this means the argument is not a `String`. To do this we assed the `expression` property and casted it using the `as(StringLiteralExprSyntax.self)?` then we assed the `segments` which contains the string we want.

To be able to understand what I did next let me explain first what is the `StringLiteralExprSyntax`. This is basically the tree syntax representation of string type in the [https://github.com/swiftlang/swift-syntax](https://github.com/swiftlang/swift-syntax) package, every root of the tree contains a property that represent something in the string type, to understand what I mean let’s print it and see what it looks like:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567377894/31b9e647-2015-4372-bcd2-015c53249ee8.png align="center")

As you see this why we assed the `segments` property to get the string we want, but why it called `segments`? Because every space in the string is basically a separator between two segments, for example: “Omar is the best 😅😎” here we have 5 `segments` on the other hand something like "[www.klivvr.com](http://www.klivvr.com/)" has only one segment. This is why the second condition in the guard is `case .stringSegment(let segment)? = segments.first`. This line takes the first element in the `segments` and makes sure it is equal to the `.stringSegment` case then we created the urlString from the `segment.content.text` like:

```swift
let urlString = segment.content.text
```

The third step we need to do is to check if the string is a valid url or not. If not, we will throw a compile time error, it will look something like:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567401122/948142b1-a894-4236-89c9-fd3248819c84.png align="center")

We checked first if we can make a url from the `urlString` using the `URL(string: urlString)` then we checked if the url have a `host` and a `scheme` or url is a file url at this point we can say it is a valid url. After that we can return the url:

```swift
return "URL(string: \\(argument))!"
```

This `ExprSyntax` is then converted by the compiler to `URL(string: \\(argument))!`. Before celebrating our first macro, let’s first see how I created the `URLMacroErrors` enum to throw compile time errors.

4. ## URLMacroErrors
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567429050/e4a53c49-85e3-44c2-9a94-95f9110c4c44.png align="center")

As you can see, the `URLMacroErrors` is an enum that conforms to two protocols `Errors` and the `CustomStringConvertible`. It has one requirement: the `description` property, which represents the message that will be represented with the compile time error.

**Alert:** Don’t forget to add the type in `MyMacroPlugin` like so:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567442201/066186d6-af7d-4c27-a666-63498af8ae48.png align="center")

Final outcome is as follows:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567465008/eda0853d-ebeb-47c7-a877-8a905b771af1.png align="center")

The macro succeeded in the first one because we passed a valid url that contains a scheme and a host but for the second one we didn’t specify the scheme hence why it failed.

## Testing your Macro

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727567479590/c8a9e579-8b1e-47c7-be56-3b737a943336.png align="center")

The last step is to test our Macro. To test your macro, add a new key-value pair in the `testMacros`. The key represent the name of the macro, and the value represent the macro type. In this case, the value is `URLMacro.self`. Then you will call the `assertMacroExpansion` method in a test method. It takes two parameters, the `originalSource` which represent the calling of the macro, and the `expandedSource` which represents the expected output from the macro. If the actual output is equal, the expected output you wrote in the `assertMacroExpansion` test will pass, otherwise it will fail.

# Enhancing Code Efficiency with Swift Macros

Swift Macros are a powerful feature that enables you to write cleaner, more maintainable code by reducing boilerplate and enforcing compile-time checks. At Klivvr, we’ve started using Macros to streamline our code, and we encourage developers to explore this feature in their projects to enhance efficiency. Good luck as you dive into the world of Swift Macros! 🍀

# Resources

Macro created: [https://github.com/EngOmarElsayed/URLMacro.git](https://github.com/EngOmarElsayed/URLMacro.git)

Macro examples:

[https://github.com/swiftlang/swift-syntax/tree/main/Examples/Tests/MacroExamples/Implementation](https://github.com/swiftlang/swift-syntax/tree/main/Examples/Tests/MacroExamples/Implementation)

WWDC sessions:

[https://developer.apple.com/wwdc23/10166](https://developer.apple.com/wwdc23/10166)

[https://developer.apple.com/wwdc23/10167](https://developer.apple.com/wwdc23/10167)
