The implementation of Any in the standard library package typeinfo and the implementation of Variant in @yglukhov's lovely variant package take an interestingly different approach to dissecting and using types during compile-time execution. typeinfo's approach is based on getTypeInfo(), which yields a PNimType. variant's approach is based on getTypeImpl(), which yield a NimNode.
What are the strengths and weaknesses of these two approaches? Do they have different limitations? Different maintainability? Are they ultimately complementary? I.e. if a package goes far enough in dissecting and using types, will it tend to use both? (And if so, is that a good thing?)
The implementation of Any begins by converting the type to a PNimType:
proc toAny*[T](x: var T): Any {.inline.} =
newAny(addr(x), cast[PNimType](getTypeInfo(x)))
Then it dissects and uses that PNimType (as rawType). Here's a representative example:
proc invokeNewSeq*(x: Any, len: int) =
assert x.rawType.kind == tySequence
var z = newSeq(x.rawType, len)
genericShallowAssign(x.value, addr(z), x.rawType)
In contrast, the implementation of Variant begins by obtaining the type's implementation as a NimNode:
proc mangledName(t: NimNode): string =
mangledNameAux(getTypeImpl(t)[1])
Then it dissects the type by understanding that NimNode and by navigating deeper with additional calls to getTypeImpl() or getImpl(). Here's a representative example:
proc mangledNameAux(t: NimNode): string =
case t.typeKind
of ntyAlias:
assert(t.kind == nnkSym)
let impl = t.symbol.getImpl()
assert(impl.kind == nnkTypeDef)
result = mangledNameAux(impl[^1])
. . .
of ntySequence:
let impl = t.getTypeImpl()
assert impl.kind == nnkBracketExpr
assert impl.len == 2
result = "seq[" & mangledNameAux(impl[^1]) & "]"
. . .
Thoughts on the pros and cons of these two approaches?
Dean
typeinfo is runtime type information, comparable to Java's reflection (but weaker in its capabilities). It also is pretty unsafe as documented. getTypeImpl() came much later in Nim's development and offers similar things but works completely at compile-time. It's the preferred solution, but compile-time introspection can be harder to write than runtime introspection.
In the longer run I would like to deprecate runtime type information.
In response to my PR, @timotheecour described a pre-compiled debugging plugin he has built that makes extensive use of RTTI including Any. Here are some excerpts from his comments:
I rely on typeinfo.nim for a lldb plugin I wrote that allows to use nim plugins during a debugging session (and can do more stuff then nim-gdb python plugin, including printing enum names, fully customizable pretty printing etc).
I already implemented it and it works, and is 100% dependent on RTTI; I can share more details if needed, but in short, it converts a (pointer, type_name) (provided by debugger, in my case lldb but should work with gdb), to a (pointer, PNimType) thanks to a mapping from type_name to PNimType; from there i get a Any, and from there I can do arbitrary data navigation (read/write/call) using typeinfo api
So, @Araq, what are the liabilities of RTTI that lead you to say "In the longer run I would like to deprecate runtime type information."? And could you unpack why you say macros.getTypeImpl and similar are the preferred solution?