Simple example:
proc foo[T](self: T) {.bar.} = discard
foo(3)
bar only gets idents instead of symbols since it is evaluated instantly instead
of every time foo is instantiated for a new concrete type. This is really
frustrating when requiring the type of T in the macro.
I probably should quickly go over my actual use case: Nim is by and large fast
enough that you can just focus on writing readable code and as long as
algorithm and data structure choice aren't god aweful it will be fast enough.
For the occasional very tight loop actual optimization might be necessary which
in large parts means minimizing copying and allocations.
So I figured that a macro that you can put on the actually important functions
and which emits warnings on implicit copying might make this a lot less
annoying. To check whether a copy will actually allocate memory the macro has
to look up whether the type contains any strings or seqs, though, so this
doesn't function at all for generics. Any way to work around this?
Small addendum, each call to warning actually emits two warnings - one for the
macro instantiation location and one for the actual warning. Is there a
reasonable way to silence the first one? Currently the output looks like this:
> test.nim(73, 17) template/generic instantiation from here
>
> test.nim(4, 10) Warning: implicit copy: test.nim(75,7) [User]
>
> test.nim(73, 17) template/generic instantiation from here
>
> test.nim(4, 10) Warning: implicit copy: test.nim(80,9) [User]
>
> test.nim(94, 1) template/generic instantiation from here
>
> test.nim(4, 10) Warning: implicit copy: test.nim(97,8) [User]
If anyone is interested, the macro currently looks like this. Sorry that the
code blocks bloat the post so much, really wish they could be hidden behind
spoiler tags or limited in size with scrollbar.
import macros
proc warnImplicit(n: NimNode) {.compiletime.} =
warning("implicit copy: " & n.lineinfo)
proc isHeapValue(n: NimNode): bool{.compiletime.} =
eqIdent(n, "seq") or eqIdent(n, "string")
proc containsHeapValue(t: NimNode): bool {.compiletime.} =
# true if type includes seq or string
case t.kind
of nnkObjectTy:
for field in t[^1]:
if field.getType.containsHeapValue(): return true
of nnkTupleTy:
for field in t:
if field.getType.containsHeapValue(): return true
of nnkBracketExpr:
let baseType = t[0]
if baseType.isHeapValue: return true
if eqIdent(baseType, "tuple"):
for i in 1..<t.len:
if t[i].containsHeapValue(): return true
of nnkSym:
return t.isHeapValue
else: return false
proc checkHeapValue(n: NimNode, mutable: bool) {.compiletime.} =
# traverse right hand side of assignment and search for implicit copies
if n.isHeapValue:
if mutable: n.warnImplicit
case n.kind
of nnkPar, nnkBracket, nnkObjConstr:
for child in n:
# deep copies regardless whether assignment is mutable
child.checkHeapValue(true)
of nnkCall, nnkPrefix, nnkPostFix, nnkDotCall, nnkCommand:
# rvalues
discard
of nnkEmpty, nnkIdent: discard
else:
if mutable and n.getType().containsHeapValue():
n.warnImplicit()
proc findDeepCopies(n: NimNode, global: bool) {.compiletime.} =
# find all assignments within n
case n.kind
of nnkAsgn, nnkIdentDefs:
n[^1].checkHeapValue(mutable = true)
of nnkLetSection:
for child in n:
# global namespace sees your immutability, global namespace don't
care
child[^1].checkHeapValue(mutable = global)
of nnkProcDef:
for child in n[^1]:
child.findDeepCopies(global = false)
else:
for child in n:
child.findDeepCopies(global = global)
macro checkexplicit*(d: typed, global = true): typed =
d.findDeepCopies(global = true)
if d.kind != nnkProcDef:
result = d
proc copy*[T](a: T): T = a
And the test code that emitted these warnings looks like this:
type
A = object
a: B
B = object
b: C
C = (string, int)
var
glob = A(a: B(b: ("hi", 1)))
proc test(a: A) {.checkexplicit.} =
var
g = a # copy
f = a.copy # copy without warning
let
b = a # no copy
c = a.a # no copy
d = (a.a, 1) # copy
e = (a.a.copy, 1) # copy without warning
echo repr glob.a
echo repr a.a
echo repr b.a
echo repr c
echo repr d[0]
echo repr e[0]
echo repr f.a
echo repr g.a
glob.test()
checkExplicit:
let
a = @[1, 2]
b = a # copy
I almost certainly forgot a bunch of cases so if you notice anything obvious
please tell!