summary refs log tree commit diff stats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/async/tasyncdiscard.nim2
-rw-r--r--tests/async/tasynctry.nim10
-rw-r--r--tests/ccgbugs/twrong_string_asgn.nim2
-rw-r--r--tests/enum/tenum.nim6
-rw-r--r--tests/float/tfloat4.nim32
-rw-r--r--tests/float/tfloat5.nim15
-rw-r--r--tests/float/tfloat6.nim21
-rw-r--r--tests/float/tfloat7.nim26
-rw-r--r--tests/gc/thavlak.nim457
-rw-r--r--tests/gc/tlists.nim37
-rw-r--r--tests/generics/tgenerictmpl2.nim31
-rw-r--r--tests/js/tclosures.nim2
-rw-r--r--tests/js/test2.nim13
-rw-r--r--tests/js/testtojsstr.nim8
-rw-r--r--tests/lexer/tstrlits.nim6
-rw-r--r--tests/macros/tgettypeinst.nim122
-rw-r--r--tests/manyloc/keineschweine/lib/vehicles.nim2
-rw-r--r--tests/manyloc/nake/nakefile.nim4
-rw-r--r--tests/manyloc/named_argument_bug/main.nim.cfg1
-rw-r--r--tests/manyloc/named_argument_bug/tri_engine/math/circle.nim4
-rw-r--r--tests/manyloc/named_argument_bug/tri_engine/math/vec.nim2
-rw-r--r--tests/overload/tstmtoverload.nim2
-rw-r--r--tests/parallel/twrong_refcounts.nim2
-rw-r--r--tests/parser/tdo.nim79
-rw-r--r--tests/stdlib/tmath.nim2
-rw-r--r--tests/stdlib/tparscfg.nim58
-rw-r--r--tests/stdlib/tunittest.nim2
-rw-r--r--tests/template/t2do.nim7
-rw-r--r--tests/testament/categories.nim5
-rw-r--r--tests/threads/ttryrecv.nim2
-rw-r--r--tests/trmacros/tstatic_t_bug.nim24
-rw-r--r--tests/usingstmt/tthis.nim15
-rw-r--r--tests/vm/meta.nim240
-rw-r--r--tests/vm/tcomponent.nim132
34 files changed, 1313 insertions, 60 deletions
diff --git a/tests/async/tasyncdiscard.nim b/tests/async/tasyncdiscard.nim
index 71aba29e2..e7c87ad42 100644
--- a/tests/async/tasyncdiscard.nim
+++ b/tests/async/tasyncdiscard.nim
@@ -36,4 +36,4 @@ proc main {.async.} =
   discard await g()
   echo 6
 
-asyncCheck main()
+waitFor(main())
diff --git a/tests/async/tasynctry.nim b/tests/async/tasynctry.nim
index f77198e2e..5930f296f 100644
--- a/tests/async/tasynctry.nim
+++ b/tests/async/tasynctry.nim
@@ -48,7 +48,7 @@ proc catch() {.async.} =
   except OSError, EInvalidField:
     assert false
 
-asyncCheck catch()
+waitFor catch()
 
 proc test(): Future[bool] {.async.} =
   result = false
@@ -92,13 +92,13 @@ proc test4(): Future[int] {.async.} =
     result = 2
 
 var x = test()
-assert x.read
+assert x.waitFor()
 
 x = test2()
-assert x.read
+assert x.waitFor()
 
 var y = test3()
-assert y.read == 2
+assert y.waitFor() == 2
 
 y = test4()
-assert y.read == 2
+assert y.waitFor() == 2
diff --git a/tests/ccgbugs/twrong_string_asgn.nim b/tests/ccgbugs/twrong_string_asgn.nim
index b62e70e7c..669b7f8f5 100644
--- a/tests/ccgbugs/twrong_string_asgn.nim
+++ b/tests/ccgbugs/twrong_string_asgn.nim
@@ -16,4 +16,4 @@ x.callback =
   proc () =
     finished = true
 
-while not finished: discard
+while not finished: poll()
diff --git a/tests/enum/tenum.nim b/tests/enum/tenum.nim
index b081212e6..6d9bdd539 100644
--- a/tests/enum/tenum.nim
+++ b/tests/enum/tenum.nim
@@ -6,3 +6,9 @@ type
 var
   en: E
 en = a
+
+# Bug #4066
+import macros
+macro genEnum(): untyped = newNimNode(nnkEnumTy).add(newEmptyNode(), newIdentNode("geItem1"))
+type GeneratedEnum = genEnum()
+doAssert(type(geItem1) is GeneratedEnum)
diff --git a/tests/float/tfloat4.nim b/tests/float/tfloat4.nim
index 960c4e5f7..006b4d88f 100644
--- a/tests/float/tfloat4.nim
+++ b/tests/float/tfloat4.nim
@@ -1,3 +1,8 @@
+discard """
+  file: "tfloat4.nim"
+  output: "passed all tests."
+  exitcode: 0
+"""
 import math, strutils
 
 proc c_sprintf(buf, fmt: cstring) {.importc:"sprintf", header: "<stdio.h>", varargs.}
@@ -11,8 +16,9 @@ proc floatToStr(f: float64): string =
       return
     add(result, ch)
 
+
 let testFloats = [
-  "0", "-1", "1", "1.", ".3", "3.3", "-.3", "-99.99",
+  "0", "-0", "0.", "0.0", "-0.", "-0.0", "-1", "1", "1.", ".3", "3.3", "-.3", "-99.99",
   "1.1e10", "-2e100", "1.234e-10", "1.234e+10",
   "-inf", "inf", "+inf",
   "3.14159265358979323846264338327950288",
@@ -25,18 +31,20 @@ let testFloats = [
 ]
 
 for num in testFloats:
-  assert num.parseFloat.floatToStr.parseFloat == num.parseFloat
+  doAssert num.parseFloat.floatToStr.parseFloat == num.parseFloat
 
-assert "0".parseFloat == 0.0
-assert "-.1".parseFloat == -0.1
-assert "2.5e1".parseFloat == 25.0
-assert "1e10".parseFloat == 10_000_000_000.0
-assert "0.000_005".parseFloat == 5.000_000e-6
-assert "1.234_567e+2".parseFloat == 123.4567
-assert "1e1_00".parseFloat == "1e100".parseFloat
-assert "3.1415926535897932384626433".parseFloat ==
+doAssert "0".parseFloat == 0.0
+doAssert "-.1".parseFloat == -0.1
+doAssert "2.5e1".parseFloat == 25.0
+doAssert "1e10".parseFloat == 10_000_000_000.0
+doAssert "0.000_005".parseFloat == 5.000_000e-6
+doAssert "1.234_567e+2".parseFloat == 123.4567
+doAssert "1e1_00".parseFloat == "1e100".parseFloat
+doAssert "3.1415926535897932384626433".parseFloat ==
        3.1415926535897932384626433
-assert "2.71828182845904523536028747".parseFloat ==
+doAssert "2.71828182845904523536028747".parseFloat ==
        2.71828182845904523536028747
-assert 0.00097656250000000021684043449710088680149056017398834228515625 ==
+doAssert 0.00097656250000000021684043449710088680149056017398834228515625 ==
      "0.00097656250000000021684043449710088680149056017398834228515625".parseFloat
+
+echo("passed all tests.")
diff --git a/tests/float/tfloat5.nim b/tests/float/tfloat5.nim
new file mode 100644
index 000000000..aa7dc6c53
--- /dev/null
+++ b/tests/float/tfloat5.nim
@@ -0,0 +1,15 @@
+discard """
+  file: "tfloat5.nim"
+  output: '''0 : 0.0
+0 : 0.0
+0 : 0.0
+0 : 0.0'''
+"""
+
+import parseutils
+
+var f: float
+echo "*".parseFloat(f), " : ", f
+echo "/".parseFloat(f), " : ", f
+echo "+".parseFloat(f), " : ", f
+echo "-".parseFloat(f), " : ", f
diff --git a/tests/float/tfloat6.nim b/tests/float/tfloat6.nim
new file mode 100644
index 000000000..721abd721
--- /dev/null
+++ b/tests/float/tfloat6.nim
@@ -0,0 +1,21 @@
+discard """
+  file: "tfloat6.nim"
+  output: '''1e-06 : 1e-06
+1e-06 : 1e-06
+0.001 : 0.001
+1e-06 : 1e-06
+1e-06 : 1e-06
+10.000001 : 10.000001
+100.000001 : 100.000001'''
+"""
+
+import strutils
+
+echo "0.00_0001".parseFloat(), " : ", 1E-6
+echo "0.00__00_01".parseFloat(), " : ", 1E-6
+echo "0.0_01".parseFloat(), " : ", 0.001
+echo "0.00_000_1".parseFloat(), " : ", 1E-6
+echo "0.00000_1".parseFloat(), " : ", 1E-6
+
+echo "1_0.00_0001".parseFloat(), " : ", 10.000001
+echo "1__00.00_0001".parseFloat(), " : ", 1_00.000001
diff --git a/tests/float/tfloat7.nim b/tests/float/tfloat7.nim
new file mode 100644
index 000000000..2337d1dd4
--- /dev/null
+++ b/tests/float/tfloat7.nim
@@ -0,0 +1,26 @@
+discard """
+  file: "tfloat6.nim"
+  output: '''passed.
+passed.
+passed.
+passed.
+passed.
+passed.
+passed.'''
+"""
+
+import strutils
+template expect_fail(x: expr) =
+  try:
+    discard x
+    echo("expected to fail!")
+  except ValueError:
+    echo("passed.")
+
+expect_fail("1_0._00_0001".parseFloat())
+expect_fail("_1_0_00.0001".parseFloat())
+expect_fail("10.00.01".parseFloat())
+expect_fail("10.00E_01".parseFloat())
+expect_fail("10.00E_01".parseFloat())
+expect_fail("10.00E".parseFloat())
+expect_fail("10.00A".parseFloat())
diff --git a/tests/gc/thavlak.nim b/tests/gc/thavlak.nim
new file mode 100644
index 000000000..efab49e36
--- /dev/null
+++ b/tests/gc/thavlak.nim
@@ -0,0 +1,457 @@
+discard """
+  output: '''Welcome to LoopTesterApp, Nim edition
+Constructing Simple CFG...
+15000 dummy loops
+Constructing CFG...
+Performing Loop Recognition
+1 Iteration
+Another 50 iterations...
+..................................................
+Found 1 loops (including artificial root node) (50)'''
+"""
+
+# bug #3184
+
+import tables
+import sequtils
+import sets
+
+type
+  BasicBlock = object
+    inEdges: seq[ref BasicBlock]
+    outEdges: seq[ref BasicBlock]
+    name: int
+
+proc newBasicBlock(name: int): ref BasicBlock =
+  new(result)
+  result.inEdges = newSeq[ref BasicBlock]()
+  result.outEdges = newSeq[ref BasicBlock]()
+  result.name = name
+
+proc hash(x: ref BasicBlock): int {.inline.} =
+  result = x.name
+
+type
+  BasicBlockEdge = object
+    fr: ref BasicBlock
+    to: ref BasicBlock
+
+  Cfg = object
+    basicBlockMap: Table[int, ref BasicBlock]
+    edgeList: seq[BasicBlockEdge]
+    startNode: ref BasicBlock
+
+proc newCfg(): Cfg =
+  result.basicBlockMap = initTable[int, ref BasicBlock]()
+  result.edgeList = newSeq[BasicBlockEdge]()
+
+proc createNode(self: var Cfg, name: int): ref BasicBlock =
+  result = self.basicBlockMap.getOrDefault(name)
+  if result == nil:
+    result = newBasicBlock(name)
+    self.basicBlockMap.add name, result
+
+  if self.startNode == nil:
+    self.startNode = result
+
+proc addEdge(self: var Cfg, edge: BasicBlockEdge) =
+  self.edgeList.add(edge)
+
+proc getNumNodes(self: Cfg): int =
+  self.basicBlockMap.len
+
+proc newBasicBlockEdge(cfg: var Cfg, fromName: int, toName: int): BasicBlockEdge =
+  result.fr = cfg.createNode(fromName)
+  result.to = cfg.createNode(toName)
+  result.fr.outEdges.add(result.to)
+  result.to.inEdges.add(result.fr)
+  cfg.addEdge(result)
+
+type
+  SimpleLoop = object
+    basicBlocks: seq[ref BasicBlock] # TODO: set here
+    children: seq[ref SimpleLoop] # TODO: set here
+    parent: ref SimpleLoop
+    header: ref BasicBlock
+    isRoot: bool
+    isReducible: bool
+    counter: int
+    nestingLevel: int
+    depthLevel: int
+
+proc newSimpleLoop(): ref SimpleLoop =
+  new(result)
+  result.basicBlocks = newSeq[ref BasicBlock]()
+  result.children = newSeq[ref SimpleLoop]()
+  result.parent = nil
+  result.header = nil
+  result.isRoot = false
+  result.isReducible = true
+  result.counter = 0
+  result.nestingLevel = 0
+  result.depthLevel = 0
+
+proc addNode(self: ref SimpleLoop, bb: ref BasicBlock) =
+  self.basicBlocks.add bb
+
+proc addChildLoop(self: ref SimpleLoop, loop: ref SimpleLoop) =
+  self.children.add loop
+
+proc setParent(self: ref SimpleLoop, parent: ref SimpleLoop) =
+  self.parent = parent
+  self.parent.addChildLoop(self)
+
+proc setHeader(self: ref SimpleLoop, bb: ref BasicBlock) =
+  self.basicBlocks.add(bb)
+  self.header = bb
+
+proc setNestingLevel(self: ref SimpleLoop, level: int) =
+  self.nestingLevel = level
+  if level == 0: self.isRoot = true
+
+var loop_counter: int = 0
+
+type
+  Lsg = object
+    loops: seq[ref SimpleLoop]
+    root: ref SimpleLoop
+
+proc createNewLoop(self: var Lsg): ref SimpleLoop =
+  result = newSimpleLoop()
+  loop_counter += 1
+  result.counter = loop_counter
+
+proc addLoop(self: var Lsg, l: ref SimpleLoop) =
+  self.loops.add l
+
+proc newLsg(): Lsg =
+  result.loops = newSeq[ref SimpleLoop]()
+  result.root = result.createNewLoop()
+  result.root.setNestingLevel(0)
+  result.addLoop(result.root)
+
+proc getNumLoops(self: Lsg): int =
+  self.loops.len
+
+type
+  UnionFindNode = object
+    parent: ref UnionFindNode
+    bb: ref BasicBlock
+    l: ref SimpleLoop
+    dfsNumber: int
+
+proc newUnionFindNode(): ref UnionFindNode =
+  new(result)
+  when false:
+    result.parent = nil
+    result.bb = nil
+    result.l = nil
+    result.dfsNumber = 0
+
+proc initNode(self: ref UnionFindNode, bb: ref BasicBlock, dfsNumber: int) =
+  self.parent = self
+  self.bb = bb
+  self.dfsNumber = dfsNumber
+
+proc findSet(self: ref UnionFindNode): ref UnionFindNode =
+  var nodeList = newSeq[ref UnionFindNode]()
+  result = self
+
+  while result != result.parent:
+    var parent = result.parent
+    if parent != parent.parent: nodeList.add result
+    result = parent
+
+  for iter in nodeList: iter.parent = result.parent
+
+proc union(self: ref UnionFindNode, unionFindNode: ref UnionFindNode) =
+  self.parent = unionFindNode
+
+
+const
+  BB_TOP          = 0 # uninitialized
+  BB_NONHEADER    = 1 # a regular BB
+  BB_REDUCIBLE    = 2 # reducible loop
+  BB_SELF         = 3 # single BB loop
+  BB_IRREDUCIBLE  = 4 # irreducible loop
+  BB_DEAD         = 5 # a dead BB
+  BB_LAST         = 6 # Sentinel
+
+  # # Marker for uninitialized nodes.
+  UNVISITED = -1
+
+  # # Safeguard against pathologic algorithm behavior.
+  MAXNONBACKPREDS = (32 * 1024)
+
+type
+  HavlakLoopFinder = object
+    cfg: Cfg
+    lsg: Lsg
+
+proc newHavlakLoopFinder(cfg: Cfg, lsg: Lsg): HavlakLoopFinder =
+  result.cfg = cfg
+  result.lsg = lsg
+
+proc isAncestor(w: int, v: int, last: seq[int]): bool =
+  w <= v and v <= last[w]
+
+proc dfs(currentNode: ref BasicBlock, nodes: var seq[ref UnionFindNode], number: var Table[ref BasicBlock, int], last: var seq[int], current: int): int =
+  var stack = @[(currentNode, current)]
+  while stack.len > 0:
+    let (currentNode, current) = stack.pop()
+    nodes[current].initNode(currentNode, current)
+    number[currentNode] = current
+
+    result = current
+    for target in currentNode.outEdges:
+      if number[target] == UNVISITED:
+        stack.add((target, result+1))
+        #result = dfs(target, nodes, number, last, result + 1)
+  last[number[currentNode]] = result
+
+proc findLoops(self: var HavlakLoopFinder): int =
+  var startNode = self.cfg.startNode
+  if startNode == nil: return 0
+  var size = self.cfg.getNumNodes
+
+  var nonBackPreds    = newSeq[HashSet[int]]()
+  var backPreds       = newSeq[seq[int]]()
+  var number          = initTable[ref BasicBlock, int]()
+  var header          = newSeq[int](size)
+  var types           = newSeq[int](size)
+  var last            = newSeq[int](size)
+  var nodes           = newSeq[ref UnionFindNode]()
+
+  for i in 1..size:
+    nonBackPreds.add initSet[int](1)
+    backPreds.add newSeq[int]()
+    nodes.add newUnionFindNode()
+
+  # Step a:
+  #   - initialize all nodes as unvisited.
+  #   - depth-first traversal and numbering.
+  #   - unreached BB's are marked as dead.
+  #
+  for v in self.cfg.basicBlockMap.values: number[v] = UNVISITED
+  var res = dfs(startNode, nodes, number, last, 0)
+
+  # Step b:
+  #   - iterate over all nodes.
+  #
+  #   A backedge comes from a descendant in the DFS tree, and non-backedges
+  #   from non-descendants (following Tarjan).
+  #
+  #   - check incoming edges 'v' and add them to either
+  #     - the list of backedges (backPreds) or
+  #     - the list of non-backedges (nonBackPreds)
+  #
+  for w in 0 .. <size:
+    header[w] = 0
+    types[w]  = BB_NONHEADER
+
+    var nodeW = nodes[w].bb
+    if nodeW != nil:
+      for nodeV in nodeW.inEdges:
+        var v = number[nodeV]
+        if v != UNVISITED:
+          if isAncestor(w, v, last):
+            backPreds[w].add v
+          else:
+            nonBackPreds[w].incl v
+    else:
+      types[w] = BB_DEAD
+
+  # Start node is root of all other loops.
+  header[0] = 0
+
+  # Step c:
+  #
+  # The outer loop, unchanged from Tarjan. It does nothing except
+  # for those nodes which are the destinations of backedges.
+  # For a header node w, we chase backward from the sources of the
+  # backedges adding nodes to the set P, representing the body of
+  # the loop headed by w.
+  #
+  # By running through the nodes in reverse of the DFST preorder,
+  # we ensure that inner loop headers will be processed before the
+  # headers for surrounding loops.
+
+  for w in countdown(size - 1, 0):
+    # this is 'P' in Havlak's paper
+    var nodePool = newSeq[ref UnionFindNode]()
+
+    var nodeW = nodes[w].bb
+    if nodeW != nil: # dead BB
+      # Step d:
+      for v in backPreds[w]:
+        if v != w:
+          nodePool.add nodes[v].findSet
+        else:
+          types[w] = BB_SELF
+
+      # Copy nodePool to workList.
+      #
+      var workList = newSeq[ref UnionFindNode]()
+      for x in nodePool: workList.add x
+
+      if nodePool.len != 0: types[w] = BB_REDUCIBLE
+
+      # work the list...
+      #
+      while workList.len > 0:
+        var x = workList[0]
+        workList.del(0)
+
+        # Step e:
+        #
+        # Step e represents the main difference from Tarjan's method.
+        # Chasing upwards from the sources of a node w's backedges. If
+        # there is a node y' that is not a descendant of w, w is marked
+        # the header of an irreducible loop, there is another entry
+        # into this loop that avoids w.
+        #
+
+        # The algorithm has degenerated. Break and
+        # return in this case.
+        #
+        var nonBackSize = nonBackPreds[x.dfsNumber].len
+        if nonBackSize > MAXNONBACKPREDS: return 0
+
+        for iter in nonBackPreds[x.dfsNumber]:
+          var y = nodes[iter]
+          var ydash = y.findSet
+
+          if not isAncestor(w, ydash.dfsNumber, last):
+            types[w] = BB_IRREDUCIBLE
+            nonBackPreds[w].incl ydash.dfsNumber
+          else:
+            if ydash.dfsNumber != w and not nodePool.contains(ydash):
+              workList.add ydash
+              nodePool.add ydash
+
+      # Collapse/Unionize nodes in a SCC to a single node
+      # For every SCC found, create a loop descriptor and link it in.
+      #
+      if (nodePool.len > 0) or (types[w] == BB_SELF):
+        var l = self.lsg.createNewLoop
+
+        l.setHeader(nodeW)
+        l.isReducible = types[w] != BB_IRREDUCIBLE
+
+        # At this point, one can set attributes to the loop, such as:
+        #
+        # the bottom node:
+        #    iter  = backPreds(w).begin();
+        #    loop bottom is: nodes(iter).node;
+        #
+        # the number of backedges:
+        #    backPreds(w).size()
+        #
+        # whether this loop is reducible:
+        #    types(w) != BB_IRREDUCIBLE
+        #
+        nodes[w].l = l
+
+        for node in nodePool:
+          # Add nodes to loop descriptor.
+          header[node.dfsNumber] = w
+          node.union(nodes[w])
+
+          # Nested loops are not added, but linked together.
+          var node_l = node.l
+          if node_l != nil:
+            node_l.setParent(l)
+          else:
+            l.addNode(node.bb)
+
+        self.lsg.addLoop(l)
+
+  result = self.lsg.getNumLoops
+
+
+type
+  LoopTesterApp = object
+    cfg: Cfg
+    lsg: Lsg
+
+proc newLoopTesterApp(): LoopTesterApp =
+  result.cfg = newCfg()
+  result.lsg = newLsg()
+
+proc buildDiamond(self: var LoopTesterApp, start: int): int =
+  var bb0 = start
+  var x1 = newBasicBlockEdge(self.cfg, bb0, bb0 + 1)
+  var x2 = newBasicBlockEdge(self.cfg, bb0, bb0 + 2)
+  var x3 = newBasicBlockEdge(self.cfg, bb0 + 1, bb0 + 3)
+  var x4 = newBasicBlockEdge(self.cfg, bb0 + 2, bb0 + 3)
+  result = bb0 + 3
+
+proc buildConnect(self: var LoopTesterApp, start1: int, end1: int) =
+  var x1 = newBasicBlockEdge(self.cfg, start1, end1)
+
+proc buildStraight(self: var LoopTesterApp, start: int, n: int): int =
+  for i in 0..n-1:
+    self.buildConnect(start + i, start + i + 1)
+  result = start + n
+
+proc buildBaseLoop(self: var LoopTesterApp, from1: int): int =
+  var header   = self.buildStraight(from1, 1)
+  var diamond1 = self.buildDiamond(header)
+  var d11      = self.buildStraight(diamond1, 1)
+  var diamond2 = self.buildDiamond(d11)
+  var footer   = self.buildStraight(diamond2, 1)
+
+  self.buildConnect(diamond2, d11)
+  self.buildConnect(diamond1, header)
+  self.buildConnect(footer, from1)
+  result = self.buildStraight(footer, 1)
+
+proc run(self: var LoopTesterApp) =
+  echo "Welcome to LoopTesterApp, Nim edition"
+  echo "Constructing Simple CFG..."
+
+  var x1 = self.cfg.createNode(0)
+  var x2 = self.buildBaseLoop(0)
+  var x3 = self.cfg.createNode(1)
+  self.buildConnect(0, 2)
+
+  echo "15000 dummy loops"
+
+  for i in 1..15000:
+    var h = newHavlakLoopFinder(self.cfg, newLsg())
+    var res = h.findLoops
+
+  echo "Constructing CFG..."
+  var n = 2
+
+  for parlooptrees in 1..10:
+    var x6 = self.cfg.createNode(n + 1)
+    self.buildConnect(2, n + 1)
+    n += 1
+    for i in 1..100:
+      var top = n
+      n = self.buildStraight(n, 1)
+      for j in 1..25: n = self.buildBaseLoop(n)
+      var bottom = self.buildStraight(n, 1)
+      self.buildConnect n, top
+      n = bottom
+    self.buildConnect(n, 1)
+
+  echo "Performing Loop Recognition\n1 Iteration"
+
+  var h = newHavlakLoopFinder(self.cfg, newLsg())
+  var loops = h.findLoops
+
+  echo "Another 50 iterations..."
+
+  var sum = 0
+  for i in 1..50:
+    write stdout, "."
+    flushFile(stdout)
+    var hlf = newHavlakLoopFinder(self.cfg, newLsg())
+    sum += hlf.findLoops
+    #echo getOccupiedMem()
+  echo "\nFound ", loops, " loops (including artificial root node) (", sum, ")"
+
+var l = newLoopTesterApp()
+l.run
diff --git a/tests/gc/tlists.nim b/tests/gc/tlists.nim
new file mode 100644
index 000000000..26b32396c
--- /dev/null
+++ b/tests/gc/tlists.nim
@@ -0,0 +1,37 @@
+discard """
+    output: '''Success'''
+"""
+
+# bug #3793
+
+import os
+import math
+import lists
+import strutils
+
+proc mkleak() =
+    # allocate 10 MB via linked lists
+    let numberOfLists = 100
+    for i in countUp(1, numberOfLists):
+        var leakList = initDoublyLinkedList[string]()
+        let numberOfLeaks = 50000
+        for j in countUp(1, numberOfLeaks):
+            let leakSize = 200
+            let leaked = newString(leakSize)
+            leakList.append(leaked)
+
+proc mkManyLeaks() =
+    for i in 0..0:
+        when false: echo getOccupiedMem()
+        mkleak()
+        when false: echo getOccupiedMem()
+        # Force a full collection. This should free all of the
+        # lists and bring the memory usage down to a few MB's.
+        GC_fullCollect()
+        when false: echo getOccupiedMem()
+        if getOccupiedMem() > 8 * 200 * 50_000 * 2:
+          echo GC_getStatistics()
+          quit "leaking"
+    echo "Success"
+
+mkManyLeaks()
diff --git a/tests/generics/tgenerictmpl2.nim b/tests/generics/tgenerictmpl2.nim
new file mode 100644
index 000000000..0ecaf9ded
--- /dev/null
+++ b/tests/generics/tgenerictmpl2.nim
@@ -0,0 +1,31 @@
+discard """
+  output: '''1
+1
+1
+1
+999
+999
+999
+2'''
+"""
+
+# test if we can pass explicit generic arguments to generic templates
+# based on bug report #3496
+
+proc     tproc[T](t: T = 999) = echo t
+template ttmpl[T](t: T = 999) = echo t
+
+tproc(1)
+tproc[int](1)
+ttmpl(1)
+ttmpl[int](1) #<- crash case #1
+
+tproc[int]()
+discard tproc[int]
+ttmpl[int]()  #<- crash case #2
+ttmpl[int]    #<- crash case #3
+
+# but still allow normal use of [] on non-generic templates
+
+template tarr: expr = [1, 2, 3, 4]
+echo tarr[1]
diff --git a/tests/js/tclosures.nim b/tests/js/tclosures.nim
index c0d93814c..0ec4f4743 100644
--- a/tests/js/tclosures.nim
+++ b/tests/js/tclosures.nim
@@ -2,7 +2,7 @@ discard """
   action: run
 """
 
-import math, strutils
+import math, random, strutils
 const consolePrefix = "jsCallbacks"
 
 asm """
diff --git a/tests/js/test2.nim b/tests/js/test2.nim
index f6976d058..0f460d6f8 100644
--- a/tests/js/test2.nim
+++ b/tests/js/test2.nim
@@ -1,7 +1,8 @@
 discard """
   output: '''foo
 js 3.14
-7'''
+7
+1'''
 """
 
 # This file tests the JavaScript generator
@@ -29,3 +30,13 @@ proc test(x: C, T: typedesc): T =
   cast[T](x)
 
 echo 7.test(int8)
+
+# #4222
+const someConst = [ "1"]
+
+proc procThatRefersToConst() # Forward decl
+procThatRefersToConst() # Call bar before it is defined
+
+proc procThatRefersToConst() =
+  var i = 0 # Use a var index, otherwise nim will constfold foo[0]
+  echo someConst[i] # JS exception here: foo is still not initialized (undefined)
diff --git a/tests/js/testtojsstr.nim b/tests/js/testtojsstr.nim
new file mode 100644
index 000000000..03ac89e20
--- /dev/null
+++ b/tests/js/testtojsstr.nim
@@ -0,0 +1,8 @@
+discard """
+  output = "И\n"
+"""
+
+let s: string = "И\n"
+let cs = s.cstring
+
+echo $s
diff --git a/tests/lexer/tstrlits.nim b/tests/lexer/tstrlits.nim
index f5b7ce937..cc8872f60 100644
--- a/tests/lexer/tstrlits.nim
+++ b/tests/lexer/tstrlits.nim
@@ -1,6 +1,6 @@
 discard """
   file: "tstrlits.nim"
-  output: "a\"\"long string\"\"\"\"\"abc\"def"
+  output: "a\"\"long string\"\"\"\"\"abc\"def_'2'●"
 """
 # Test the new different string literals
 
@@ -11,9 +11,13 @@ const
 
   raw = r"abc""def"
 
+  escaped = "\x5f'\50'\u25cf"
+
+
 stdout.write(rawQuote)
 stdout.write(tripleEmpty)
 stdout.write(raw)
+stdout.write(escaped)
 #OUT a""long string"""""abc"def
 
 
diff --git a/tests/macros/tgettypeinst.nim b/tests/macros/tgettypeinst.nim
new file mode 100644
index 000000000..22e03a119
--- /dev/null
+++ b/tests/macros/tgettypeinst.nim
@@ -0,0 +1,122 @@
+discard """
+"""
+
+import macros, strUtils
+
+proc symToIdent(x: NimNode): NimNode =
+  case x.kind:
+    of nnkCharLit..nnkUInt64Lit:
+      result = newNimNode(x.kind)
+      result.intVal = x.intVal
+    of nnkFloatLit..nnkFloat64Lit:
+      result = newNimNode(x.kind)
+      result.floatVal = x.floatVal
+    of nnkStrLit..nnkTripleStrLit:
+      result = newNimNode(x.kind)
+      result.strVal = x.strVal
+    of nnkIdent, nnkSym:
+      result = newIdentNode($x)
+    else:
+      result = newNimNode(x.kind)
+      for c in x:
+        result.add symToIdent(c)
+
+macro testX(x,inst0: typed; recurse: static[bool]; implX: stmt): typed =
+  let inst = x.getTypeInst
+  let impl = x.getTypeImpl
+  let inst0r = inst0.symToIdent.treeRepr
+  let instr = inst.symToIdent.treeRepr
+  #echo inst0r
+  #echo instr
+  doAssert(instr == inst0r)
+  var impl0 =
+    if implX.kind == nnkNilLit: inst0
+    else: implX[0][2]
+  let impl0r = impl0.symToIdent.treerepr
+  let implr = impl.symToIdent.treerepr
+  #echo impl0r
+  #echo implr
+  doAssert(implr == impl0r)
+  template echoString(s:string) = echo s.replace("\n","\n  ")
+  result = newStmtList()
+  #result.add getAst(echoString("  " & inst0.repr))
+  #result.add getAst(echoString("  " & inst.repr))
+  #result.add getAst(echoString("  " & impl0.repr))
+  #result.add getAst(echoString("  " & impl.repr))
+  if recurse:
+    template testDecl(n, m :typed) =
+      testV(n, false):
+        type _ = m
+    result.add getAst(testDecl(inst.symToIdent, impl.symToIdent))
+
+template testV(inst, recurse, impl) =
+  block:
+    #echo "testV(" & astToStr(inst) & ", " & $recurse & "):" & astToStr(impl)
+    var x: inst
+    testX(x, inst, recurse, impl)
+template testT(inst, recurse) =
+  block:
+    type myType = inst
+    testV(myType, recurse):
+      type _ = inst
+
+template test(inst) =
+  testT(inst, false)
+  testV(inst, true, nil)
+template test(inst, impl) = testV(inst, true, impl)
+
+type
+  Model = object of RootObj
+  User = object of Model
+    name : string
+    password : string
+
+  Tree = object of RootObj
+    value : int
+    left,right : ref Tree
+
+  MyEnum = enum
+    valueA, valueB, valueC
+
+  MySet = set[MyEnum]
+  MySeq = seq[int]
+  MyIntPtr = ptr int
+  MyIntRef = ref int
+
+  GenericObject[T] = object
+    value:T
+  Foo[N:static[int],T] = object
+  Bar[N:static[int],T] = object
+    #baz:Foo[N+1,GenericObject[T]]
+    baz:Foo[N,GenericObject[T]]
+
+test(bool)
+test(char)
+test(int)
+test(float)
+test(ptr int)
+test(ref int)
+test(array[1..10,Bar[2,Foo[3,float]]])
+test(distinct Bar[2,Foo[3,float]])
+test(tuple[a:int,b:Foo[-1,float]])
+#test(MyEnum):
+#  type _ = enum
+#    valueA, valueB, valueC
+test(set[MyEnum])
+test(seq[int])
+test(Bar[2,Foo[3,float]]):
+  type _ = object
+    baz: Foo[2, GenericObject[Foo[3, float]]]
+test(Model):
+  type _ = object of RootObj
+test(User):
+  type _ = object of Model
+    name: string
+    password: string
+test(Tree):
+  type _ = object of RootObj
+    value: int
+    left: ref Tree
+    right: ref Tree
+test(proc (a: int, b: Foo[2,float]))
+test(proc (a: int, b: Foo[2,float]): Bar[3,int])
diff --git a/tests/manyloc/keineschweine/lib/vehicles.nim b/tests/manyloc/keineschweine/lib/vehicles.nim
index 94ebf9f57..ddfb43b38 100644
--- a/tests/manyloc/keineschweine/lib/vehicles.nim
+++ b/tests/manyloc/keineschweine/lib/vehicles.nim
@@ -1,6 +1,6 @@
 import
   sfml, chipmunk,
-  sg_assets, sfml_stuff, keineschweine
+  sg_assets, sfml_stuff, "../keineschweine"
 
 
 proc accel*(obj: PVehicle, dt: float) =
diff --git a/tests/manyloc/nake/nakefile.nim b/tests/manyloc/nake/nakefile.nim
index e91b86986..2055d7834 100644
--- a/tests/manyloc/nake/nakefile.nim
+++ b/tests/manyloc/nake/nakefile.nim
@@ -1,5 +1,5 @@
 import nake
-import httpclient, zip/zipfiles, times, math, sequtils
+import httpclient, zip/zipfiles, times, random, sequtils
 nakeImports
 
 randomize()
@@ -145,7 +145,7 @@ task "download", "download game assets":
     echo "Extracted the libs dir. Copy the ones you need to this dir."
 
 task "zip-lib", "zip up the libs dir":
-  var z: TZipArchive
+  var z: ZipArchive
   if not z.open("libs-" & getDateStr() & ".zip", fmReadWrite):
     quit "Could not open zip"
   for file in walkDirRec("libs", {pcFile, pcDir}):
diff --git a/tests/manyloc/named_argument_bug/main.nim.cfg b/tests/manyloc/named_argument_bug/main.nim.cfg
index 27cf8e688..7df7a0e97 100644
--- a/tests/manyloc/named_argument_bug/main.nim.cfg
+++ b/tests/manyloc/named_argument_bug/main.nim.cfg
@@ -1,2 +1,3 @@
 # this file only exists to mark 'main.nim' as the main file
 
+--path:"$projectpath"
diff --git a/tests/manyloc/named_argument_bug/tri_engine/math/circle.nim b/tests/manyloc/named_argument_bug/tri_engine/math/circle.nim
index 7e7517998..b95cfa379 100644
--- a/tests/manyloc/named_argument_bug/tri_engine/math/circle.nim
+++ b/tests/manyloc/named_argument_bug/tri_engine/math/circle.nim
@@ -1,6 +1,6 @@
 import
-  tri_engine/config,
-  tri_engine/math/vec
+  ../config,
+  vec
 
 type
   TCircle* = tuple[p: TV2[TR], r: TR]
diff --git a/tests/manyloc/named_argument_bug/tri_engine/math/vec.nim b/tests/manyloc/named_argument_bug/tri_engine/math/vec.nim
index 3b57acb8e..926958fe4 100644
--- a/tests/manyloc/named_argument_bug/tri_engine/math/vec.nim
+++ b/tests/manyloc/named_argument_bug/tri_engine/math/vec.nim
@@ -1,6 +1,6 @@
 import
   macros,
-  tri_engine/config
+  "../config"
 
 type
   TV2*[T:SomeNumber=TR] = array[0..1, T]
diff --git a/tests/overload/tstmtoverload.nim b/tests/overload/tstmtoverload.nim
index f1944b637..75584bcab 100644
--- a/tests/overload/tstmtoverload.nim
+++ b/tests/overload/tstmtoverload.nim
@@ -10,7 +10,7 @@ template test(loopCount: int, extraI: int, testBody: stmt): stmt =
 
 template test(loopCount: int, extraF: float, testBody: stmt): stmt =
   block:
-    test(loopCount, round(extraF), testBody)
+    test(loopCount, round(extraF).int, testBody)
 
 template test(loopCount: int, testBody: stmt): stmt =
   block:
diff --git a/tests/parallel/twrong_refcounts.nim b/tests/parallel/twrong_refcounts.nim
index db32a96d8..57e0588a0 100644
--- a/tests/parallel/twrong_refcounts.nim
+++ b/tests/parallel/twrong_refcounts.nim
@@ -2,7 +2,7 @@ discard """
   output: "Success"
 """
 
-import math, threadPool
+import math, random, threadPool
 
 # ---
 
diff --git a/tests/parser/tdo.nim b/tests/parser/tdo.nim
new file mode 100644
index 000000000..7bd1f7411
--- /dev/null
+++ b/tests/parser/tdo.nim
@@ -0,0 +1,79 @@
+discard """
+  output: '''true
+true
+true
+true inner B'''
+"""
+
+template withValue(a, b, c, d, e: untyped) =
+  if c:
+    d
+  else:
+    e
+
+template withValue(a, b, c, d: untyped) =
+  if c:
+    d
+
+const
+  EVENT_READ = 1
+  EVENT_WRITE = 2
+  FLAG_HANDLE = 3
+  EVENT_MASK = 3
+
+var s: string
+
+proc main =
+  var value = false
+  var fd = 8888
+  var event = 0
+  s.withValue(fd, value) do:
+    if value:
+      var oe = (EVENT_MASK)
+      if (oe xor event) != 0:
+        if (oe and EVENT_READ) != 0 and (event and EVENT_READ) == 0:
+          discard
+        if (oe and EVENT_WRITE) != 0 and (event and EVENT_WRITE) == 0:
+          discard
+        if (oe and EVENT_READ) == 0 and (event and EVENT_READ) != 0:
+          discard
+        if (oe and EVENT_WRITE) == 0 and (event and EVENT_WRITE) != 0:
+          discard
+    else:
+      raise newException(ValueError, "error")
+  do:
+    raise newException(ValueError, "Descriptor is not registered in queue")
+
+proc main2 =
+  var unused = 8
+  # test 'then' branch:
+  s.withValue(unused, true) do:
+    echo "true"
+  do:
+    echo "false"
+
+  # test overloading:
+  s.withValue(unused, false) do:
+    echo "cannot come here"
+
+  # test 'else' branch:
+  s.withValue(unused, false) do:
+    echo "false"
+  do:
+    echo "true"
+
+  # test proper nesting:
+  s.withValue(unused, false) do:
+    echo "false"
+    s.withValue(unused, false) do:
+      echo "false inner A"
+    do:
+      echo "true inner A"
+  do:
+    echo "true"
+    s.withValue(unused, false) do:
+      echo "false inner B"
+    do:
+      echo "true inner B"
+
+main2()
diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim
index 1ac9c8092..538582ba8 100644
--- a/tests/stdlib/tmath.nim
+++ b/tests/stdlib/tmath.nim
@@ -1,4 +1,4 @@
-import math
+import math, random
 import unittest
 import sets
 
diff --git a/tests/stdlib/tparscfg.nim b/tests/stdlib/tparscfg.nim
index 4c11ccf61..7022d071b 100644
--- a/tests/stdlib/tparscfg.nim
+++ b/tests/stdlib/tparscfg.nim
@@ -1,25 +1,37 @@
+import parsecfg
 
-import
-  os, parsecfg, strutils, streams
+## Creating a configuration file.
+var dict1=newConfig()
+dict1.setSectionKey("","charset","utf-8")
+dict1.setSectionKey("Package","name","hello")
+dict1.setSectionKey("Package","--threads","on")
+dict1.setSectionKey("Author","name","lihf8515")
+dict1.setSectionKey("Author","qq","10214028")
+dict1.setSectionKey("Author","email","lihaifeng@wxm.com")
+dict1.writeConfig("config.ini")
+
+## Reading a configuration file.
+var dict2 = loadConfig("config.ini")
+var charset = dict2.getSectionValue("","charset")
+var threads = dict2.getSectionValue("Package","--threads")
+var pname = dict2.getSectionValue("Package","name")
+var name = dict2.getSectionValue("Author","name")
+var qq = dict2.getSectionValue("Author","qq")
+var email = dict2.getSectionValue("Author","email")
+echo charset
+echo threads
+echo pname
+echo name
+echo qq
+echo email
+
+## Modifying a configuration file.
+var dict3 = loadConfig("config.ini")
+dict3.setSectionKey("Author","name","lhf")
+dict3.writeConfig("config.ini")
+
+## Deleting a section key in a configuration file.
+var dict4 = loadConfig("config.ini")
+dict4.delSectionKey("Author","email")
+dict4.writeConfig("config.ini")
 
-var f = newFileStream(paramStr(1), fmRead)
-if f != nil:
-  var p: TCfgParser
-  open(p, f, paramStr(1))
-  while true:
-    var e = next(p)
-    case e.kind
-    of cfgEof:
-      echo("EOF!")
-      break
-    of cfgSectionStart:   ## a ``[section]`` has been parsed
-      echo("new section: " & e.section)
-    of cfgKeyValuePair:
-      echo("key-value-pair: " & e.key & ": " & e.value)
-    of cfgOption:
-      echo("command: " & e.key & ": " & e.value)
-    of cfgError:
-      echo(e.msg)
-  close(p)
-else:
-  echo("cannot open: " & paramStr(1))
diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim
index 4b210c23b..73113ac68 100644
--- a/tests/stdlib/tunittest.nim
+++ b/tests/stdlib/tunittest.nim
@@ -26,7 +26,7 @@ test "unittest multiple requires":
   require(true)
 
 
-import math
+import math, random
 from strutils import parseInt
 proc defectiveRobot() =
   randomize()
diff --git a/tests/template/t2do.nim b/tests/template/t2do.nim
index b87e3328c..ec364c5f3 100644
--- a/tests/template/t2do.nim
+++ b/tests/template/t2do.nim
@@ -15,8 +15,9 @@ template toFloatHelper(result: expr; tooSmall, tooLarge: stmt) {.immediate.} =
     tooLarge
 
 proc toFloat*(a: int): float =
-  toFloatHelper(result)
-    do: raise newException(ValueError, "number too small"):
-        raise newException(ValueError, "number too large")
+  toFloatHelper(result) do:
+    raise newException(ValueError, "number too small")
+  do:
+    raise newException(ValueError, "number too large")
 
 echo toFloat(8)
diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim
index 150c55edc..125643d5a 100644
--- a/tests/testament/categories.nim
+++ b/tests/testament/categories.nim
@@ -152,6 +152,9 @@ proc gcTests(r: var TResults, cat: Category, options: string) =
   testWithoutBoehm "closureleak"
   testWithoutMs "refarrayleak"
 
+  testWithoutBoehm "tlists"
+  testWithoutBoehm "thavlak"
+
   test "stackrefleak"
   test "cyclecollector"
 
@@ -223,7 +226,7 @@ proc jsTests(r: var TResults, cat: Category, options: string) =
                    "varres/tvartup", "misc/tints", "misc/tunsignedinc"]:
     test "tests/" & testfile & ".nim"
 
-  for testfile in ["pure/strutils", "pure/json"]:
+  for testfile in ["pure/strutils", "pure/json", "pure/random", "pure/times"]:
     test "lib/" & testfile & ".nim"
 
 # ------------------------- manyloc -------------------------------------------
diff --git a/tests/threads/ttryrecv.nim b/tests/threads/ttryrecv.nim
index be79fadae..4a98e6c27 100644
--- a/tests/threads/ttryrecv.nim
+++ b/tests/threads/ttryrecv.nim
@@ -4,7 +4,7 @@ discard """
 
 # bug #1816
 
-from math import random
+from random import random
 from os import sleep
 
 type PComm = ptr Channel[int]
diff --git a/tests/trmacros/tstatic_t_bug.nim b/tests/trmacros/tstatic_t_bug.nim
new file mode 100644
index 000000000..cdfa53514
--- /dev/null
+++ b/tests/trmacros/tstatic_t_bug.nim
@@ -0,0 +1,24 @@
+discard """
+  output: "optimized"
+"""
+# bug #4227
+type Vector64[N: static[int]] = array[N, int]
+
+proc `*`*[N: static[int]](a: Vector64[N]; b: float64): Vector64[N] =
+  result = a
+
+proc `+=`*[N: static[int]](a: var Vector64[N]; b: Vector64[N]) =
+  echo "regular"
+
+proc linearCombinationMut[N: static[int]](a: float64, v: var Vector64[N], w: Vector64[N])  {. inline .} =
+  echo "optimized"
+
+template rewriteLinearCombinationMut*{v += `*`(w, a)}(a: float64, v: var Vector64, w: Vector64): auto =
+  linearCombinationMut(a, v, w)
+
+proc main() =
+  const scaleVal = 9.0
+  var a, b: Vector64[7]
+  a += b * scaleval
+
+main()
diff --git a/tests/usingstmt/tthis.nim b/tests/usingstmt/tthis.nim
new file mode 100644
index 000000000..83d75d08c
--- /dev/null
+++ b/tests/usingstmt/tthis.nim
@@ -0,0 +1,15 @@
+
+# bug #4177
+
+type
+  Parent = object of RootObj
+    parentField: int
+  Child = object of Parent
+    childField: int
+
+{.this: self.}
+proc sumFields(self: Child): int =
+  result = parentField + childField # Error: undeclared identifier: 'parentField'
+
+proc sumFieldsWorks(self: Child): int =
+  result = self.parentField + childField
diff --git a/tests/vm/meta.nim b/tests/vm/meta.nim
new file mode 100644
index 000000000..2aa01b5b3
--- /dev/null
+++ b/tests/vm/meta.nim
@@ -0,0 +1,240 @@
+#
+# meta.nim
+#
+
+import tables
+import macros
+
+type
+  NodeSeq* = seq[NimNode]
+  Ident* = tuple[name: string, exported: bool]
+  Bracket* = seq[Ident]
+  Field* = tuple[identifier: Ident, type_name: string, default: string]
+  FieldSeq* = seq[Field]
+  TypeDef* = object
+    identifier*: Ident
+    fields*: FieldSeq
+    is_ref*: bool
+    object_type*: string
+    base_type*: string
+  TypeDefSeq* = seq[TypeDef]
+  Proc* = tuple[identifier: Ident, params: FieldSeq,
+                returns: Ident, generics: FieldSeq, body: NimNode]
+  ProcSeq* = seq[Proc]
+
+# Ident procs
+proc newIdent*(name: string, exported = false): Ident =
+  result.name = name
+  result.exported = exported
+
+proc newIdent*(node: NimNode): Ident =
+  case node.kind:
+    of nnkPostfix:
+      result = newIdent(node[1])
+      result.exported = true
+    of nnkIdent, nnkSym:
+      result.name = $(node)
+    else:
+      let msg = "newIdent cannot initialize from node kind: " & $(node.kind)
+      raise newException(ValueError, msg)
+
+proc render*(i: Ident): NimNode {.compileTime.} =
+  if i.name == nil:
+    return newNimNode(nnkEmpty)
+
+  if i.exported:
+    result = newNimNode(nnkPostfix)
+    result.add(ident "*")
+    result.add(ident i.name)
+  else:
+    result = ident i.name
+
+proc `$`*(identifier: Ident): string = identifier.name
+
+converter toString*(x: Ident): string = x.name
+
+proc newBracket*(node: NimNode): Bracket =
+  result = @[]
+  case node.kind:
+    of nnkBracket:
+      for child in node:
+        if child.kind != nnkIdent:
+          let msg = "Bracket members can only be nnkIdent not kind: " & $(node.kind)
+          raise newException(ValueError, msg)
+        result.add(newIdent(child))
+    else:
+      let msg = "newBracket must initialize from node kind nnkBracket not: " & $(node.kind)
+      raise newException(ValueError, msg)
+
+# Field procs
+proc newField*(identifier: Ident, type_name: string, default: string = nil): Field =
+  result.identifier = identifier
+  result.type_name = type_name
+  result.default = default
+
+proc newField*(node: NimNode): Field =
+  case node.kind:
+    of nnkIdentDefs:
+      if node.len > 3:
+        let msg = "newField cannot initialize from nnkIdentDefs with multiple names"
+        raise newException(ValueError, msg)
+      result.identifier = newIdent(node[0])
+      result.type_name = $(node[1])
+      case node[2].kind:
+        of nnkIdent:
+          result.default = $(node[2])
+        else:
+          result.default = nil
+    else:
+      let msg = "newField cannot initialize from node kind: " & $(node.kind)
+      raise newException(ValueError, msg)
+
+# FieldSeq procs
+proc newFieldSeq*(node: NimNode): FieldSeq =
+  result = @[]
+  case node.kind:
+    of nnkIdentDefs:
+      let
+        type_name = $(node[node.len - 2])
+        default_node = node[node.len - 1]
+      var default: string
+      case default_node.kind:
+        of nnkIdent:
+          default = $(default_node)
+        else:
+          default = nil
+      for i in 0..node.len - 3:
+        let name = newIdent(node[i])
+        result.add(newField(name, type_name, default))
+    of nnkRecList, nnkVarSection, nnkGenericParams:
+      for child in node:
+        result = result & newFieldSeq(child)
+    else:
+      let msg = "newFieldSeq cannot initialize from node kind: " & $(node.kind)
+      raise newException(ValueError, msg)
+
+proc render*(f: Field): NimNode {.compileTime.} =
+  let identifier = f.identifier.render()
+  let type_name = if f.type_name != nil: ident(f.type_name) else: newEmptyNode()
+  let default = if f.default != nil: ident(f.default) else: newEmptyNode()
+  newIdentDefs(identifier, type_name, default)
+
+proc render*(fs: FieldSeq): NimNode {.compileTime.} =
+  result = newNimNode(nnkRecList)
+  for field in fs:
+    result.add(field.render())
+
+# TypeDef procs
+proc newTypeDef*(identifier: Ident, is_ref = false,
+                object_type = "object",
+                base_type: string = nil): TypeDef {.compileTime.} =
+  result.identifier = identifier
+  result.fields = @[]
+  result.is_ref = is_ref
+  result.object_type = "object"
+  result.base_type = base_type
+
+proc newTypeDef*(node: NimNode): TypeDef {.compileTime.} =
+  case node.kind:
+    of nnkTypeDef:
+      result.identifier = newIdent($(node[0]))
+      var object_node: NimNode
+      case node[2].kind:
+        of nnkRefTy:
+          object_node = node[2][0]
+          result.is_ref = true
+        of nnkObjectTy:
+          object_node = node[2]
+          result.is_ref = false
+        else:
+          let msg = "newTypeDef could not parse RefTy/ObjectTy, found: " & $(node[2].kind)
+          raise newException(ValueError, msg)
+      case object_node[1].kind:
+        of nnkOfInherit:
+          result.base_type = $(object_node[1][0])
+        else:
+          result.base_type = "object"
+      result.fields = newFieldSeq(object_node[2])
+    else:
+      let msg = "newTypeDef cannot initialize from node kind: " & $(node.kind)
+      raise newException(ValueError, msg)
+
+proc render*(typedef: TypeDef): NimNode {.compileTime.} =
+  result = newNimNode(nnkTypeDef)
+  result.add(typedef.identifier.render)
+  result.add(newEmptyNode())
+  let object_node = newNimNode(nnkObjectTy)
+  object_node.add(newEmptyNode())
+  if typedef.base_type == nil:
+    object_node.add(newEmptyNode())
+  else:
+    var base_type = newNimNode(nnkOfInherit)
+    base_type.add(ident(typedef.base_type))
+    object_node.add(base_type)
+  let fields = typedef.fields.render()
+  object_node.add(fields)
+  if typedef.is_ref:
+    let ref_node = newNimNode(nnkRefTy)
+    ref_node.add(object_node)
+    result.add(ref_node)
+  else:
+    result.add(object_node)
+
+proc newTypeDefSeq*(node: NimNode): TypeDefSeq =
+  result = @[]
+  case node.kind:
+    of nnkTypeSection:
+      for child in node:
+        result.add(newTypeDef(child))
+    else:
+      let msg = "newTypeSection could not parse TypeDef, found: " & $(node.kind)
+      raise newException(ValueError, msg)
+
+proc render*(typeseq: TypeDefSeq): NimNode {.compileTime.} =
+  result = newNimNode(nnkTypeSection)
+  for typedef in typeseq:
+    result.add(typedef.render())
+
+proc newProc*(identifier: Ident, params: FieldSeq = nil,
+              returns: Ident, generics: FieldSeq = nil): Proc =
+  result.identifier = identifier
+  result.params = params
+  result.returns = returns
+  result.generics = generics
+
+proc newProc*(node: NimNode): Proc =
+  case node.kind:
+    of nnkProcDef, nnkMethodDef:
+      result.identifier = newIdent(node[0])
+      case node[2].kind:
+        of nnkGenericParams:
+          result.generics = newFieldSeq(node[2])
+        else: result.generics = nil
+      let formal_params = node[3]
+      case formal_params[0].kind:
+        of nnkIdent:
+          result.returns = newIdent(formal_params[0])
+        else: discard
+      result.params = @[]
+      for i in 1..formal_params.len - 1:
+        let param = formal_params[i]
+        for field in newFieldSeq(param):
+          result.params.add(field)
+      result.body = node[6]
+    else:
+      let msg = "newProc cannot initialize from node kind: " & $(node.kind)
+      raise newException(ValueError, msg)
+
+proc render*(procdef: Proc): NimNode {.compileTime.} =
+  result = newNimNode(nnkProcDef)
+  result.add(procdef.identifier.render())
+  result.add(newEmptyNode())
+  result.add(newEmptyNode())
+  let formal_params = newNimNode(nnkFormalParams)
+  formal_params.add(procdef.returns.render())
+  for param in procdef.params:
+    formal_params.add(param.render())
+  result.add(formal_params)
+  result.add(newEmptyNode())
+  result.add(newEmptyNode())
+  result.add(procdef.body)
diff --git a/tests/vm/tcomponent.nim b/tests/vm/tcomponent.nim
new file mode 100644
index 000000000..efeba2a6d
--- /dev/null
+++ b/tests/vm/tcomponent.nim
@@ -0,0 +1,132 @@
+discard """
+  output: '''`:)` @ 0,0
+FOO: blah'''
+"""
+
+#
+# magic.nim
+#
+
+# bug #3729
+
+import macros, sequtils, tables
+import strutils
+import future, meta
+
+type
+  Component = object
+    fields: FieldSeq
+    field_index: seq[string]
+    procs: ProcSeq
+    procs_index: seq[string]
+
+  Registry = object
+    field_index: seq[string]
+    procs_index: seq[string]
+    components: Table[string, Component]
+    builtin: Component
+
+proc newRegistry(): Registry =
+  result.field_index = @[]
+  result.procs_index = @[]
+  result.components = initTable[string, Component]()
+
+var registry {.compileTime.} = newRegistry()
+
+proc validateComponent(r: var Registry, name: string, c: Component) =
+  if r.components.hasKey(name):
+    let msg = "`component` macro cannot consume duplicated identifier: " & name
+    raise newException(ValueError, msg)
+
+  for field_name in c.field_index:
+    if r.field_index.contains(field_name):
+      let msg = "`component` macro cannot delcare duplicated field: " & field_name
+      raise newException(ValueError, msg)
+    r.field_index.add(field_name)
+
+  for proc_name in c.procs_index:
+    if r.procs_index.contains(proc_name):
+      let msg = "`component` macro cannot delcare duplicated proc: " & proc_name
+      raise newException(ValueError, msg)
+    r.procs_index.add(proc_name)
+
+proc addComponent(r: var Registry, name: string, c: Component) =
+  r.validateComponent(name, c)
+  r.components.add(name, c)
+
+proc parse_component(body: NimNode): Component =
+  result.field_index = @[]
+  result.procs_index = @[]
+  for node in body:
+    case node.kind:
+      of nnkVarSection:
+        result.fields = newFieldSeq(node)
+        for field in result.fields:
+          result.field_index.add(field.identifier.name)
+      of nnkMethodDef, nnkProcDef:
+        let new_proc = meta.newProc(node)
+        result.procs = result.procs & @[new_proc]
+        for procdef in result.procs:
+          result.procs_index.add(procdef.identifier.name)
+      else: discard
+
+macro component*(name: expr, body: stmt): stmt {.immediate.} =
+  let component = parse_component(body)
+  registry.addComponent($name, component)
+  parseStmt("discard")
+
+macro component_builtins(body: stmt): stmt {.immediate.} =
+  let builtin = parse_component(body)
+  registry.field_index = builtin.field_index
+  registry.procs_index = builtin.procs_index
+  registry.builtin = builtin
+
+proc bind_methods*(component: var Component, identifier: Ident): seq[NimNode] =
+  result = @[]
+  for procdef in component.procs.mitems:
+    let this_field = newField(newIdent("this"), identifier)
+    procdef.params.insert(this_field, 0)
+    result.add(procdef.render())
+
+macro bind_components*(type_name, component_names: expr): stmt {.immediate.} =
+  result = newStmtList()
+  let identifier = newIdent(type_name)
+  let components = newBracket(component_names)
+  var entity_type = newTypeDef(identifier, true, "object", "RootObj")
+  entity_type.fields = registry.builtin.fields
+  for component_name, component in registry.components:
+    if components.contains(newIdent(component_name)):
+      entity_type.fields = entity_type.fields & component.fields
+  # TODO why doesn't the following snippet work instead of the one above?
+  # for name in components:
+  #   echo "Registering $1 to $2" % [name.name, identifier.name]
+  #   let component = registry.components[name.name]
+  #   entity_type.fields = entity_type.fields & component.fields
+  let type_section: TypeDefSeq = @[entity_type]
+  result.add type_section.render
+  var builtin = registry.builtin
+  let builtin_methods = bind_methods(builtin, identifier)
+  for builtin_proc in builtin_methods:
+    result.add(builtin_proc)
+  echo "SIGSEV here"
+  for component in registry.components.mvalues():
+    for method_proc in bind_methods(component, identifier):
+      result.add(method_proc)
+
+component_builtins:
+  proc foo(msg: string) =
+    echo "FOO: $1" % msg
+
+component position:
+  var x*, y*: int
+
+component name:
+  var name*: string
+  proc render*(x, y: int) = echo "`$1` @ $2,$3" % [this.name, $x, $y]
+
+bind_components(Entity, [position, name])
+
+var e = new(Entity)
+e.name = ":)"
+e.render(e.x, e.y)
+e.foo("blah")