# How to add a protocol with a macro if it is not added yet?

**URL:** <https://forums.swift.org/t/how-to-add-a-protocol-with-a-macro-if-it-is-not-added-yet/78626>\
**Category:** Using Swift\
**Tags:** swift-syntax, macros\
**Created:** [March 18, 2025, 12:12pm UTC](https://forums.swift.org/t/how-to-add-a-protocol-with-a-macro-if-it-is-not-added-yet/78626 "2025-03-18T12:12:45Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![RussBaz](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/russbaz/32/30841_2.png) [@RussBaz](https://forums.swift.org/u/RussBaz)\
**Post date:** [March 18, 2025, 12:12pm UTC](https://forums.swift.org/t/how-to-add-a-protocol-with-a-macro-if-it-is-not-added-yet/78626/1 "2025-03-18T12:12:45Z")

</div>

Hi.

I just wrote my first Member Macro that adds a couple of properties to the struct/class that require a certain protocol to be applied. Now, I would like to automatically add this protocol to that class as well if it is not yet added. Any idea how?

(I am using Swift 6.0)

Thank you!

---

<div class="post-metadata">

**Author:** ![RussBaz](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/russbaz/32/30841_2.png) [@RussBaz](https://forums.swift.org/u/RussBaz)\
**Post date:** [March 20, 2025, 8:00am UTC](https://forums.swift.org/t/how-to-add-a-protocol-with-a-macro-if-it-is-not-added-yet/78626/2 "2025-03-20T08:00:41Z")

</div>

I have found the solution I was looking for by carefully reading through this evolution: [swift-evolution/proposals/0407-member-macro-conformances.md at main · swiftlang/swift-evolution · GitHub](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0407-member-macro-conformances.md)

EDIT: Here is how I did it.

First, here is the task: I would like to add `created_at` and `updated_at` `Fluent` timestamp fields to models in my `Vapor` app. I will call this macro `TrackedModel` and I would like to add `Model` conformance if the class does not have it yet. I would like to do it with a custom macro.

Let's say I have two targets called `App` and `Macros` (two separate folders under the `Sources` folder)

Here is how I defined them in `Package.swift`:

```swift
// swift-tools-version:6.0
import PackageDescription
import CompilerPluginSupport

```

(later in the file)

```swift
.macro(
    name: "Macros",
    dependencies: [
        .product(name: "SwiftSyntax", package: "swift-syntax"),
        .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
        .product(name: "SwiftCompilerPlugin", package: "swift-syntax")
    ]
 ),
 .executableTarget(
        name: "App",
        dependencies: [
            .product(name: "Fluent", package: "fluent"),
            .product(name: "Vapor", package: "vapor"),
            "Macros",
        ]
),

```

Then I created a `Macros` target with two files:  
`TrackedModelMacro.swift`:

```swift
import SwiftSyntax
import SwiftSyntaxMacros

public struct TrackedModelMacro: MemberMacro, ExtensionMacro {
    public static func expansion(
        of _: AttributeSyntax,
        providingMembersOf _: some DeclGroupSyntax,
        conformingTo _: [TypeSyntax],
        in _: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        [
            "@Timestamp(key: \"created_at\", on: .create)",
            "var createdAt: Date?",
            "@Timestamp(key: \"updated_at\", on: .update)",
            "var updatedAt: Date?",
        ]
    }

    public static func expansion(
        of _: AttributeSyntax,
        attachedTo _: some DeclGroupSyntax,
        providingExtensionsOf type: some TypeSyntaxProtocol,
        conformingTo protocols: [TypeSyntax],
        in _: some MacroExpansionContext
    ) throws -> [ExtensionDeclSyntax] {
        let containsProtocol = protocols.contains { p in
            if let protocolDecl = p.as(IdentifierTypeSyntax.self),
               protocolDecl.name.text == "Model"
            {
                return true
            }

            return false
        }

        guard containsProtocol else { return [] }

        let modelExtension = try ExtensionDeclSyntax("extension \(type.trimmed): Model {}")

        return [modelExtension]
    }
}

```

and `macros.swift`:

```swift
import SwiftCompilerPlugin
import SwiftSyntaxMacros

@main
struct Macros: CompilerPlugin {
    var providingMacros: [Macro.Type] = [
        TrackedModelMacro.self,
    ]
}

```

And finally, I need to properly set up this macro in my `App` target. I created a separate file for this, called `TrackedModel.swift`:

```swift
import Fluent

@attached(member, names: named(createdAt), named(updatedAt))
@attached(extension, conformances: Model)
macro TrackedModel() = #externalMacro(module: "Macros", type: "TrackedModelMacro")

```

And now one can use it with or without declaring `Model` protocol. Here is an example usage inside the `App` target:

```swift
import Foundation
import Fluent

@TrackedModel
final class A: @unchecked Sendable {
    static let schema = "a"

    @ID
    var id: UUID?
}

@TrackedModel
final class B: Model, @unchecked Sendable {
    static let schema = "b"

    @ID
    var id: UUID?
}

```

In addition, I might later add a `Diagnostic` message that notifies me that an explicit model conformance is not needed.

---

<div class="post-metadata">

**Author:** ![millswip](https://avatars.discourse-cdn.com/v4/letter/m/f04885/32.png) [@millswip](https://forums.swift.org/u/millswip)\
**Post date:** [March 21, 2025, 3:24pm UTC](https://forums.swift.org/t/how-to-add-a-protocol-with-a-macro-if-it-is-not-added-yet/78626/3 "2025-03-21T15:24:16Z")

</div>

You can use `@attached(member, conformances: YourProtocol)` in your macro definition to automatically add the protocol if it’s not already there. Just make sure the macro expands correctly to enforce conformance.

---

<div class="post-metadata">

**Author:** ![RussBaz](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/russbaz/32/30841_2.png) [@RussBaz](https://forums.swift.org/u/RussBaz)\
**Post date:** [March 21, 2025, 5:32pm UTC](https://forums.swift.org/t/how-to-add-a-protocol-with-a-macro-if-it-is-not-added-yet/78626/4 "2025-03-21T17:32:49Z")

</div>

I might be mistaken, but if I write `TrackedModel.swift` like this:

```swift
import Fluent

@attached(member, conformances: Model, names: named(createdAt), named(updatedAt))
//@attached(extension, conformances: Model, names: named(createdAt), named(updatedAt))
macro TrackedModel() = #externalMacro(module: "Macros", type: "TrackedModelMacro")

```

I am getting compiler errors. Am I misunderstanding you here?

---

<div class="post-metadata">

**Author:** ![RussBaz](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/russbaz/32/30841_2.png) [@RussBaz](https://forums.swift.org/u/RussBaz)\
**Post date:** [March 26, 2025, 11:15am UTC](https://forums.swift.org/t/how-to-add-a-protocol-with-a-macro-if-it-is-not-added-yet/78626/5 "2025-03-26T11:15:10Z")

</div>

I made a small logic error when checking for protocols array, as I misunderstood what goes into it. I fixed it now.

The protocols array will show MISSING protocols from the conformances declarations.

Next, be careful with manual testing the macros because it will not reapply a newer version of macros when the macro source is updated. It will only rerun the macro when the source that uses said macro changes. Therefore, one can make a runtime error in the macro, but it will not be shown unless the macro call site is changed too.

Finally, when the macro is declared in the App, declarations (@attached) do not have to repeat all the arguments. I did not need them all repeated in my case. I will soon edit the original answer to indicate a briefer option.
