我正在Swift中实现MemberMacro
.除了一个要求之外,我能够实现所有要求.
以下是我到目前为止能够做的事情:
- 在我附加宏的
class
内创建嵌入式struct
- 该 struct 具有与类相同的属性
所以基本上这个.
@RecordInterfacable
@Observable
class Model {
let id: UUID
var title: String
init(
id: UUID = UUID(),
title: String
) {
self.id = id
self.title = title
}
}
结果是:
@Observable
class Model {
let id: UUID
var title: String
init(
id: UUID = UUID(),
title: String
) {
self.id = id
self.title = title
}
// I need to make this struct conform to protocols that I inject at call site of the macro – but how?
struct ModelRecord: Codable {
let id: UUID
var title: String
}
init(from record: ModelRecord) {
init(
id: record.id,
title: record.title
)
}
func convertToRecord() -> ModelRecord {
ModelRecord(
id: self.id,
title: self.title
)
}
以下是缺失的内容:
- 我需要 struct 体根据需要符合特定的符合性-这意味着我希望能够在呼叫现场定义添加的嵌入 struct 体符合什么
实现MemberMacro
的函数是static expansion(of:providingMembersOf:conformingTo:in:)
,具有属性conformingTo
.
此funcc的文档中关于此属性有以下说明:
conformingTo
The set of protocols that were declared in the set of conformances for the macro and to
which the declaration does not explicitly conform. The member macro itself cannot declare
conformances to these protocols (only an extension macro can do that), but can provide
supporting declarations, such as a required initializer or stored property, that cannot be
written in an extension.
事情是这样的:我还没有找到how to declare those conformances at callsite.
在呼叫网站,我目前只是用@@RecordInterfacable
来注释我的班级-但我不知道如何在这里注入所需的一致性.有人能给我一个提示如何做到这一点吗?
以下是宏实现:
//RecordInterfacable
@attached(member, names: arbitrary)
public macro RecordInterfacable() = #externalMacro(module: "RecordInterfacableMacros", type: "RecordInterfacableMacro")
//RecordInterfacableMacro
public struct RecordInterfacableMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
let classDecl = try assertClassDecl(for: declaration)
let symbolName = try extractSymbolName(from: classDecl)
/// Extracts all the elements of the body of the given class.
/// This includes all properties and functions.
let membersDeclSyntax = declaration
.as(ClassDeclSyntax.self)?
.memberBlock
.members
.compactMap {
$0
.as(MemberBlockItemSyntax.self)?
.decl
.as(DeclSyntax.self)
}
/// Further extracts all variables
let membersVariableDeclSyntax = membersDeclSyntax?
.compactMap {
$0
.as(VariableDeclSyntax.self)
}
let memberBindingSpecifiers: [String]? = membersVariableDeclSyntax?
.compactMap { member in
guard let memberBindingSpecifier = member
.bindingSpecifier
.text
.split(separator: ".")
.last
else { fatalError() }
return "\(memberBindingSpecifier)"
}
guard let memberBindingSpecifiers else { fatalError() }
/// Create a string with the declaration of all members
let identifierTexts = membersVariableDeclSyntax?
.map { member in
let identifierText = member
.bindings
.compactMap {
$0
.as(PatternBindingSyntax.self)?
.pattern
.as(IdentifierPatternSyntax.self)?
.identifier
.text
}
.first
guard let identifierText else { fatalError() }
return identifierText
}
guard let identifierTexts
else { fatalError() }
let memberTypes = membersVariableDeclSyntax?
.map { member in
let memberType = member
.bindings
.compactMap {
$0
.as(PatternBindingSyntax.self)?
.typeAnnotation?
.type
.as(IdentifierTypeSyntax.self)?
.name
.text
}
.first
guard let memberType else { fatalError() }
return memberType
}
guard let memberTypes
else { fatalError() }
var memberStrings = [String]()
var initStrings = [String]()
var varString = [String]()
for i in 0..<identifierTexts.count {
memberStrings.append("\(memberBindingSpecifiers[i]) \(identifierTexts[i]): \(memberTypes[i])")
initStrings.append("\(identifierTexts[i]): record.\(identifierTexts[i])")
varString.append("\(identifierTexts[i]): self.\(identifierTexts[i])")
}
let memberString = memberStrings
.joined(separator: "\n")
let initString = initStrings
.joined(separator: ", ")
return [
DeclSyntax(
stringLiteral: """
struct \(symbolName)Record: Codable, FetchableRecord, PersistableRecord {
\(memberString)
}
"""
),
DeclSyntax(
extendedGraphemeClusterLiteral: """
convenience init(from record: \(symbolName)Record) {
self.init(\(initString))
}
"""
),
DeclSyntax(
stringLiteral: """
var record: \(symbolName)Record {
\(symbolName)Record(id: self.id, title: self.title)
}
"""
),
]
}
}