[Swift] デフォルト引数を持つオーバーロードをProtocol Extensionに生成するMacro

# 背景

Swift 6.0現在ではprotocolのメソッドに直接デフォルト引数を持たせることができません。

```swift
protocol ItemRepositoryProtocol {
    // 🔴 Default argument not permitted in a protocol method
    func getItems(pageSize: Int = 20, pageToken: String? = nil) async throws -> [Item]
}
```

迂回手法としてextensionにデフォルト引数を持つ定義を書くことができます。

```swift
extension ItemRepositoryProtocol {
    func getItems(pageSize: Int = 20, pageToken: String? = nil) async throws -> [Item] {
        try await getItems(pageSize: pageSize, pageToken: pageToken)
    }
}
```

# DefaultArgumentMacro

上記パターンを自動的に生成するmacroです。

https://github.com/kntkymt/swift-DefaultArgumentMacro/tree/main

## 使用例

```swift
import DefaultArgument

struct Item {}
enum SortKind {
    case name
    case like
}

@DefaultArgument(funcName: "getUseritems", defaultValues: ["sortKind": SortKind.name, "pageSize": 20, "pageToken": nil])
@DefaultArgument(funcName: "getItems", defaultValues: ["sortKind": SortKind.name, "pageSize": 20, "pageToken": nil])
protocol ItemRepositoryProtocol {
    func getItem(id: String) async throws -> Item
    func getItems(sortKind: SortKind, pageSize: Int, pageToken: String?) async throws -> [Item]
    func getUseritems(userID: String, sortKind: SortKind, pageSize: Int, pageToken: String?) async throws -> [Item]
}

struct ItemRepository: ItemRepositoryProtocol {
    func getItem(id: String) async throws -> Item {
        .init()
    }

    func getItems(sortKind: SortKind, pageSize: Int, pageToken: String?) async -> [Item] {
        []
    }

    func getUseritems(userID: String, sortKind: SortKind, pageSize: Int, pageToken: String?) async throws -> [Item] {
        []
    }
}

do {
    let itemRepository: any ItemRepositoryProtocol = ItemRepository()
    // マクロが生成したデフォルト引数ありの関数を呼ぶ
    _ = try await itemRepository.getItems(pageSize: 10)
    _ = try await itemRepository.getUseritems(userID: "1234")
} catch {
}
```

## 実装方法

Attached macroのextension macroで実装。重要なところだけ軽く説明。100行程度なので是非実際のソースコードを読んでみてください。

https://github.com/kntkymt/swift-DefaultArgumentMacro/blob/03ca57edb9a5ff131dc3cbc310f9fee8bd673f9c/Sources/DefaultArgumentMacros/DefaultArgumentMacros.swift

関数呼び出しを生成するところ。めんどくさいので完全にSyntaxBuilderに任せている

```swift
// add functionCallExpr
do {
    let parameters = targetFuncDecl.signature.parameterClause.parameters
    let callParameters = parameters.map { parameter in
        let parameterLabel = parameter.firstName.text
        return "\(parameterLabel): \(parameterLabel)"
    }.joined(separator: ", ")

    var callBase = "\(funcName)(\(callParameters))"
    if targetFuncDecl.isAsyncFunc {
        callBase = "await " + callBase
    }
    if targetFuncDecl.isThrowsFunc {
        callBase = "try " + callBase
    }
    targetFuncDecl.body = CodeBlockSyntax(stringLiteral: "{\(callBase)}")
}
```

デフォルト引数を関数定義 (`FunctionDecl`) に追加するところ

```swift
// macroの引数で指定したDictionaryを取り出す
let defaults: [String: ExprSyntax] = ...
for (argName, defaultValue) in defaults {
    guard let index = targetFuncDecl.signature.parameterClause.parameters.firstIndex(where: { $0.firstName.text == argName }) else {
        throw DefaultArgumentMacroError.argNameNotFound
    }
    // FunctionParameter.defaultValue（デフォルト値のnode）にmacroの引数で指定したデフォルト値を入れる
    targetFuncDecl.signature.parameterClause.parameters[index].defaultValue = InitializerClauseSyntax(value: defaultValue)
}
```


# 感想

まあこれくらいならmacro使わず普通に書けば良いというのはあるのであくまでマクロ実装の参考までに、マクロは実装コストと受けられる恩恵のコスパ判断が難しいですね。