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!

Reply via email to