How can i introspect a proc symbol passed to a template / macro ?
I would like to extract the return type (i've tried typeof(proc()) but requires that i pass the correct number of arguments) and the arguments types, but couldn't figure it out after looking at std/typetraits.
import std/macros
func f(x: int): seq[string] =
discard
macro getReturnType(x: proc): untyped = x.getimpl.params[0]
echo getReturnType(f)
echo default(getReturnType(f)) & "monkeys"
It doesn't return a NimNode, it does a code replacement (that's what macros do) of the return type of the proc.
That can be demonstrated with the following code
import std/macros
func f(x: int): seq[string] =
discard
macro getReturnType(x: proc): untyped = x.getimpl.params[0]
echo getReturnType(f) is typedesc[seq[string]]
https://play.nim-lang.org/#ix=3WhbYeah i see, but then any passed type inside a macro is not possible to "materialize" it.
import std/macros
func f(x: int): seq[string] =
discard
macro getReturnType(x: proc): untyped =
var x : x.getimpl.params[0] = @["doesn't"]
result = x.getimpl.params[0]
var x : getReturnType(f) = @["works"]
I'm not sure what are you trying to do. What is the seq with a string in is supposed to be? A type? You also redeclared x.
What are you trying to accomplish?
A macro is code manipulation tool, you can't return a variable initialized inside the macro. What you can do is generate the AST that will initialize a variable.
Is this what you wanted?
import std/[macros, strformat, strutils]
func f(x: int): seq[string] =
discard
macro getReturnType(x: proc): untyped = x.getimpl.params[0]
let
x: getReturnType(f) = "this is a sequence of strings".split
y = default(getReturnType(f))
echo &"{x = }\n{y = }"
I would recommend doing the least necessary inside the macro.
You could technically also do this.
import std/[genasts, macros, strformat, strutils]
func f(x: int): seq[string] =
discard
macro getReturnType(x: proc): untyped = x.getimpl.params[0]
macro newVarWithReturnType(x: proc, name: untyped): untyped =
result = genast(name=name, returntype = x.getimpl.params[0]):
var name: returntype
let
x: getReturnType(f) = "this is a sequence of strings".split
y = default(getReturnType(f))
echo &"{x = }\n{y = }"
newVarWithReturnType(f, myVar)
myVar &= x
echo &"{myVar = }"
At this time i'm just experimenting. This is what i'm trying to do, more or less:
import std/[macros, strformat, strutils]
func f(x: int): seq[string] =
discard
macro getReturnType(x: proc): untyped =
x.getimpl.params[0]
macro doStuffWithReturnType(x: proc): untyped =
let a: getReturnType(x) = "this is a sequence of strings".split
if a[0] == "this":
newIdentNode("int")
else:
newIdentNode("float")
let
x: doStuffWithReturnType(f) = 1
echo &"{x = }"
try this
import std/[macros, strformat, strutils]
func f(x: int): seq[string] =
discard
proc getReturnType(x: NimNode): NimNode =
x.getimpl.params[0]
macro doStuffWithReturnType(x: proc): untyped =
let
rT = getReturnType(x)
result = quote do:
macro t() : untyped {.gensym.} =
let a: `rT` = "this is a sequence of strings".split
if a[0] == "this":
ident("int")
else:
ident("float")
t()
let
x: doStuffWithReturnType(f) = 1
echo &"{x = }"
I know that this is just an example, but what are you trying to do?
The reason this doesn't work, is because the parameter passed in to the macro is a proc type, but because macros operate on ASTs it is converted to a NimNode. If you just want to get the return type, you wouldn't use a macro inside the macro. Here is an example of something you could do, but it's hard to know what you're getting after.
import std/[genasts, macros, strformat]
func f(x: int): seq[string] =
discard
func getReturnType(x: NimNode): NimNode =
debugecho x.kind == nnkSym and x.symkind in [nskProc, nskFunc]
result = x.getimpl.params[0]
macro doStuffWithReturnType(x: proc): untyped =
result =
case repr getreturntype(x)
of "int":
ident "string"
of "seq[string]":
ident "int"
else:
genast:
seq[int]
let
x: doStuffWithReturnType(f) = 1
echo &"{x = }"
https://play.nim-lang.org/#ix=3WlMIn your example you have to manage the return type by comparing its string representation, not manipulating the actual type. If you were to know the size in bytes of the type returned from the passed proc you need to switch over the string and create a map that will map the string representation of a type to its size. Then what about a type not known by the macro ?
I'm trying to decompose a proc to it's returned and passed arguments, but not as strings or AST nodes, but as concrete types as if i was to introspect the real type definitions.
Think of this, i want a way to extract the size in bytes of the returned type of a procedure:
proc getReturnTypeSizeInBytes(p: proc): int64 {.compiletime.} = ...
proc returnsAnInteger64(): int64 = discard
proc returnsAnObject(): SomeObject = discard
static:
assert getReturnTypeSizeInBytes(returnsAnInteger64) == 8
assert getReturnTypeSizeInBytes(returnsAnObject) == 32
This can be easily achieved for both return type and arguments types in c++ by using a bit of template metaprogramming https://godbolt.org/z/EEbn5oxfb
As i understood, macros can only manipulate the AST, so you can't work with the type system at that level because everything you receive is a NimNode and the place where they will "materialize" is after the macro has finished. Is it possible to write such generic code in nim, using template or compiletime procedure ?
This should work
import std/[macros]
func f(x: int, y: seq[string], z: cint): seq[string] =
discard
proc expectSymKind*(n: NimNode, k: set[NimSymKind]) =
if n.symkind notin k:
error("Expected one of " & $k & ", got " & $n.kind, n)
macro returnType(x: proc): untyped =
x.expectKind nnkSym
x.expectSymKind {nskProc, nskFunc}
result = x.getimpl.params[0]
macro nthArgType(x: proc, n: static int): untyped =
x.expectKind nnkSym
x.expectSymKind {nskProc, nskFunc}
result = x.getimpl.params[1+n][1]
echo treerepr result
echo sizeof returntype f
echo sizeof nthArgType(f, 1)
https://play.nim-lang.org/#ix=3WoA