我很好奇,有没有一种方法可以使用Swift macros来添加新成员,同时以某种方式增加初始值设定项以包括新成员的初始化?

例如,如果我有一个名为Counted的宏,它将count个成员添加到 struct 中,那么使用attached member macro来转换将相对容易:

@Counted
struct Item {
    var name: String

    init(name: String) {
        self.name = name
    }
}

致:

struct Item {
    var name: String
    var count: Int = 0    // <- "= 0" is what allows old init to work

    init(name: String) {
        self.name = name
    }
}

但它不允许我在初始化期间设置Count成员.如果我希望能够创建这样的项目:

let myItem = Item(name: "Johnny Appleseed", count: 5)

我不能,因为没有一个初始化器同时接受name和count.

design philosophy67/" rel="nofollow noreferrer">WWDC'23 Video Expand on Swift macros列出了用于Swift宏的design philosophy,包括一条规则,即changes must be incorporated in predictable, additive, ways.这实际上为宏打开了一扇大门,使其能够将add代码添加到现有初始化器的末尾,但我没有看到任何attached macro roles表明它们能够做到这一点.

是否有一个宏角色可以添加到现有的初始化器中,如果没有,我将如何组织事情,以便宏可以创建我想要的初始化器?

推荐答案

如果您只需要一个额外的初始值设定项,MemberMacro可以生成:

enum CountedMacro: MemberMacro {
    static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
        let initialisers = declaration.memberBlock.members.compactMap { member in
            member.decl.as(InitializerDeclSyntax.self)
        }
        guard initialisers.count > 0 else {
            context.diagnose(...)
            return []
        }
        let newInitialisers = initialisers.map(generateNewInitialiser).map(DeclSyntax.init)
        return ["var count = 0"] + newInitialisers
    }
    
    static func generateNewInitialiser(from initialiser: InitializerDeclSyntax) -> InitializerDeclSyntax {
        var newInitialiser = initialiser
        // add parameter
        let newParameterList = FunctionParameterListSyntax {
            newInitialiser.signature.parameterClause.parameters
            "count: Int"
        }
        newInitialiser.signature.parameterClause.parameters = newParameterList
        
        // add statement initialising count
        newInitialiser.body?.statements.append("self.count = count")
        
        return newInitialiser
    }
}

如果要在现有的初始化器中添加一个新的count参数,宏目前不能这样做.一种解决方法是要求宏的用户将初始化器标记为私有.然后,宏可以 for each 专用初始化器生成新的public个初始化器.这实际上"隐藏"了现有的初始值设定项.

// in generateNewInitialiser...
let modifiersToBeRemoved: [TokenSyntax] = ["public", "private", "internal", "fileprivate", "required"]
newInitialiser.modifiers = newInitialiser.modifiers.filter { m in modifiersToBeRemoved.contains { m.name == $0 } }
newInitialiser.modifiers.insert(DeclModifierSyntax(name: "public"), at: newInitialiser.modifiers.startIndex)

另一种更灵活的设计是将其拆分为three个宏:

  • @Counted是一个成员宏,它添加var count = 0以及附加的member attribute macro that adds @CountedInitialiserto all initialisers without a@CountedIgnored`宏.
  • @CountedInitialiser是使用generateNewInitialiser生成公共初始值设定项的对等宏.
  • @CountedIgnored是不执行任何操作的对等宏.它仅用于告诉@Counted忽略初始值设定项

这允许宏的用户准确地 Select 他们想要增加的初始值设定项.例如:

@Counted
struct Foo {
    let name: String

    @CountedIgnored
    init() { name = "Default" }

    // @CountedInitialiser will be added to this when @Counted expands
    private init(name: String) { self.name = name }
}

根据SWIFT数据,@Observable个(@ObservationTracked/@ObservationIgnored)和@Model个(@_PersistedProperty/@Transient)可以看到类似的技术.

Swift相关问答推荐

向文本元素添加背景色失败

Swift-Can无法在AudioKit中找出简单的麦克风效果-文件链

在SwiftUI中绘制形状的路径后填充形状

计算 CoreData 中所有唯一对象的数量?

从 Obj-C 函数返回 swift 类的不兼容指针类型

如何使一个 Reality Composer 场景中的分组对象可拖动?

TabView 无法在屏幕上正确显示

从 iPhone 中的安全飞地获取真正的随机数?

有没有更快的方法来循环浏览 macOS 上已安装的应用程序?

在 Swift 中增量写入大型文本文件的最佳方法

按钮图像不会立即刷新

如何 for each 用户制作一个单独的按钮,以便在按下它时切换 isFollowingUser Bool 并更改为关注?

SwiftUI Preview 不适用于 Core Data

用于 Swift 编码的 iOS 应用程序的 Ffmpeg

为什么swiftui中的导航视图栏那么大?

找不到接受提供的参数的/的重载

在运行时访问 UIView 宽度

由于编译器中的内部保护级别,无法访问框架 init 中的公共 struct

Swift 2.0 按属性对对象数组进行排序

Xcode6 中的 Swift 类与 Cocoa Touch 类