我正在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)
      }
      """
      ),
    ]
  }
}

推荐答案

我认为一个可能的解决方案是创建单独的扩展宏来实现一致性(它已经取代了废弃的"一致性"宏),然后将您想要遵守的类型作为参数传递给您的RecordableInterface宏.

例如,如果您想使内部 struct 符合Hashable,您可以编写一个可以做到这一点的扩展宏.我不会在这里包含实现,因为如何创建扩展宏有点超出了范围,但宏声明可能看起来像这样:

@attached(extension, conformances: Hashable)
public macro InnerHashable() = #externalMacro(module: "MacroImplementations", type: "InnerHashableMacro")

接下来,更新RecordInterfaceable宏的声明:

@attached(member, names: arbitrary)
public macro RecordInterfaceable(conformances: Any.Type...) = #externalMacro(module: "MacroExamplesImplementation", type: "RecordInterfaceableMacro")

现在,在宏实现中,您可以使用node参数访问参数列表.然后,您可以判断传递了哪些类型,并 Select 适当的宏添加到生成的嵌套类型中.这是一个截断的示例:

public struct RecordInterfacableMacro: MemberMacro {
  public static func expansion(
    of node: AttributeSyntax,
    providingMembersOf declaration: some DeclGroupSyntax,
    conformingTo protocols: [TypeSyntax],
    in context: some MacroExpansionContext
  ) throws -> [DeclSyntax] {
    guard let args = node.arguments?.as(LabeledExprListSyntax.self) else {
        return []
    }

    let attributes = try args.compactMap { labeledExpr in
        guard let declBase = labeledExpr
            .expression.as(MemberAccessExprSyntax.self)?
            .base?.as(DeclReferenceExprSyntax.self)
        else {
            return nil
        }
        
        //Handle as many possible macros as needed.
        if declBase == "Hashable" {
            return "@InnerHashable"
        } else {
            return nil
        }
    }

    let innerAttribs = attributes.joined("\n")

    ...

    return [
      DeclSyntax(
        stringLiteral: """
        \(raw: innerAttribs)
        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)
      }
      """
      ),
    ]
  }
}

我还没有真正测试过这个代码,所以我确信有一些细节需要解决,但我认为这个一般过程将实现您想要做的事情.

Swift相关问答推荐

结束手势不会在滚动视图中触发

Swift Tree实现中的弱var

为什么在Swift属性Wrapper中需要类型注释?

如何在SWIFT中解码Instagram Backup中的字符串?

阻塞(受CPU限制的)任务的异步功能?

SWIFT`String.Encoding`:`.unicode`vs`.utf16`

异步/等待函数的XCTAssertThrowsError

MacOS 13-如何使用SwiftUI创建Message.App设置工具栏?

使用UICollectionViewFlowLayout创建水平的UICollectionView,并同时将单元格对齐到左边和右边的能力

如何让默认函数参数引用另一个函数参数?

使用 swift 的 Firebase 身份验证

动画偏移正在 destruct 按钮 SwiftUI

Swift 中的Combine和didSet有什么区别?

如何更改任务栏 colored颜色 和 sf 符号 colored颜色 ?

如何将回调传递给 View init

无法从自定义动态框架(Swift)访问类

在 Swift 中获取双精度的小数部分

将强制向下转换视为可选将永远不会产生零

自定义 Google 登录按钮 - iOS

强制打开已在同一行代码中 Select 性访问的变量是否安全?