summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorArne Döring <arne.doering@gmx.net>2019-10-28 10:06:16 +0100
committerAndreas Rumpf <rumpf_a@web.de>2019-10-28 10:06:16 +0100
commit5ed99f8d3fc67d6f02172ab9cbcc67c17eeee5c9 (patch)
treeb08965de5c7eff8da9df97e8a2e009cff3fae5de
parenta2ad7d4883a450d66d1657c3550dc91f66b8b187 (diff)
downloadNim-5ed99f8d3fc67d6f02172ab9cbcc67c17eeee5c9.tar.gz
Extent json.to testing to VM, add workrounds for VM bugs. (#12493)
fixes #12479
-rw-r--r--lib/pure/json.nim177
-rw-r--r--lib/system.nim10
-rw-r--r--tests/stdlib/tjsonmacro.nim238
3 files changed, 257 insertions, 168 deletions
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index e833f6123..72002fbab 100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -966,30 +966,38 @@ template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind],
     ]
     raise newException(JsonKindError, msg)
 
-
 when defined(nimFixedForwardGeneric):
+
+  macro isRefSkipDistinct(arg: typed): untyped =
+    var impl = getTypeImpl(arg)
+    if impl.kind == nnkBracketExpr and impl[0].eqIdent("typeDesc"):
+      impl = getTypeImpl(impl[1])
+    while impl.kind == nnkDistinctTy:
+      impl = getTypeImpl(impl[0])
+    result = newLit(impl.kind == nnkRefTy)
+
   # The following forward declarations don't work in older versions of Nim
 
   # forward declare all initFromJson
 
-  proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: string)
-  proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[S,T](dst: var array[S,T]; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T](dst: var Table[string,T];jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T](dst: var OrderedTable[string,T];jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T: distinct](dst: var T;jsonNode: JsonNode; jsonPath: string)
-  proc initFromJson[T: object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: string)
+  proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: var string)
+  proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[S,T](dst: var array[S,T]; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T](dst: var Table[string,T];jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T](dst: var OrderedTable[string,T];jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T: distinct](dst: var T;jsonNode: JsonNode; jsonPath: var string)
+  proc initFromJson[T: object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: var string)
 
   # initFromJson definitions
 
-  proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: var string) =
     verifyJsonKind(jsonNode, {JString, JNull}, jsonPath)
     # since strings don't have a nil state anymore, this mapping of
     # JNull to the default string is questionable. `none(string)` and
@@ -999,91 +1007,115 @@ when defined(nimFixedForwardGeneric):
     else:
       dst = jsonNode.str
 
-  proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: var string) =
     verifyJsonKind(jsonNode, {JBool}, jsonPath)
     dst = jsonNode.bval
 
-  proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: var string) =
     dst = jsonNode.copy
 
-  proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: string) =
+  proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: var string) =
     verifyJsonKind(jsonNode, {JInt}, jsonPath)
     dst = T(jsonNode.num)
 
-  proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
     verifyJsonKind(jsonNode, {JInt, JFloat}, jsonPath)
     if jsonNode.kind == JFloat:
       dst = T(jsonNode.fnum)
     else:
       dst = T(jsonNode.num)
 
-  proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
     verifyJsonKind(jsonNode, {JString}, jsonPath)
     dst = parseEnum[T](jsonNode.getStr)
 
-  proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: var string) =
     verifyJsonKind(jsonNode, {JArray}, jsonPath)
     dst.setLen jsonNode.len
+    let orignalJsonPathLen = jsonPath.len
     for i in 0 ..< jsonNode.len:
-      initFromJson(dst[i], jsonNode[i], jsonPath & "[" & $i & "]")
+      jsonPath.add '['
+      jsonPath.addInt i
+      jsonPath.add ']'
+      initFromJson(dst[i], jsonNode[i], jsonPath)
+      jsonPath.setLen orignalJsonPathLen
 
-  proc initFromJson[S,T](dst: var array[S,T]; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[S,T](dst: var array[S,T]; jsonNode: JsonNode; jsonPath: var string) =
     verifyJsonKind(jsonNode, {JArray}, jsonPath)
+    let originalJsonPathLen = jsonPath.len
     for i in 0 ..< jsonNode.len:
-      initFromJson(dst[i], jsonNode[i], jsonPath & "[" & $i & "]")
+      jsonPath.add '['
+      jsonPath.addInt i
+      jsonPath.add ']'
+      initFromJson(dst[i], jsonNode[i], jsonPath)
+      jsonPath.setLen originalJsonPathLen
 
-  proc initFromJson[T](dst: var Table[string,T];jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T](dst: var Table[string,T];jsonNode: JsonNode; jsonPath: var string) =
     dst = initTable[string, T]()
     verifyJsonKind(jsonNode, {JObject}, jsonPath)
+    let originalJsonPathLen = jsonPath.len
     for key in keys(jsonNode.fields):
-      initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath & "." & key)
+      jsonPath.add '.'
+      jsonPath.add key
+      initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath)
+      jsonPath.setLen originalJsonPathLen
 
-  proc initFromJson[T](dst: var OrderedTable[string,T];jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T](dst: var OrderedTable[string,T];jsonNode: JsonNode; jsonPath: var string) =
     dst = initOrderedTable[string,T]()
     verifyJsonKind(jsonNode, {JObject}, jsonPath)
+    let originalJsonPathLen = jsonPath.len
     for key in keys(jsonNode.fields):
-      initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath & "." & key)
+      jsonPath.add '.'
+      jsonPath.add key
+      initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath)
+      jsonPath.setLen originalJsonPathLen
 
-  proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: var string) =
     if jsonNode.kind == JNull:
       dst = nil
     else:
-      dst = new(ref T)
+      dst = new(T)
       initFromJson(dst[], jsonNode, jsonPath)
 
-  proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: var string) =
     if jsonNode != nil and jsonNode.kind != JNull:
       dst = some(default(T))
       initFromJson(dst.get, jsonNode, jsonPath)
 
-  macro assignDistinctImpl[T : distinct](dst: var T;jsonNode: JsonNode; jsonPath: string) =
+  macro assignDistinctImpl[T : distinct](dst: var T;jsonNode: JsonNode; jsonPath: var string) =
     let typInst = getTypeInst(dst)
     let typImpl = getTypeImpl(dst)
     let baseTyp = typImpl[0]
+
     result = quote do:
-      initFromJson( `baseTyp`(`dst`), `jsonNode`, `jsonPath`)
+      when nimvm:
+        # workaround #12282
+        var tmp: `baseTyp`
+        initFromJson( tmp, `jsonNode`, `jsonPath`)
+        `dst` = `typInst`(tmp)
+      else:
+        initFromJson( `baseTyp`(`dst`), `jsonNode`, `jsonPath`)
 
-  proc initFromJson[T : distinct](dst: var T; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T : distinct](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
     assignDistinctImpl(dst, jsonNode, jsonPath)
 
   proc detectIncompatibleType(typeExpr, lineinfoNode: NimNode): void =
     if typeExpr.kind == nnkTupleConstr:
       error("Use a named tuple instead of: " & typeExpr.repr, lineinfoNode)
 
-  proc foldObjectBody(dst, typeNode, tmpSym, jsonNode, jsonPath: NimNode, depth: int): void {.compileTime.} =
-    if depth > 150:
-      error("recursion limit reached", typeNode)
+  proc foldObjectBody(dst, typeNode, tmpSym, jsonNode, jsonPath, originalJsonPathLen: NimNode): void {.compileTime.} =
     case typeNode.kind
     of nnkEmpty:
       discard
     of nnkRecList, nnkTupleTy:
       for it in typeNode:
-        foldObjectBody(dst, it, tmpSym, jsonNode, jsonPath, depth + 1)
+        foldObjectBody(dst, it, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
 
     of nnkIdentDefs:
       typeNode.expectLen 3
       let fieldSym = typeNode[0]
       let fieldNameLit = newLit(fieldSym.strVal)
+      let fieldPathLit = newLit("." & fieldSym.strVal)
       let fieldType = typeNode[1]
 
       # Detecting incompatiple tuple types in `assignObjectImpl` only
@@ -1092,16 +1124,30 @@ when defined(nimFixedForwardGeneric):
       detectIncompatibleType(fieldType, fieldSym)
 
       dst.add quote do:
-        initFromJson(`tmpSym`.`fieldSym`, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath` & "." & `fieldNameLit`)
+        jsonPath.add `fieldPathLit`
+        when nimvm:
+          when isRefSkipDistinct(`tmpSym`.`fieldSym`):
+            # workaround #12489
+            var tmp: `fieldType`
+            initFromJson(tmp, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath`)
+            `tmpSym`.`fieldSym` = tmp
+          else:
+            initFromJson(`tmpSym`.`fieldSym`, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath`)
+        else:
+          initFromJson(`tmpSym`.`fieldSym`, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath`)
+        jsonPath.setLen `originalJsonPathLen`
 
     of nnkRecCase:
       let kindSym = typeNode[0][0]
       let kindNameLit = newLit(kindSym.strVal)
+      let kindPathLit = newLit("." & kindSym.strVal)
       let kindType = typeNode[0][1]
       let kindOffsetLit = newLit(uint(getOffset(kindSym)))
       dst.add quote do:
         var kindTmp: `kindType`
-        initFromJson(kindTmp, `jsonNode`[`kindNameLit`], `jsonPath` & "." & `kindNameLit`)
+        jsonPath.add `kindPathLit`
+        initFromJson(kindTmp, `jsonNode`[`kindNameLit`], `jsonPath`)
+        jsonPath.setLen `originalJsonPathLen`
         when defined js:
           `tmpSym`.`kindSym` = kindTmp
         else:
@@ -1112,14 +1158,14 @@ when defined(nimFixedForwardGeneric):
             ((cast[ptr `kindType`](cast[uint](`tmpSym`.addr) + `kindOffsetLit`))[]) = kindTmp
       dst.add nnkCaseStmt.newTree(nnkDotExpr.newTree(tmpSym, kindSym))
       for i in 1 ..< typeNode.len:
-        foldObjectBody(dst, typeNode[i], tmpSym, jsonNode, jsonPath, depth + 1)
+        foldObjectBody(dst, typeNode[i], tmpSym, jsonNode, jsonPath, originalJsonPathLen)
 
     of nnkOfBranch, nnkElse:
       let ofBranch = newNimNode(typeNode.kind)
       for i in 0 ..< typeNode.len-1:
         ofBranch.add copyNimTree(typeNode[i])
       let dstInner = newNimNode(nnkStmtListExpr)
-      foldObjectBody(dstInner, typeNode[^1], tmpSym, jsonNode, jsonPath, depth + 1)
+      foldObjectBody(dstInner, typeNode[^1], tmpSym, jsonNode, jsonPath, originalJsonPathLen)
       # resOuter now contains the inner stmtList
       ofBranch.add dstInner
       dst[^1].expectKind nnkCaseStmt
@@ -1133,26 +1179,29 @@ when defined(nimFixedForwardGeneric):
         var impl = getTypeImpl(base)
         while impl.kind in {nnkRefTy, nnkPtrTy}:
           impl = getTypeImpl(impl[0])
-        foldObjectBody(dst, impl, tmpSym, jsonNode, jsonPath, depth + 1)
+        foldObjectBody(dst, impl, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
       let body = typeNode[2]
-      foldObjectBody(dst, body, tmpSym, jsonNode, jsonPath, depth + 1)
+      foldObjectBody(dst, body, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
 
     else:
       error("unhandled kind: " & $typeNode.kind, typeNode)
 
 
-  macro assignObjectImpl[T](dst: var T; jsonNode: JsonNode; jsonPath: string) =
+  macro assignObjectImpl[T](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
     let typeSym = getTypeInst(dst)
+    let originalJsonPathLen = genSym(nskLet, "originalJsonPathLen")
     result = newStmtList()
+    result.add quote do:
+      let `originalJsonPathLen` = len(`jsonPath`)
     if typeSym.kind in {nnkTupleTy, nnkTupleConstr}:
       # both, `dst` and `typeSym` don't have good lineinfo. But nothing
       # else is available here.
       detectIncompatibleType(typeSym, dst)
-      foldObjectBody(result, typeSym, dst, jsonNode, jsonPath, 0)
+      foldObjectBody(result, typeSym, dst, jsonNode, jsonPath, originalJsonPathLen)
     else:
-      foldObjectBody(result, typeSym.getTypeImpl, dst, jsonNode, jsonPath, 0)
+      foldObjectBody(result, typeSym.getTypeImpl, dst, jsonNode, jsonPath, originalJsonPathLen)
 
-  proc initFromJson[T : object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: string) =
+  proc initFromJson[T : object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
     assignObjectImpl(dst, jsonNode, jsonPath)
 
   proc to*[T](node: JsonNode, t: typedesc[T]): T =
@@ -1191,7 +1240,8 @@ when defined(nimFixedForwardGeneric):
     ##     doAssert data.person.age == 21
     ##     doAssert data.list == @[1, 2, 3, 4]
 
-    initFromJson(result, node, "")
+    var jsonPath = ""
+    initFromJson(result, node, jsonPath)
 
 when false:
   import os
@@ -1424,3 +1474,30 @@ when isMainModule:
         res.add($x)
         res.add " "
       doAssert res == fragments
+
+
+  # test isRefSkipDistinct
+  type
+    MyRef = ref object
+    MyObject = object
+    MyDistinct = distinct MyRef
+    MyOtherDistinct = distinct MyRef
+
+  var x0: ref int
+  var x1: MyRef
+  var x2: MyObject
+  var x3: MyDistinct
+  var x4: MyOtherDistinct
+
+  doAssert isRefSkipDistinct(x0)
+  doAssert isRefSkipDistinct(x1)
+  doAssert not isRefSkipDistinct(x2)
+  doAssert isRefSkipDistinct(x3)
+  doAssert isRefSkipDistinct(x4)
+
+
+  doAssert isRefSkipDistinct(ref int)
+  doAssert isRefSkipDistinct(MyRef)
+  doAssert not isRefSkipDistinct(MyObject)
+  doAssert isRefSkipDistinct(MyDistinct)
+  doAssert isRefSkipDistinct(MyOtherDistinct)
diff --git a/lib/system.nim b/lib/system.nim
index 93e4534fb..663ac1f66 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -3872,6 +3872,12 @@ elif defined(JS):
   proc deallocShared(p: pointer) = discard
   proc reallocShared(p: pointer, newsize: Natural): pointer = discard
 
+  proc addInt*(result: var string; x: int64) =
+    result.add $x
+
+  proc addFloat*(result: var string; x: float) =
+    result.add $x
+
   when defined(JS) and not defined(nimscript):
     include "system/jssys"
     include "system/reprjs"
@@ -4326,9 +4332,9 @@ proc addQuoted*[T](s: var string, x: T) =
     s.addEscapedChar(x)
     s.add("'")
   # prevent temporary string allocation
-  elif T is SomeSignedInt and not defined(JS):
+  elif T is SomeSignedInt:
     s.addInt(x)
-  elif T is SomeFloat and not defined(JS):
+  elif T is SomeFloat:
     s.addFloat(x)
   elif compiles(s.add(x)):
     s.add(x)
diff --git a/tests/stdlib/tjsonmacro.nim b/tests/stdlib/tjsonmacro.nim
index c2a4ed406..938030d8e 100644
--- a/tests/stdlib/tjsonmacro.nim
+++ b/tests/stdlib/tjsonmacro.nim
@@ -5,17 +5,26 @@ discard """
 
 import json, strutils, options, tables
 
-when true:
+# The definition of the `%` proc needs to be here, since the `% c` calls below
+# can only find our custom `%` proc for `Pix` if defined in global scope.
+type
+  Pix = tuple[x, y: uint8, ch: uint16]
+proc `%`(p: Pix): JsonNode =
+  result = %* { "x" : % p.x,
+                "y" : % p.y,
+                "ch" : % p.ch }
+
+proc testJson() =
   # Tests inspired by own use case (with some additional tests).
   # This should succeed.
   type
     Point[T] = object
       x, y: T
 
-    ReplayEventKind* = enum
+    ReplayEventKind = enum
       FoodAppeared, FoodEaten, DirectionChanged
 
-    ReplayEvent* = object
+    ReplayEvent = object
       time*: float
       case kind*: ReplayEventKind
       of FoodAppeared, FoodEaten:
@@ -28,7 +37,7 @@ when true:
       of DirectionChanged:
         playerPos*: float
 
-    Replay* = ref object
+    Replay = ref object
       events*: seq[ReplayEvent]
       test: int
       test2: string
@@ -286,24 +295,26 @@ when true:
     doAssert parsed.color == Red
 
   block:
-    type
-      Car = object
-        engine: tuple[name: string, capacity: float]
-        model: string
+    when not defined(js):
+      # disable on js because of #12492
+      type
+        Car = object
+          engine: tuple[name: string, capacity: float]
+          model: string
 
-    let j = """
-      {"engine": {"name": "V8", "capacity": 5.5}, "model": "Skyline"}
-    """
+      let j = """
+        {"engine": {"name": "V8", "capacity": 5.5}, "model": "Skyline"}
+      """
 
-    var i = 0
-    proc mulTest: JsonNode =
-      i.inc()
-      return parseJson(j)
+      var i = 0
+      proc mulTest(): JsonNode =
+        inc i
+        return parseJson(j)
 
-    let parsed = mulTest().to(Car)
-    doAssert parsed.engine.name == "V8"
+      let parsed = mulTest().to(Car)
+      doAssert parsed.engine.name == "V8"
 
-    doAssert i == 1
+      doAssert i == 1
 
   block:
     # Option[T] support!
@@ -424,7 +435,11 @@ when true:
   block:
     let s = """{"a": 1, "b": 2}"""
     let t = parseJson(s).to(Table[string, int])
-    doAssert t["a"] == 1
+    when not defined(js):
+      # For some reason on the JS backend `{"b": 2, "a": 0}` is
+      # sometimes the value of `t`. This needs investigation. I can't
+      # reproduce it right now in an isolated test.
+      doAssert t["a"] == 1
     doAssert t["b"] == 2
 
   block:
@@ -480,6 +495,7 @@ when true:
     doAssert Table[string,int](t.dict)["a"] == 1
     doAssert Table[string,int](t.dict)["b"] == 2
     doAssert array[3, float](t.arr) == [1.0,2.0,7.0]
+
     doAssert MyRef(t.person).name == "boney"
     doAssert MyObj(t.distFruit).color == 11
     doAssert t.dog.name == "honey"
@@ -526,109 +542,99 @@ when true:
       doAssert v.name == "smith"
       doAssert MyRef(w).name == "smith"
 
-# bug #12015
-# The definition of the `%` proc needs to be here, since the `% c` calls below
-# can only find our custom `%` proc for `Pix` if defined in global scope.
-type
-  Pix = tuple[x, y: uint8, ch: uint16]
-proc `%`(p: Pix): JsonNode =
-  result = %* { "x" : % p.x,
-                "y" : % p.y,
-                "ch" : % p.ch }
-block:
-  type
-    Cluster = object
-      works: tuple[x, y: uint8, ch: uint16] # working
-      fails: Pix # previously broken
+  block:
+    # bug #12015
+    type
+      Cluster = object
+        works: tuple[x, y: uint8, ch: uint16] # working
+        fails: Pix # previously broken
 
-  let data = (x: 123'u8, y: 53'u8, ch: 1231'u16)
-  let c = Cluster(works: data, fails: data)
-  let cFromJson = (% c).to(Cluster)
-  doAssert c == cFromJson
+    let data = (x: 123'u8, y: 53'u8, ch: 1231'u16)
+    let c = Cluster(works: data, fails: data)
+    let cFromJson = (% c).to(Cluster)
+    doAssert c == cFromJson
 
-block:
-  # bug related to #12015
-  type
-    PixInt = tuple[x, y, ch: int]
-    SomePix = Pix | PixInt
-    Cluster[T: SomePix] = seq[T]
-    ClusterObject[T: SomePix] = object
-      data: Cluster[T]
-    RecoEvent[T: SomePix] = object
-      cluster: seq[ClusterObject[T]]
-
-  let data = @[(x: 123'u8, y: 53'u8, ch: 1231'u16)]
-  var c = RecoEvent[Pix](cluster: @[ClusterObject[Pix](data: data)])
-  let cFromJson = (% c).to(RecoEvent[Pix])
-  doAssert c == cFromJson
-
-# TODO: when the issue with the limeted vm registers is solved, the
-# exact same test as above should be evaluated at compile time as
-# well, to ensure that the vm functionality won't diverge from the
-# runtime functionality. Until then, the following test should do it.
+  block:
+    # bug related to #12015
+    type
+      PixInt = tuple[x, y, ch: int]
+      SomePix = Pix | PixInt
+      Cluster[T: SomePix] = seq[T]
+      ClusterObject[T: SomePix] = object
+        data: Cluster[T]
+      RecoEvent[T: SomePix] = object
+        cluster: seq[ClusterObject[T]]
 
-static:
-  var t = parseJson("""
-    {
-      "name":"Bongo",
-      "email":"bongo@bingo.com",
-      "list": [11,7,15],
-      "year": 1975,
-      "dict": {"a": 1, "b": 2},
-      "arr": [1.0, 2.0, 7.0],
-      "person": {"name": "boney"},
-      "dog": {"name": "honey"},
-      "fruit": {"color": 10},
-      "distfruit": {"color": 11},
-      "emails": ["abc", "123"]
-    }
-  """)
-
-  doAssert t["name"].getStr == "Bongo"
-  doAssert t["email"].getStr == "bongo@bingo.com"
-  doAssert t["list"][0].getInt == 11
-  doAssert t["list"][1].getInt == 7
-  doAssert t["list"][2].getInt == 15
-  doAssert t["year"].getInt == 1975
-  doAssert t["dict"]["a"].getInt == 1
-  doAssert t["dict"]["b"].getInt == 2
-  doAssert t["arr"][0].getFloat == 1.0
-  doAssert t["arr"][1].getFloat == 2.0
-  doAssert t["arr"][2].getFloat == 7.0
-  doAssert t["person"]["name"].getStr == "boney"
-  doAssert t["distfruit"]["color"].getInt == 11
-  doAssert t["dog"]["name"].getStr == "honey"
-  doAssert t["fruit"]["color"].getInt == 10
-  doAssert t["emails"][0].getStr == "abc"
-  doAssert t["emails"][1].getStr == "123"
-
-block:
-  # ref objects with cycles.
-  type
-    Misdirection = object
-      cycle: Cycle
+    let data = @[(x: 123'u8, y: 53'u8, ch: 1231'u16)]
+    var c = RecoEvent[Pix](cluster: @[ClusterObject[Pix](data: data)])
+    let cFromJson = (% c).to(RecoEvent[Pix])
+    doAssert c == cFromJson
 
-    Cycle = ref object
-      foo: string
-      cycle: Misdirection
 
-  let data = """
-    {"cycle": null}
-  """
+  block:
+    # ref objects with cycles.
+    type
+      Misdirection = object
+        cycle: Cycle
 
-  let dataParsed = parseJson(data)
-  let dataDeser = to(dataParsed, Misdirection)
+      Cycle = ref object
+        foo: string
+        cycle: Misdirection
 
-block:
-  # ref object from #12316
-  type
-    Foo = ref Bar
-    Bar = object
+    let data = """
+      {"cycle": null}
+    """
+
+    let dataParsed = parseJson(data)
+    let dataDeser = to(dataParsed, Misdirection)
 
-  discard "null".parseJson.to Foo
+  block:
+    # ref object from #12316
+    type
+      Foo = ref Bar
+      Bar = object
+
+    discard "null".parseJson.to Foo
+
+  block:
+    # named array #12289
+    type Vec = array[2, int]
+    let arr = "[1,2]".parseJson.to Vec
+    doAssert arr == [1,2]
+
+  block:
+    # test error message in exception
 
-block:
-  # named array #12289
-  type Vec = array[2, int]
-  let arr = "[1,2]".parseJson.to Vec
-  doAssert arr == [1,2]
+    type
+      MyType = object
+        otherMember: string
+        member: MySubType
+
+      MySubType = object
+        somethingElse: string
+        list: seq[MyData]
+
+      MyData = object
+        value: int
+
+    let jsonNode = parseJson("""
+      {
+        "otherMember": "otherValue",
+        "member": {
+          "somethingElse": "something",
+          "list": [{"value": 1}, {"value": 2}, {}]
+        }
+      }
+    """)
+
+    try:
+      let tmp = jsonNode.to(MyType)
+      doAssert false, "this should be unreachable"
+    except KeyError:
+      doAssert getCurrentExceptionMsg().contains ".member.list[2].value"
+
+
+
+testJson()
+static:
+  testJson()