创建一个`MemberMacro`来创建一个具有注入一致性的嵌入结构

问题描述 投票:0回答:1

我正在 Swift 中实现

MemberMacro
。我能够实现除一项要求之外的所有要求。

这是我到目前为止所能做的:

  • struct
    中创建嵌入的
    class
    ,我将宏附加到
  • 该结构与类具有相同的属性

基本上就是这样……

@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
    )
}

以下是缺少的内容:

  • 我需要结构体符合特定的要求——这意味着我希望能够在调用站点定义添加的嵌入结构体符合什么

实现

MemberMacro
的函数是
static expansion(of:providingMembersOf:conformingTo:in:)
具有属性
conformingTo

此函数的文档对此属性有以下说明:

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.

但事情是这样的:我还没有找到如何在调用站点声明这些一致性。

在调用站点,我目前只是用

@@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)
      }
      """
      ),
    ]
  }
}
swift macros
1个回答
0
投票

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

RecordableInterface
宏。

例如,如果您想让内部结构符合

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
参数访问参数列表。然后,您可以检查传入的类型并选择适当的宏以添加到生成的嵌套类型中。这是一个截断的示例:

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

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

© www.soinside.com 2019 - 2024. All rights reserved.