如何迭代宏中 tuple/object 的字段(名称+类型)?

How to iterate over fields (names + types) of a tuple/object in a macro?

我想编写宏,它必须根据命名元组或对象的字段执行一些逻辑。我认为最好通过将 tuple/object 作为 typed 参数传递给宏来实现。

问题是,通常如何遍历 typed 参数的字段?我基本上是在为宏寻找 fieldPairs 的等价物,即,不是采用具体的 tuple/object,而是必须对 NimNode 和 return 字段进行操作 names/types 如此(用于进一步生成 AST)。

我找到了解决问题的办法,似乎工作正常,但我不确定是否有更好的选择。该解决方案基于在 typed 参数上使用 getTypeImpl。要了解它是如何工作的,查看 t.getTypeImpl.treeRepr 的简单元组和对象的输出会有所帮助。

  • Tuple:例如 (x: 0, y: 1, name: "") 的类型 impl AST 看起来像这样:

    TupleTy
      IdentDefs
        Sym "x"
        Sym "int"
        Empty
      IdentDefs
        Sym "y"
        Sym "int"
        Empty
      IdentDefs
        Sym "name"
        Sym "string"
        Empty
    

    注:getTypeImpltypeKindntyTuple

  • Object:具有相同结构的对象的类型 impl AST 为:

    ObjectTy
      Empty
      Empty
      RecList
        IdentDefs
          Sym "x"
          Sym "int"
          Empty
        IdentDefs
          Sym "y"
          Sym "int"
          Empty
        IdentDefs
          Sym "name"
          Sym "string"
          Empty
    

    注:getTypeImpltypeKindntyObject

这表明我们正在查找的信息在 IdentDefs 中可用。我们只需要确保适当地处理元组和对象:对于元组,IdentDefsNimNode 的直接子节点,而对于对象,IdentDefs 存储在子节点中索引 2(索引 0 上的子项包含 pragma 信息,索引 1 上的子项是父项的信息)。

总体而言,宏可能如下所示(添加了一些调试输出以供说明):

macro iterateFields*(t: typed): untyped =
  echo "--------------------------------"

  # check type of t
  var tTypeImpl = t.getTypeImpl
  echo tTypeImpl.len
  echo tTypeImpl.kind
  echo tTypeImpl.typeKind
  echo tTypeImpl.treeRepr

  case tTypeImpl.typeKind:
  of ntyTuple:
    # For a tuple the IdentDefs are top level, no need to descent
    discard
  of ntyObject:
    # For an object we have to descent to the nnkRecList
    tTypeImpl = tTypeImpl[2]
  else:
    error "Not a tuple or object"

  # iterate over fields
  for child in tTypeImpl.children:
    if child.kind == nnkIdentDefs:
      let field = child[0] # first child of IdentDef is a Sym corresponding to field name
      let ftype = child[1] # second child is type
      echo "Iterating field: " & $field & " -> " & $ftype
    else:
      echo "Unexpected kind: " & child.kind.repr
      # Note that this can happen for an object with a case
      # fields, which would give a child of type nnkRecCase.
      # How to handle them depends on the use case.

# small test
type
  TestObj = object
    x: int
    y: int
    name: string

let t = (x: 0, y: 1, name: "")
let o = TestObj(x: 0, y: 1, name: "")

iterateFields(t)
iterateFields(o)