// The Swift Programming Language // https://docs.swift.org/swift-book import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros import SwiftDiagnostics import Foundation // 宏实现:自动生成 Codable 协议的实现 public struct AutoCodableMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { // 仅支持类和结构体 guard let typeDecl: DeclGroupSyntax = declaration.as(ClassDeclSyntax.self) ?? declaration.as(StructDeclSyntax.self) else { context.diagnose( Diagnostic( node: node, message: AutoCodableMacroError.onlyClassesAndStructs ) ) return [] } // 解析类型遵循的协议列表 let inheritedProtocols = typeDecl.inheritanceClause?.inheritedTypes .compactMap { $0.type.as(IdentifierTypeSyntax.self)?.name.text } ?? [] // 判断是否遵循 Codable/Decodable/Encodable let conformsToCodable = inheritedProtocols.contains("Codable") let conformsToDecodable = inheritedProtocols.contains("Decodable") || conformsToCodable let conformsToEncodable = inheritedProtocols.contains("Encodable") || conformsToCodable // 校验:至少遵循一个协议,否则报错 guard conformsToDecodable || conformsToEncodable else { context.diagnose(Diagnostic( node: node, message: AutoCodableMacroError.protocolError )) return [] } // 提取所有存储属性 let properties: [(name: String, type: String)] = typeDecl.memberBlock.members.compactMap { member -> (name: String, type: String)? in // 1. 检查是否是变量声明 guard let variableDecl = member.decl.as(VariableDeclSyntax.self) else { return nil } // 2. 确保只处理单个属性的声明 guard variableDecl.bindings.count == 1, let binding = variableDecl.bindings.first else { return nil } // 过滤计算属性 if let accessorBlock = binding.accessorBlock { // 判断 accessor 类型 switch accessorBlock.accessors { case .accessors(let accessors): let hasGet = accessors.contains { $0.accessorSpecifier.tokenKind == .keyword(.get) } if hasGet { return nil } case .getter: return nil } } // 3. 提取属性名称 guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil } let name = identifierPattern.identifier.text // 4. 提取属性类型 guard let typeAnnotation = binding.typeAnnotation else { return nil } let type = typeAnnotation.type.description.trimmingCharacters(in: .whitespacesAndNewlines) return (name: name, type: type) } if properties.isEmpty { context.diagnose( Diagnostic( node: node, message: AutoCodableMacroError.noStoredProperties ) ) } var syntax: [DeclSyntax] = [] // 生成 CodingKeys 枚举 let codingKeys = try EnumDeclSyntax("enum CodingKeys: String, CodingKey") { for property in properties { DeclSyntax("case \(raw: property.name)") } } syntax.append(DeclSyntax(codingKeys)) if conformsToDecodable { // 生成 init(from:) 方法 let bodyBuilder: SyntaxNodeString = declaration is ClassDeclSyntax ? "required init(from decoder: Decoder) throws" : "init(from decoder: Decoder) throws" let initFrom = try InitializerDeclSyntax(bodyBuilder) { CodeBlockItemListSyntax { "let container = try decoder.container(keyedBy: CodingKeys.self)" for property in properties { """ if let value = try? container.decode(\(raw: property.type).self, forKey: .\(raw: property.name)) { self.\(raw: property.name) = value } """ } } } syntax.append(DeclSyntax(initFrom)) } if conformsToEncodable { // 生成 encode(to:) 方法 let encodeTo = try FunctionDeclSyntax("func encode(to encoder: Encoder) throws") { CodeBlockItemListSyntax { "var container = encoder.container(keyedBy: CodingKeys.self)" for property in properties { "try container.encode(\(raw: property.name), forKey: .\(raw: property.name))" } } } syntax.append(DeclSyntax(encodeTo)) } return syntax } } // 错误提示定义 enum AutoCodableMacroError: DiagnosticMessage { case onlyClassesAndStructs case noStoredProperties case protocolError case customError(String) var message: String { switch self { case .onlyClassesAndStructs: "AutoCodable can only be applied to classes and structs" case .noStoredProperties: "Type has no stored properties; Codable implementation will be empty" case .protocolError: "类型必须遵循 Codable、Decodable 或 Encodable" case .customError(let err): err } } var diagnosticID: MessageID { .init(domain: "Codable", id: "\(self)") } var severity: DiagnosticSeverity { .error } }