summary refs log tree commit diff stats
path: root/compiler/transf.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/transf.nim')
-rw-r--r--compiler/transf.nim1136
1 files changed, 700 insertions, 436 deletions
diff --git a/compiler/transf.nim b/compiler/transf.nim
index 815cb6286..8dd24e090 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -18,73 +18,75 @@
 # * performs lambda lifting for closure support
 # * transforms 'defer' into a 'try finally' statement
 
+import std / tables
+
 import
-  intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os,
-  idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread,
-  lambdalifting, sempass2, lowerings, lookups, destroyer
+  options, ast, astalgo, trees, msgs,
+  idents, renderer, types, semfold, magicsys, cgmeth,
+  lowerings, liftlocals,
+  modulegraphs, lineinfos
+
+when defined(nimPreviewSlimSystem):
+  import std/assertions
 
 type
-  PTransNode* = distinct PNode
+  TransformFlag* = enum
+    useCache, keepOpenArrayConversions, force
+  TransformFlags* = set[TransformFlag]
+
+proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: TransformFlags): PNode
 
-  PTransCon = ref TTransCon
-  TTransCon{.final.} = object # part of TContext; stackable
-    mapping: TIdNodeTable     # mapping from symbols to nodes
+import closureiters, lambdalifting
+
+type
+  PTransCon = ref object # part of TContext; stackable
+    mapping: Table[ItemId, PNode]     # mapping from symbols to nodes
     owner: PSym               # current owner
     forStmt: PNode            # current for stmt
-    forLoopBody: PTransNode   # transformed for loop body
+    forLoopBody: PNode   # transformed for loop body
     yieldStmts: int           # we count the number of yield statements,
                               # because we need to introduce new variables
                               # if we encounter the 2nd yield statement
     next: PTransCon           # for stacking
 
-  TTransfContext = object of passes.TPassContext
+  PTransf = ref object
     module: PSym
     transCon: PTransCon      # top of a TransCon stack
     inlining: int            # > 0 if we are in inlining context (copy vars)
-    nestedProcs: int         # > 0 if we are in a nested proc
     contSyms, breakSyms: seq[PSym]  # to transform 'continue' and 'break'
-    deferDetected, tooEarly, needsDestroyPass: bool
-  PTransf = ref TTransfContext
+    deferDetected, tooEarly: bool
+    isIntroducingNewLocalVars: bool  # true if we are in `introducingNewLocalVars` (don't transform yields)
+    inAddr: bool
+    flags: TransformFlags
+    graph: ModuleGraph
+    idgen: IdGenerator
 
-proc newTransNode(a: PNode): PTransNode {.inline.} =
-  result = PTransNode(shallowCopy(a))
+proc newTransNode(a: PNode): PNode {.inline.} =
+  result = shallowCopy(a)
 
 proc newTransNode(kind: TNodeKind, info: TLineInfo,
-                  sons: int): PTransNode {.inline.} =
+                  sons: int): PNode {.inline.} =
   var x = newNodeI(kind, info)
   newSeq(x.sons, sons)
-  result = x.PTransNode
+  result = x
 
 proc newTransNode(kind: TNodeKind, n: PNode,
-                  sons: int): PTransNode {.inline.} =
+                  sons: int): PNode {.inline.} =
   var x = newNodeIT(kind, n.info, n.typ)
   newSeq(x.sons, sons)
-  x.typ = n.typ
-  result = x.PTransNode
-
-proc `[]=`(a: PTransNode, i: int, x: PTransNode) {.inline.} =
-  var n = PNode(a)
-  n.sons[i] = PNode(x)
-
-proc `[]`(a: PTransNode, i: int): PTransNode {.inline.} =
-  var n = PNode(a)
-  result = n.sons[i].PTransNode
-
-proc add(a, b: PTransNode) {.inline.} = addSon(PNode(a), PNode(b))
-proc len(a: PTransNode): int {.inline.} = result = sonsLen(a.PNode)
+#  x.flags = n.flags
+  result = x
 
 proc newTransCon(owner: PSym): PTransCon =
   assert owner != nil
-  new(result)
-  initIdNodeTable(result.mapping)
-  result.owner = owner
+  result = PTransCon(mapping: initTable[ItemId, PNode](), owner: owner)
 
 proc pushTransCon(c: PTransf, t: PTransCon) =
   t.next = c.transCon
   c.transCon = t
 
 proc popTransCon(c: PTransf) =
-  if (c.transCon == nil): internalError("popTransCon")
+  if (c.transCon == nil): internalError(c.graph.config, "popTransCon")
   c.transCon = c.transCon.next
 
 proc getCurrOwner(c: PTransf): PSym =
@@ -92,36 +94,40 @@ proc getCurrOwner(c: PTransf): PSym =
   else: result = c.module
 
 proc newTemp(c: PTransf, typ: PType, info: TLineInfo): PNode =
-  let r = newSym(skTemp, getIdent(genPrefix), getCurrOwner(c), info)
-  r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias})
+  let r = newSym(skTemp, getIdent(c.graph.cache, genPrefix), c.idgen, getCurrOwner(c), info)
+  r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias, tySink})
   incl(r.flags, sfFromGeneric)
   let owner = getCurrOwner(c)
-  if owner.isIterator and not c.tooEarly:
-    result = freshVarForClosureIter(r, owner)
+  if owner.isIterator and not c.tooEarly and not isDefined(c.graph.config, "nimOptIters"):
+    result = freshVarForClosureIter(c.graph, r, c.idgen, owner)
   else:
     result = newSymNode(r)
 
-proc transform(c: PTransf, n: PNode): PTransNode
+proc transform(c: PTransf, n: PNode): PNode
 
-proc transformSons(c: PTransf, n: PNode): PTransNode =
+proc transformSons(c: PTransf, n: PNode): PNode =
   result = newTransNode(n)
-  for i in countup(0, sonsLen(n)-1):
-    result[i] = transform(c, n.sons[i])
-
-proc newAsgnStmt(c: PTransf, le: PNode, ri: PTransNode): PTransNode =
-  result = newTransNode(nkFastAsgn, PNode(ri).info, 2)
-  result[0] = PTransNode(le)
+  for i in 0..<n.len:
+    result[i] = transform(c, n[i])
+
+proc newAsgnStmt(c: PTransf, kind: TNodeKind, le: PNode, ri: PNode; isFirstWrite: bool): PNode =
+  result = newTransNode(kind, ri.info, 2)
+  result[0] = le
+  if isFirstWrite:
+    le.flags.incl nfFirstWrite
   result[1] = ri
 
 proc transformSymAux(c: PTransf, n: PNode): PNode =
   let s = n.sym
   if s.typ != nil and s.typ.callConv == ccClosure:
+    if s.kind in routineKinds:
+      discard transformBody(c.graph, c.idgen, s, {useCache}+c.flags)
     if s.kind == skIterator:
       if c.tooEarly: return n
-      else: return liftIterSym(n, getCurrOwner(c))
+      else: return liftIterSym(c.graph, n, c.idgen, getCurrOwner(c))
     elif s.kind in {skProc, skFunc, skConverter, skMethod} and not c.tooEarly:
       # top level .closure procs are still somewhat supported for 'Nake':
-      return makeClosure(s, nil, n.info)
+      return makeClosure(c.graph, c.idgen, s, nil, n.info)
   #elif n.sym.kind in {skVar, skLet} and n.sym.typ.callConv == ccClosure:
   #  echo n.info, " come heer for ", c.tooEarly
   #  if not c.tooEarly:
@@ -129,13 +135,34 @@ proc transformSymAux(c: PTransf, n: PNode): PNode =
   var tc = c.transCon
   if sfBorrow in s.flags and s.kind in routineKinds:
     # simply exchange the symbol:
-    b = s.getBody
-    if b.kind != nkSym: internalError(n.info, "wrong AST for borrowed symbol")
+    var s = s
+    while true:
+      # Skips over all borrowed procs getting the last proc symbol without an implementation
+      let body = getBody(c.graph, s)
+      if body.kind == nkSym and sfBorrow in body.sym.flags and getBody(c.graph, body.sym).kind == nkSym:
+        s = body.sym
+      else:
+        break
+    b = getBody(c.graph, s)
+    if b.kind != nkSym: internalError(c.graph.config, n.info, "wrong AST for borrowed symbol")
     b = newSymNode(b.sym, n.info)
+  elif c.inlining > 0:
+    # see bug #13596: we use ref-based equality in the DFA for destruction
+    # injections so we need to ensure unique nodes after iterator inlining
+    # which can lead to duplicated for loop bodies! Consider:
+    #[
+      while remaining > 0:
+        if ending == nil:
+          yield ms
+          break
+        ...
+        yield ms
+    ]#
+    b = newSymNode(n.sym, n.info)
   else:
     b = n
   while tc != nil:
-    result = idNodeTableGet(tc.mapping, b.sym)
+    result = getOrDefault(tc.mapping, b.sym.itemId)
     if result != nil:
       # this slightly convoluted way ensures the line info stays correct:
       if result.kind == nkSym:
@@ -145,38 +172,40 @@ proc transformSymAux(c: PTransf, n: PNode): PNode =
     tc = tc.next
   result = b
 
-proc transformSym(c: PTransf, n: PNode): PTransNode =
-  result = PTransNode(transformSymAux(c, n))
+proc transformSym(c: PTransf, n: PNode): PNode =
+  result = transformSymAux(c, n)
 
 proc freshVar(c: PTransf; v: PSym): PNode =
   let owner = getCurrOwner(c)
-  if owner.isIterator and not c.tooEarly:
-    result = freshVarForClosureIter(v, owner)
+  if owner.isIterator and not c.tooEarly and not isDefined(c.graph.config, "nimOptIters"):
+    result = freshVarForClosureIter(c.graph, v, c.idgen, owner)
   else:
-    var newVar = copySym(v)
+    var newVar = copySym(v, c.idgen)
     incl(newVar.flags, sfFromGeneric)
     newVar.owner = owner
     result = newSymNode(newVar)
 
-proc transformVarSection(c: PTransf, v: PNode): PTransNode =
+proc transformVarSection(c: PTransf, v: PNode): PNode =
   result = newTransNode(v)
-  for i in countup(0, sonsLen(v)-1):
-    var it = v.sons[i]
+  for i in 0..<v.len:
+    var it = v[i]
     if it.kind == nkCommentStmt:
-      result[i] = PTransNode(it)
+      result[i] = it
     elif it.kind == nkIdentDefs:
-      if it.sons[0].kind == nkSym:
-        internalAssert(it.len == 3)
-        let x = freshVar(c, it.sons[0].sym)
-        idNodeTablePut(c.transCon.mapping, it.sons[0].sym, x)
+      var vn = it[0]
+      if vn.kind == nkPragmaExpr: vn = vn[0]
+      if vn.kind == nkSym:
+        internalAssert(c.graph.config, it.len == 3)
+        let x = freshVar(c, vn.sym)
+        c.transCon.mapping[vn.sym.itemId] = x
         var defs = newTransNode(nkIdentDefs, it.info, 3)
-        if importantComments():
+        if importantComments(c.graph.config):
           # keep documentation information:
-          PNode(defs).comment = it.comment
-        defs[0] = x.PTransNode
-        defs[1] = it.sons[1].PTransNode
-        defs[2] = transform(c, it.sons[2])
-        if x.kind == nkSym: x.sym.ast = defs[2].PNode
+          defs.comment = it.comment
+        defs[0] = x
+        defs[1] = it[1]
+        defs[2] = transform(c, it[2])
+        if x.kind == nkSym: x.sym.ast = defs[2]
         result[i] = defs
       else:
         # has been transformed into 'param.x' for closure iterators, so just
@@ -184,68 +213,65 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode =
         result[i] = transform(c, it)
     else:
       if it.kind != nkVarTuple:
-        internalError(it.info, "transformVarSection: not nkVarTuple")
-      var L = sonsLen(it)
-      var defs = newTransNode(it.kind, it.info, L)
-      for j in countup(0, L-3):
-        let x = freshVar(c, it.sons[j].sym)
-        idNodeTablePut(c.transCon.mapping, it.sons[j].sym, x)
-        defs[j] = x.PTransNode
-      assert(it.sons[L-2].kind == nkEmpty)
-      defs[L-2] = ast.emptyNode.PTransNode
-      defs[L-1] = transform(c, it.sons[L-1])
+        internalError(c.graph.config, it.info, "transformVarSection: not nkVarTuple")
+      var defs = newTransNode(it.kind, it.info, it.len)
+      for j in 0..<it.len-2:
+        if it[j].kind == nkSym:
+          let x = freshVar(c, it[j].sym)
+          c.transCon.mapping[it[j].sym.itemId] = x
+          defs[j] = x
+        else:
+          defs[j] = transform(c, it[j])
+      assert(it[^2].kind == nkEmpty)
+      defs[^2] = newNodeI(nkEmpty, it.info)
+      defs[^1] = transform(c, it[^1])
       result[i] = defs
 
-proc transformConstSection(c: PTransf, v: PNode): PTransNode =
-  result = newTransNode(v)
-  for i in countup(0, sonsLen(v)-1):
-    var it = v.sons[i]
-    if it.kind == nkCommentStmt:
-      result[i] = PTransNode(it)
-    else:
-      if it.kind != nkConstDef: internalError(it.info, "transformConstSection")
-      if it.sons[0].kind != nkSym:
-        internalError(it.info, "transformConstSection")
+proc transformConstSection(c: PTransf, v: PNode): PNode =
+  result = v
+  when false:
+    result = newTransNode(v)
+    for i in 0..<v.len:
+      var it = v[i]
+      if it.kind == nkCommentStmt:
+        result[i] = it
+      else:
+        if it.kind != nkConstDef: internalError(c.graph.config, it.info, "transformConstSection")
+        if it[0].kind != nkSym:
+          debug it[0]
+          internalError(c.graph.config, it.info, "transformConstSection")
 
-      result[i] = PTransNode(it)
+        result[i] = it
 
 proc hasContinue(n: PNode): bool =
   case n.kind
-  of nkEmpty..nkNilLit, nkForStmt, nkParForStmt, nkWhileStmt: discard
+  of nkEmpty..nkNilLit, nkForStmt, nkParForStmt, nkWhileStmt: result = false
   of nkContinueStmt: result = true
   else:
-    for i in countup(0, sonsLen(n) - 1):
-      if hasContinue(n.sons[i]): return true
+    result = false
+    for i in 0..<n.len:
+      if hasContinue(n[i]): return true
 
 proc newLabel(c: PTransf, n: PNode): PSym =
-  result = newSym(skLabel, nil, getCurrOwner(c), n.info)
-  result.name = getIdent(genPrefix & $result.id)
-
-proc freshLabels(c: PTransf, n: PNode; symMap: var TIdTable) =
-  if n.kind in {nkBlockStmt, nkBlockExpr}:
-    if n.sons[0].kind == nkSym:
-      let x = newLabel(c, n[0])
-      idTablePut(symMap, n[0].sym, x)
-      n.sons[0].sym = x
-  if n.kind == nkSym and n.sym.kind == skLabel:
-    let x = PSym(idTableGet(symMap, n.sym))
-    if x != nil: n.sym = x
-  else:
-    for i in 0 .. <safeLen(n): freshLabels(c, n.sons[i], symMap)
+  result = newSym(skLabel, getIdent(c.graph.cache, genPrefix), c.idgen, getCurrOwner(c), n.info)
 
-proc transformBlock(c: PTransf, n: PNode): PTransNode =
+proc transformBlock(c: PTransf, n: PNode): PNode =
   var labl: PSym
-  if n.sons[0].kind != nkEmpty:
-    # already named block? -> Push symbol on the stack:
-    labl = n.sons[0].sym
+  if c.inlining > 0:
+    labl = newLabel(c, n[0])
+    c.transCon.mapping[n[0].sym.itemId] = newSymNode(labl)
   else:
-    labl = newLabel(c, n)
+    labl =
+      if n[0].kind != nkEmpty:
+        n[0].sym  # already named block? -> Push symbol on the stack
+      else:
+        newLabel(c, n)
   c.breakSyms.add(labl)
   result = transformSons(c, n)
   discard c.breakSyms.pop
-  result[0] = newSymNode(labl).PTransNode
+  result[0] = newSymNode(labl)
 
-proc transformLoopBody(c: PTransf, n: PNode): PTransNode =
+proc transformLoopBody(c: PTransf, n: PNode): PNode =
   # What if it contains "continue" and "break"? "break" needs
   # an explicit label too, but not the same!
 
@@ -257,59 +283,41 @@ proc transformLoopBody(c: PTransf, n: PNode): PTransNode =
     c.contSyms.add(labl)
 
     result = newTransNode(nkBlockStmt, n.info, 2)
-    result[0] = newSymNode(labl).PTransNode
+    result[0] = newSymNode(labl)
     result[1] = transform(c, n)
     discard c.contSyms.pop()
   else:
     result = transform(c, n)
 
-proc transformWhile(c: PTransf; n: PNode): PTransNode =
+proc transformWhile(c: PTransf; n: PNode): PNode =
   if c.inlining > 0:
     result = transformSons(c, n)
   else:
     let labl = newLabel(c, n)
     c.breakSyms.add(labl)
     result = newTransNode(nkBlockStmt, n.info, 2)
-    result[0] = newSymNode(labl).PTransNode
+    result[0] = newSymNode(labl)
 
     var body = newTransNode(n)
-    for i in 0..n.len-2:
-      body[i] = transform(c, n.sons[i])
-    body[<n.len] = transformLoopBody(c, n.sons[<n.len])
+    for i in 0..<n.len-1:
+      body[i] = transform(c, n[i])
+    body[^1] = transformLoopBody(c, n[^1])
     result[1] = body
     discard c.breakSyms.pop
 
-proc transformBreak(c: PTransf, n: PNode): PTransNode =
-  if n.sons[0].kind != nkEmpty or c.inlining > 0:
-    result = n.PTransNode
-    when false:
-      let lablCopy = idNodeTableGet(c.transCon.mapping, n.sons[0].sym)
-      if lablCopy.isNil:
-        result = n.PTransNode
-      else:
-        result = newTransNode(n.kind, n.info, 1)
-        result[0] = lablCopy.PTransNode
-  elif c.breakSyms.len > 0:
-    # this check can fail for 'nim check'
+proc transformBreak(c: PTransf, n: PNode): PNode =
+  result = transformSons(c, n)
+  if n[0].kind == nkEmpty and c.breakSyms.len > 0:
     let labl = c.breakSyms[c.breakSyms.high]
-    result = transformSons(c, n)
-    result[0] = newSymNode(labl).PTransNode
-  else:
-    result = n.PTransNode
-
-proc unpackTuple(c: PTransf, n: PNode, father: PTransNode) =
-  # XXX: BUG: what if `n` is an expression with side-effects?
-  for i in countup(0, sonsLen(c.transCon.forStmt) - 3):
-    add(father, newAsgnStmt(c, c.transCon.forStmt.sons[i],
-        transform(c, newTupleAccess(n, i))))
+    result[0] = newSymNode(labl)
 
-proc introduceNewLocalVars(c: PTransf, n: PNode): PTransNode =
+proc introduceNewLocalVars(c: PTransf, n: PNode): PNode =
   case n.kind
   of nkSym:
     result = transformSym(c, n)
   of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
     # nothing to be done for leaves:
-    result = PTransNode(n)
+    result = n
   of nkVarSection, nkLetSection:
     result = transformVarSection(c, n)
   of nkClosure:
@@ -318,98 +326,230 @@ proc introduceNewLocalVars(c: PTransf, n: PNode): PTransNode =
     # (bug #2604). We need to patch this environment here too:
     let a = n[1]
     if a.kind == nkSym:
-      n.sons[1] = transformSymAux(c, a)
-    return PTransNode(n)
+      n[1] = transformSymAux(c, a)
+    return n
+  of nkProcDef: # todo optimize nosideeffects?
+    result = newTransNode(n)
+    let x = newSymNode(copySym(n[namePos].sym, c.idgen))
+    c.transCon.mapping[n[namePos].sym.itemId] = x
+    result[namePos] = x # we have to copy proc definitions for iters
+    for i in 1..<n.len:
+      result[i] = introduceNewLocalVars(c, n[i])
+    result[namePos].sym.ast = result
   else:
     result = newTransNode(n)
-    for i in countup(0, sonsLen(n)-1):
-      result[i] = introduceNewLocalVars(c, n.sons[i])
-
-proc transformYield(c: PTransf, n: PNode): PTransNode =
+    for i in 0..<n.len:
+      result[i] = introduceNewLocalVars(c, n[i])
+
+proc transformAsgn(c: PTransf, n: PNode): PNode =
+  let rhs = n[1]
+
+  if rhs.kind != nkTupleConstr:
+    return transformSons(c, n)
+
+  # Unpack the tuple assignment into N temporary variables and then pack them
+  # into a tuple: this allows us to get the correct results even when the rhs
+  # depends on the value of the lhs
+  let letSection = newTransNode(nkLetSection, n.info, rhs.len)
+  let newTupleConstr = newTransNode(nkTupleConstr, n.info, rhs.len)
+  for i, field in rhs:
+    let val = if field.kind == nkExprColonExpr: field[1] else: field
+    let def = newTransNode(nkIdentDefs, field.info, 3)
+    def[0] = newTemp(c, val.typ, field.info)
+    def[1] = newNodeI(nkEmpty, field.info)
+    def[2] = transform(c, val)
+    letSection[i] = def
+    # NOTE: We assume the constructor fields are in the correct order for the
+    # given tuple type
+    newTupleConstr[i] = def[0]
+
+  newTupleConstr.typ = rhs.typ
+
+  let asgnNode = newTransNode(nkAsgn, n.info, 2)
+  asgnNode[0] = transform(c, n[0])
+  asgnNode[1] = newTupleConstr
+
+  result = newTransNode(nkStmtList, n.info, 2)
+  result[0] = letSection
+  result[1] = asgnNode
+
+proc transformYield(c: PTransf, n: PNode): PNode =
+  proc asgnTo(lhs: PNode, rhs: PNode): PNode =
+    # Choose the right assignment instruction according to the given ``lhs``
+    # node since it may not be a nkSym (a stack-allocated skForVar) but a
+    # nkDotExpr (a heap-allocated slot into the envP block)
+    case lhs.kind
+    of nkSym:
+      internalAssert c.graph.config, lhs.sym.kind == skForVar
+      result = newAsgnStmt(c, nkFastAsgn, lhs, rhs, false)
+    of nkDotExpr:
+      result = newAsgnStmt(c, nkAsgn, lhs, rhs, false)
+    else:
+      result = nil
+      internalAssert c.graph.config, false
   result = newTransNode(nkStmtList, n.info, 0)
-  var e = n.sons[0]
+  var e = n[0]
   # c.transCon.forStmt.len == 3 means that there is one for loop variable
   # and thus no tuple unpacking:
   if e.typ.isNil: return result # can happen in nimsuggest for unknown reasons
-  if skipTypes(e.typ, {tyGenericInst, tyAlias}).kind == tyTuple and
-      c.transCon.forStmt.len != 3:
+  if c.transCon.forStmt.len != 3:
     e = skipConv(e)
-    if e.kind == nkPar:
-      for i in countup(0, sonsLen(e) - 1):
-        var v = e.sons[i]
-        if v.kind == nkExprColonExpr: v = v.sons[1]
-        add(result, newAsgnStmt(c, c.transCon.forStmt.sons[i],
-                                transform(c, v)))
+    if e.kind == nkTupleConstr:
+      for i in 0..<e.len:
+        var v = e[i]
+        if v.kind == nkExprColonExpr: v = v[1]
+        if c.transCon.forStmt[i].kind == nkVarTuple:
+          for j in 0..<c.transCon.forStmt[i].len-1:
+            let lhs = c.transCon.forStmt[i][j]
+            let rhs = transform(c, newTupleAccess(c.graph, v, j))
+            result.add(asgnTo(lhs, rhs))
+        else:
+          let lhs = c.transCon.forStmt[i]
+          let rhs = transform(c, v)
+          result.add(asgnTo(lhs, rhs))
+    elif e.kind notin {nkAddr, nkHiddenAddr}: # no need to generate temp for address operation
+      # TODO do not use temp for nodes which cannot have side-effects
+      var tmp = newTemp(c, e.typ, e.info)
+      let v = newNodeI(nkVarSection, e.info)
+      v.addVar(tmp, e)
+
+      result.add transform(c, v)
+
+      for i in 0..<c.transCon.forStmt.len - 2:
+        if c.transCon.forStmt[i].kind == nkVarTuple:
+          for j in 0..<c.transCon.forStmt[i].len-1:
+            let lhs = c.transCon.forStmt[i][j]
+            let rhs = transform(c, newTupleAccess(c.graph, newTupleAccess(c.graph, tmp, i), j))
+            result.add(asgnTo(lhs, rhs))
+        else:
+          let lhs = c.transCon.forStmt[i]
+          let rhs = transform(c, newTupleAccess(c.graph, tmp, i))
+          result.add(asgnTo(lhs, rhs))
     else:
-      unpackTuple(c, e, result)
+      for i in 0..<c.transCon.forStmt.len - 2:
+        let lhs = c.transCon.forStmt[i]
+        let rhs = transform(c, newTupleAccess(c.graph, e, i))
+        result.add(asgnTo(lhs, rhs))
   else:
-    var x = transform(c, e)
-    add(result, newAsgnStmt(c, c.transCon.forStmt.sons[0], x))
+    if c.transCon.forStmt[0].kind == nkVarTuple:
+      var notLiteralTuple = false # we don't generate temp for tuples with const value: (1, 2, 3)
+      let ev = e.skipConv
+      if ev.kind == nkTupleConstr:
+        for i in ev:
+          if not isConstExpr(i):
+            notLiteralTuple = true
+            break
+      else:
+        notLiteralTuple = true
+
+      if e.kind notin {nkAddr, nkHiddenAddr} and notLiteralTuple:
+        # TODO do not use temp for nodes which cannot have side-effects
+        var tmp = newTemp(c, e.typ, e.info)
+        let v = newNodeI(nkVarSection, e.info)
+        v.addVar(tmp, e)
+
+        result.add transform(c, v)
+        for i in 0..<c.transCon.forStmt[0].len-1:
+          let lhs = c.transCon.forStmt[0][i]
+          let rhs = transform(c, newTupleAccess(c.graph, tmp, i))
+          result.add(asgnTo(lhs, rhs))
+      else:
+        for i in 0..<c.transCon.forStmt[0].len-1:
+          let lhs = c.transCon.forStmt[0][i]
+          let rhs = transform(c, newTupleAccess(c.graph, e, i))
+          result.add(asgnTo(lhs, rhs))
+    else:
+      let lhs = c.transCon.forStmt[0]
+      let rhs = transform(c, e)
+      result.add(asgnTo(lhs, rhs))
+
+
+  # bug #23536; note that the info of forLoopBody should't change
+  for idx in 0 ..< result.len:
+    var changeNode = result[idx]
+    changeNode.info = c.transCon.forStmt.info
+    for i, child in changeNode:
+      child.info = changeNode.info
 
   inc(c.transCon.yieldStmts)
   if c.transCon.yieldStmts <= 1:
     # common case
-    add(result, c.transCon.forLoopBody)
+    result.add(c.transCon.forLoopBody)
   else:
     # we need to introduce new local variables:
-    add(result, introduceNewLocalVars(c, c.transCon.forLoopBody.PNode))
+    c.isIntroducingNewLocalVars = true # don't transform yields when introducing new local vars
+    result.add(introduceNewLocalVars(c, c.transCon.forLoopBody))
+    c.isIntroducingNewLocalVars = false
 
-proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
+proc transformAddrDeref(c: PTransf, n: PNode, kinds: TNodeKinds): PNode =
   result = transformSons(c, n)
-  if gCmd == cmdCompileToCpp or sfCompileToCpp in c.module.flags: return
-  var n = result.PNode
-  case n.sons[0].kind
+  # inlining of 'var openarray' iterators; bug #19977
+  if n.typ.kind != tyOpenArray and (c.graph.config.backend == backendCpp or sfCompileToCpp in c.module.flags): return
+  var n = result
+  case n[0].kind
   of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
-    var m = n.sons[0].sons[0]
-    if m.kind == a or m.kind == b:
+    var m = n[0][0]
+    if m.kind in kinds:
       # addr ( nkConv ( deref ( x ) ) ) --> nkConv(x)
-      n.sons[0].sons[0] = m.sons[0]
-      result = PTransNode(n.sons[0])
-      if n.typ.kind != tyOpenArray:
-        PNode(result).typ = n.typ
+      n[0][0] = m[0]
+      result = n[0]
+      if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
+        result.typ = n.typ
+      elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
+        result.typ = toVar(result.typ, n.typ.skipTypes(abstractInst).kind, c.idgen)
   of nkHiddenStdConv, nkHiddenSubConv, nkConv:
-    var m = n.sons[0].sons[1]
-    if m.kind == a or m.kind == b:
+    var m = n[0][1]
+    if m.kind in kinds:
       # addr ( nkConv ( deref ( x ) ) ) --> nkConv(x)
-      n.sons[0].sons[1] = m.sons[0]
-      result = PTransNode(n.sons[0])
-      if n.typ.kind != tyOpenArray:
-        PNode(result).typ = n.typ
+      n[0][1] = m[0]
+      result = n[0]
+      if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
+        result.typ = n.typ
+      elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
+        result.typ = toVar(result.typ, n.typ.skipTypes(abstractInst).kind, c.idgen)
   else:
-    if n.sons[0].kind == a or n.sons[0].kind == b:
+    if n[0].kind in kinds and
+        not (n[0][0].kind == nkSym and n[0][0].sym.kind == skForVar and
+          n[0][0].typ.skipTypes(abstractVar).kind == tyTuple
+        ) and not (n[0][0].kind == nkSym and n[0][0].sym.kind == skParam and
+          n.typ.kind == tyVar and
+          n.typ.skipTypes(abstractVar).kind == tyOpenArray and
+          n[0][0].typ.skipTypes(abstractVar).kind == tyString)
+        : # elimination is harmful to `for tuple unpack` because of newTupleAccess
+          # it is also harmful to openArrayLoc (var openArray) for strings
       # addr ( deref ( x )) --> x
-      result = PTransNode(n.sons[0].sons[0])
-      if n.typ.kind != tyOpenArray:
-        PNode(result).typ = n.typ
+      result = n[0][0]
+      if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
+        result.typ = n.typ
 
-proc generateThunk(prc: PNode, dest: PType): PNode =
+proc generateThunk(c: PTransf; prc: PNode, dest: PType): PNode =
   ## Converts 'prc' into '(thunk, nil)' so that it's compatible with
   ## a closure.
 
   # we cannot generate a proper thunk here for GC-safety reasons
   # (see internal documentation):
-  if gCmd == cmdCompileToJS: return prc
+  if jsNoLambdaLifting in c.graph.config.legacyFeatures and c.graph.config.backend == backendJs: return prc
   result = newNodeIT(nkClosure, prc.info, dest)
   var conv = newNodeIT(nkHiddenSubConv, prc.info, dest)
-  conv.add(emptyNode)
+  conv.add(newNodeI(nkEmpty, prc.info))
   conv.add(prc)
   if prc.kind == nkClosure:
-    internalError(prc.info, "closure to closure created")
+    internalError(c.graph.config, prc.info, "closure to closure created")
   result.add(conv)
-  result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil)))
+  result.add(newNodeIT(nkNilLit, prc.info, getSysType(c.graph, prc.info, tyNil)))
 
-proc transformConv(c: PTransf, n: PNode): PTransNode =
+proc transformConv(c: PTransf, n: PNode): PNode =
   # numeric types need range checks:
   var dest = skipTypes(n.typ, abstractVarRange)
-  var source = skipTypes(n.sons[1].typ, abstractVarRange)
+  var source = skipTypes(n[1].typ, abstractVarRange)
   case dest.kind
-  of tyInt..tyInt64, tyEnum, tyChar, tyBool, tyUInt8..tyUInt32:
+  of tyInt..tyInt64, tyEnum, tyChar, tyUInt8..tyUInt32:
     # we don't include uint and uint64 here as these are no ordinal types ;-)
     if not isOrdinalType(source):
       # float -> int conversions. ugh.
       result = transformSons(c, n)
-    elif firstOrd(n.typ) <= firstOrd(n.sons[1].typ) and
-        lastOrd(n.sons[1].typ) <= lastOrd(n.typ):
+    elif firstOrd(c.graph.config, n.typ) <= firstOrd(c.graph.config, n[1].typ) and
+        lastOrd(c.graph.config, n[1].typ) <= lastOrd(c.graph.config, n.typ):
       # BUGFIX: simply leave n as it is; we need a nkConv node,
       # but no range check:
       result = transformSons(c, n)
@@ -420,34 +560,38 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
       else:
         result = newTransNode(nkChckRange, n, 3)
       dest = skipTypes(n.typ, abstractVar)
-      result[0] = transform(c, n.sons[1])
-      result[1] = newIntTypeNode(nkIntLit, firstOrd(dest), dest).PTransNode
-      result[2] = newIntTypeNode(nkIntLit, lastOrd(dest), dest).PTransNode
+      result[0] = transform(c, n[1])
+      result[1] = newIntTypeNode(firstOrd(c.graph.config, dest), dest)
+      result[2] = newIntTypeNode(lastOrd(c.graph.config, dest), dest)
   of tyFloat..tyFloat128:
     # XXX int64 -> float conversion?
     if skipTypes(n.typ, abstractVar).kind == tyRange:
       result = newTransNode(nkChckRangeF, n, 3)
       dest = skipTypes(n.typ, abstractVar)
-      result[0] = transform(c, n.sons[1])
-      result[1] = copyTree(dest.n.sons[0]).PTransNode
-      result[2] = copyTree(dest.n.sons[1]).PTransNode
+      result[0] = transform(c, n[1])
+      result[1] = copyTree(dest.n[0])
+      result[2] = copyTree(dest.n[1])
     else:
       result = transformSons(c, n)
   of tyOpenArray, tyVarargs:
-    result = transform(c, n.sons[1])
-    PNode(result).typ = takeType(n.typ, n.sons[1].typ)
-    #echo n.info, " came here and produced ", typeToString(PNode(result).typ),
-    #   " from ", typeToString(n.typ), " and ", typeToString(n.sons[1].typ)
-  of tyCString:
+    if keepOpenArrayConversions in c.flags:
+      result = transformSons(c, n)
+    else:
+      result = transform(c, n[1])
+      #result = transformSons(c, n)
+      result.typ = takeType(n.typ, n[1].typ, c.graph, c.idgen)
+      #echo n.info, " came here and produced ", typeToString(result.typ),
+      #   " from ", typeToString(n.typ), " and ", typeToString(n[1].typ)
+  of tyCstring:
     if source.kind == tyString:
       result = newTransNode(nkStringToCString, n, 1)
-      result[0] = transform(c, n.sons[1])
+      result[0] = transform(c, n[1])
     else:
       result = transformSons(c, n)
   of tyString:
-    if source.kind == tyCString:
+    if source.kind == tyCstring:
       result = newTransNode(nkCStringToString, n, 1)
-      result[0] = transform(c, n.sons[1])
+      result[0] = transform(c, n[1])
     else:
       result = transformSons(c, n)
   of tyRef, tyPtr:
@@ -457,91 +601,142 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
       var diff = inheritanceDiff(dest, source)
       if diff < 0:
         result = newTransNode(nkObjUpConv, n, 1)
-        result[0] = transform(c, n.sons[1])
+        result[0] = transform(c, n[1])
       elif diff > 0 and diff != high(int):
         result = newTransNode(nkObjDownConv, n, 1)
-        result[0] = transform(c, n.sons[1])
+        result[0] = transform(c, n[1])
       else:
-        result = transform(c, n.sons[1])
+        result = transform(c, n[1])
+        result.typ = n.typ
     else:
       result = transformSons(c, n)
   of tyObject:
     var diff = inheritanceDiff(dest, source)
     if diff < 0:
       result = newTransNode(nkObjUpConv, n, 1)
-      result[0] = transform(c, n.sons[1])
+      result[0] = transform(c, n[1])
     elif diff > 0 and diff != high(int):
       result = newTransNode(nkObjDownConv, n, 1)
-      result[0] = transform(c, n.sons[1])
+      result[0] = transform(c, n[1])
     else:
-      result = transform(c, n.sons[1])
+      result = transform(c, n[1])
+      result.typ = n.typ
   of tyGenericParam, tyOrdinal:
-    result = transform(c, n.sons[1])
+    result = transform(c, n[1])
     # happens sometimes for generated assignments, etc.
   of tyProc:
     result = transformSons(c, n)
-    if dest.callConv == ccClosure and source.callConv == ccDefault:
-      result = generateThunk(result[1].PNode, dest).PTransNode
+    if dest.callConv == ccClosure and source.callConv == ccNimCall:
+      result = generateThunk(c, result[1], dest)
   else:
     result = transformSons(c, n)
 
 type
   TPutArgInto = enum
-    paDirectMapping, paFastAsgn, paVarAsgn, paComplexOpenarray
+    paDirectMapping, paFastAsgn, paFastAsgnTakeTypeFromArg
+    paVarAsgn, paComplexOpenarray, paViaIndirection
 
 proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
   # This analyses how to treat the mapping "formal <-> arg" in an
   # inline context.
+  if formal.kind == tyTypeDesc: return paDirectMapping
   if skipTypes(formal, abstractInst).kind in {tyOpenArray, tyVarargs}:
-    if arg.kind == nkStmtListExpr:
+    case arg.kind
+    of nkStmtListExpr:
       return paComplexOpenarray
-    return paDirectMapping    # XXX really correct?
-                              # what if ``arg`` has side-effects?
+    of nkBracket:
+      return paFastAsgnTakeTypeFromArg
+    else:
+      # XXX incorrect, causes #13417 when `arg` has side effects.
+      return paDirectMapping
   case arg.kind
   of nkEmpty..nkNilLit:
     result = paDirectMapping
-  of nkPar, nkCurly, nkBracket:
-    result = paFastAsgn
-    for i in countup(0, sonsLen(arg) - 1):
-      if putArgInto(arg.sons[i], formal) != paDirectMapping: return
+  of nkDotExpr, nkDerefExpr, nkHiddenDeref:
+    result = putArgInto(arg[0], formal)
+  of nkAddr, nkHiddenAddr:
+    result = putArgInto(arg[0], formal)
+    if result == paViaIndirection: result = paFastAsgn
+  of nkCurly, nkBracket:
+    for i in 0..<arg.len:
+      if putArgInto(arg[i], formal) != paDirectMapping:
+        return paFastAsgn
+    result = paDirectMapping
+  of nkPar, nkTupleConstr, nkObjConstr:
+    for i in 0..<arg.len:
+      let a = if arg[i].kind == nkExprColonExpr: arg[i][1]
+              else: arg[0]
+      if putArgInto(a, formal) != paDirectMapping:
+        return paFastAsgn
     result = paDirectMapping
+  of nkBracketExpr:
+    if skipTypes(formal, abstractInst).kind in {tyVar, tyLent}: result = paVarAsgn
+    else: result = paViaIndirection
   else:
-    if skipTypes(formal, abstractInst).kind == tyVar: result = paVarAsgn
+    if skipTypes(formal, abstractInst).kind in {tyVar, tyLent}: result = paVarAsgn
     else: result = paFastAsgn
 
 proc findWrongOwners(c: PTransf, n: PNode) =
   if n.kind == nkVarSection:
-    let x = n.sons[0].sons[0]
+    let x = n[0][0]
     if x.kind == nkSym and x.sym.owner != getCurrOwner(c):
-      internalError(x.info, "bah " & x.sym.name.s & " " &
+      internalError(c.graph.config, x.info, "bah " & x.sym.name.s & " " &
         x.sym.owner.name.s & " " & getCurrOwner(c).name.s)
   else:
-    for i in 0 .. <safeLen(n): findWrongOwners(c, n.sons[i])
+    for i in 0..<n.safeLen: findWrongOwners(c, n[i])
+
+proc isSimpleIteratorVar(c: PTransf; iter: PSym; call: PNode; owner: PSym): bool =
+  proc rec(n: PNode; owner: PSym; dangerousYields: var int) =
+    case n.kind
+    of nkEmpty..nkNilLit: discard
+    of nkYieldStmt:
+      if n[0].kind == nkSym and n[0].sym.owner == owner:
+        discard "good: yield a single variable that we own"
+      else:
+        inc dangerousYields
+    else:
+      for c in n: rec(c, owner, dangerousYields)
+
+  proc recSym(n: PNode; owner: PSym; sameOwner: var bool) =
+    case n.kind
+    of {nkEmpty..nkNilLit} - {nkSym}: discard
+    of nkSym:
+      if n.sym.owner != owner:
+        sameOwner = false
+    else:
+      for c in n: recSym(c, owner, sameOwner)
+
+  var dangerousYields = 0
+  rec(getBody(c.graph, iter), iter, dangerousYields)
+  result = dangerousYields == 0
+  # the parameters should be owned by the owner
+  # bug #22237
+  for i in 1..<call.len:
+    recSym(call[i], owner, result)
 
-proc transformFor(c: PTransf, n: PNode): PTransNode =
+template destructor(t: PType): PSym = getAttachedOp(c.graph, t, attachedDestructor)
+
+proc transformFor(c: PTransf, n: PNode): PNode =
   # generate access statements for the parameters (unless they are constant)
   # put mapping from formal parameters to actual parameters
-  if n.kind != nkForStmt: internalError(n.info, "transformFor")
+  if n.kind != nkForStmt: internalError(c.graph.config, n.info, "transformFor")
 
-  var length = sonsLen(n)
-  var call = n.sons[length - 2]
+  var call = n[^2]
 
   let labl = newLabel(c, n)
   result = newTransNode(nkBlockStmt, n.info, 2)
-  result[0] = newSymNode(labl).PTransNode
+  result[0] = newSymNode(labl)
   if call.typ.isNil:
     # see bug #3051
-    result[1] = newNode(nkEmpty).PTransNode
+    result[1] = newNode(nkEmpty)
     return result
   c.breakSyms.add(labl)
-  if call.kind notin nkCallKinds or call.sons[0].kind != nkSym or
-      call.sons[0].typ.callConv == ccClosure:
-    n.sons[length-1] = transformLoopBody(c, n.sons[length-1]).PNode
-    if not c.tooEarly:
-      n.sons[length-2] = transform(c, n.sons[length-2]).PNode
-      result[1] = lambdalifting.liftForLoop(n, getCurrOwner(c)).PTransNode
-    else:
-      result[1] = newNode(nkEmpty).PTransNode
+  if call.kind notin nkCallKinds or call[0].kind != nkSym or
+      call[0].typ.skipTypes(abstractInst).callConv == ccClosure:
+    result[1] = n
+    result[1][^1] = transformLoopBody(c, n[^1])
+    result[1][^2] = transform(c, n[^2])
+    result[1] = lambdalifting.liftForLoop(c.graph, result[1], c.idgen, getCurrOwner(c))
     discard c.breakSyms.pop
     return result
 
@@ -549,18 +744,26 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
   var stmtList = newTransNode(nkStmtList, n.info, 0)
   result[1] = stmtList
 
-  var loopBody = transformLoopBody(c, n.sons[length-1])
+  var loopBody = transformLoopBody(c, n[^1])
 
   discard c.breakSyms.pop
 
+  let iter = call[0].sym
+
   var v = newNodeI(nkVarSection, n.info)
-  for i in countup(0, length - 3):
-    addVar(v, copyTree(n.sons[i])) # declare new vars
-  add(stmtList, v.PTransNode)
+  for i in 0..<n.len - 2:
+    if n[i].kind == nkVarTuple:
+      for j in 0..<n[i].len-1:
+        addVar(v, copyTree(n[i][j])) # declare new vars
+    else:
+      if n[i].kind == nkSym and isSimpleIteratorVar(c, iter, call, n[i].sym.owner):
+        incl n[i].sym.flags, sfCursor
+      addVar(v, copyTree(n[i])) # declare new vars
+  stmtList.add(v)
+
 
   # Bugfix: inlined locals belong to the invoking routine, not to the invoked
   # iterator!
-  let iter = call.sons[0].sym
   var newC = newTransCon(getCurrOwner(c))
   newC.forStmt = n
   newC.forLoopBody = loopBody
@@ -568,217 +771,246 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
   if iter.kind != skIterator: return result
   # generate access statements for the parameters (unless they are constant)
   pushTransCon(c, newC)
-  for i in countup(1, sonsLen(call) - 1):
-    var arg = transform(c, call.sons[i]).PNode
+  for i in 1..<call.len:
+    var arg = transform(c, call[i])
     let ff = skipTypes(iter.typ, abstractInst)
     # can happen for 'nim check':
     if i >= ff.n.len: return result
-    var formal = ff.n.sons[i].sym
-    case putArgInto(arg, formal.typ)
+    var formal = ff.n[i].sym
+    let pa = putArgInto(arg, formal.typ)
+    case pa
     of paDirectMapping:
-      idNodeTablePut(newC.mapping, formal, arg)
-    of paFastAsgn:
+      newC.mapping[formal.itemId] = arg
+    of paFastAsgn, paFastAsgnTakeTypeFromArg:
+      var t = formal.typ
+      if pa == paFastAsgnTakeTypeFromArg:
+        t = arg.typ
+      elif formal.ast != nil and formal.ast.typ.destructor != nil and t.destructor == nil:
+        t = formal.ast.typ # better use the type that actually has a destructor.
+      elif t.destructor == nil and arg.typ.destructor != nil:
+        t = arg.typ
       # generate a temporary and produce an assignment statement:
-      var temp = newTemp(c, formal.typ, formal.info)
+      var temp = newTemp(c, t, formal.info)
+      #incl(temp.sym.flags, sfCursor)
       addVar(v, temp)
-      add(stmtList, newAsgnStmt(c, temp, arg.PTransNode))
-      idNodeTablePut(newC.mapping, formal, temp)
+      stmtList.add(newAsgnStmt(c, nkFastAsgn, temp, arg, true))
+      newC.mapping[formal.itemId] = temp
     of paVarAsgn:
-      assert(skipTypes(formal.typ, abstractInst).kind == tyVar)
-      idNodeTablePut(newC.mapping, formal, arg)
+      assert(skipTypes(formal.typ, abstractInst).kind in {tyVar, tyLent})
+      newC.mapping[formal.itemId] = arg
       # XXX BUG still not correct if the arg has a side effect!
+    of paViaIndirection:
+      let t = formal.typ
+      let vt = makeVarType(t.owner, t, c.idgen)
+      vt.flags.incl tfVarIsPtr
+      var temp = newTemp(c, vt, formal.info)
+      addVar(v, temp)
+      var addrExp = newNodeIT(nkHiddenAddr, formal.info, makeVarType(t.owner, t, c.idgen, tyPtr))
+      addrExp.add(arg)
+      stmtList.add(newAsgnStmt(c, nkFastAsgn, temp, addrExp, true))
+      newC.mapping[formal.itemId] = newDeref(temp)
     of paComplexOpenarray:
-      let typ = newType(tySequence, formal.owner)
-      addSonSkipIntLit(typ, formal.typ.sons[0])
-      var temp = newTemp(c, typ, formal.info)
+      # arrays will deep copy here (pretty bad).
+      var temp = newTemp(c, arg.typ, formal.info)
       addVar(v, temp)
-      add(stmtList, newAsgnStmt(c, temp, arg.PTransNode))
-      idNodeTablePut(newC.mapping, formal, temp)
-
-  var body = iter.getBody.copyTree
-  pushInfoContext(n.info)
-  # XXX optimize this somehow. But the check "c.inlining" is not correct:
-  var symMap: TIdTable
-  initIdTable symMap
-  freshLabels(c, body, symMap)
+      stmtList.add(newAsgnStmt(c, nkFastAsgn, temp, arg, true))
+      newC.mapping[formal.itemId] = temp
 
+  let body = transformBody(c.graph, c.idgen, iter, {useCache}+c.flags)
+  pushInfoContext(c.graph.config, n.info)
   inc(c.inlining)
-  add(stmtList, transform(c, body))
-  #findWrongOwners(c, stmtList.pnode)
+  stmtList.add(transform(c, body))
+  #findWrongOwners(c, stmtList.PNode)
   dec(c.inlining)
-  popInfoContext()
+  popInfoContext(c.graph.config)
   popTransCon(c)
-  # echo "transformed: ", stmtList.PNode.renderTree
+  # echo "transformed: ", stmtList.renderTree
 
-proc transformCase(c: PTransf, n: PNode): PTransNode =
+proc transformCase(c: PTransf, n: PNode): PNode =
   # removes `elif` branches of a case stmt
   # adds ``else: nil`` if needed for the code generator
   result = newTransNode(nkCaseStmt, n, 0)
-  var ifs = PTransNode(nil)
-  for i in 0 .. sonsLen(n)-1:
-    var it = n.sons[i]
+  var ifs: PNode = nil
+  for it in n:
     var e = transform(c, it)
     case it.kind
     of nkElifBranch:
-      if ifs.PNode == nil:
-        ifs = newTransNode(nkIfStmt, it.info, 0)
+      if ifs == nil:
+        # Generate the right node depending on whether `n` is used as a stmt or
+        # as an expr
+        let kind = if n.typ != nil: nkIfExpr else: nkIfStmt
+        ifs = newTransNode(kind, it.info, 0)
+        ifs.typ = n.typ
       ifs.add(e)
     of nkElse:
-      if ifs.PNode == nil: result.add(e)
+      if ifs == nil: result.add(e)
       else: ifs.add(e)
     else:
       result.add(e)
-  if ifs.PNode != nil:
+  if ifs != nil:
     var elseBranch = newTransNode(nkElse, n.info, 1)
     elseBranch[0] = ifs
     result.add(elseBranch)
-  elif result.PNode.lastSon.kind != nkElse and not (
-      skipTypes(n.sons[0].typ, abstractVarRange).kind in
-        {tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32}):
+  elif result.lastSon.kind != nkElse and not (
+      skipTypes(n[0].typ, abstractVarRange).kind in
+        {tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt64}):
     # fix a stupid code gen bug by normalizing:
     var elseBranch = newTransNode(nkElse, n.info, 1)
     elseBranch[0] = newTransNode(nkNilLit, n.info, 0)
-    add(result, elseBranch)
+    result.add(elseBranch)
 
-proc transformArrayAccess(c: PTransf, n: PNode): PTransNode =
+proc transformArrayAccess(c: PTransf, n: PNode): PNode =
   # XXX this is really bad; transf should use a proper AST visitor
-  if n.sons[0].kind == nkSym and n.sons[0].sym.kind == skType:
-    result = n.PTransNode
+  if n[0].kind == nkSym and n[0].sym.kind == skType:
+    result = n
   else:
     result = newTransNode(n)
-    for i in 0 .. < n.len:
-      result[i] = transform(c, skipConv(n.sons[i]))
+    for i in 0..<n.len:
+      result[i] = transform(c, skipConv(n[i]))
 
 proc getMergeOp(n: PNode): PSym =
   case n.kind
   of nkCall, nkHiddenCallConv, nkCommand, nkInfix, nkPrefix, nkPostfix,
      nkCallStrLit:
-    if n.sons[0].kind == nkSym and n.sons[0].sym.magic == mConStrStr:
-      result = n.sons[0].sym
-  else: discard
+    if n[0].kind == nkSym and n[0].sym.magic == mConStrStr:
+      result = n[0].sym
+    else:
+      result = nil
+  else: result = nil
 
 proc flattenTreeAux(d, a: PNode, op: PSym) =
+  ## Optimizes away the `&` calls in the children nodes and
+  ## lifts the leaf nodes to the same level as `op2`.
   let op2 = getMergeOp(a)
   if op2 != nil and
       (op2.id == op.id or op.magic != mNone and op2.magic == op.magic):
-    for i in countup(1, sonsLen(a)-1): flattenTreeAux(d, a.sons[i], op)
+    for i in 1..<a.len: flattenTreeAux(d, a[i], op)
   else:
-    addSon(d, copyTree(a))
+    d.add copyTree(a)
 
 proc flattenTree(root: PNode): PNode =
   let op = getMergeOp(root)
   if op != nil:
     result = copyNode(root)
-    addSon(result, copyTree(root.sons[0]))
+    result.add copyTree(root[0])
     flattenTreeAux(result, root, op)
   else:
     result = root
 
-proc transformCall(c: PTransf, n: PNode): PTransNode =
+proc transformCall(c: PTransf, n: PNode): PNode =
   var n = flattenTree(n)
   let op = getMergeOp(n)
   let magic = getMagic(n)
   if op != nil and op.magic != mNone and n.len >= 3:
     result = newTransNode(nkCall, n, 0)
-    add(result, transform(c, n.sons[0]))
+    result.add(transform(c, n[0]))
     var j = 1
-    while j < sonsLen(n):
-      var a = transform(c, n.sons[j]).PNode
+    while j < n.len:
+      var a = transform(c, n[j])
       inc(j)
       if isConstExpr(a):
-        while (j < sonsLen(n)):
-          let b = transform(c, n.sons[j]).PNode
+        while (j < n.len):
+          let b = transform(c, n[j])
           if not isConstExpr(b): break
-          a = evalOp(op.magic, n, a, b, nil)
+          a = evalOp(op.magic, n, a, b, nil, c.idgen, c.graph)
           inc(j)
-      add(result, a.PTransNode)
-    if len(result) == 2: result = result[1]
-  elif magic in {mNBindSym, mTypeOf}:
+      result.add(a)
+    if result.len == 2: result = result[1]
+  elif magic in {mNBindSym, mTypeOf, mRunnableExamples}:
     # for bindSym(myconst) we MUST NOT perform constant folding:
-    result = n.PTransNode
+    result = n
   elif magic == mProcCall:
     # but do not change to its dispatcher:
     result = transformSons(c, n[1])
+  elif magic == mStrToStr:
+    result = transform(c, n[1])
   else:
-    let s = transformSons(c, n).PNode
+    let s = transformSons(c, n)
     # bugfix: check after 'transformSons' if it's still a method call:
     # use the dispatcher for the call:
-    if s.sons[0].kind == nkSym and s.sons[0].sym.kind == skMethod:
+    if s[0].kind == nkSym and s[0].sym.kind == skMethod:
       when false:
-        let t = lastSon(s.sons[0].sym.ast)
+        let t = lastSon(s[0].sym.ast)
         if t.kind != nkSym or sfDispatcher notin t.sym.flags:
-          methodDef(s.sons[0].sym, false)
-      result = methodCall(s).PTransNode
+          methodDef(s[0].sym, false)
+      result = methodCall(s, c.graph.config)
     else:
-      result = s.PTransNode
+      result = s
 
-proc transformExceptBranch(c: PTransf, n: PNode): PTransNode =
-  result = transformSons(c, n)
-  if n[0].isInfixAs():
+proc transformExceptBranch(c: PTransf, n: PNode): PNode =
+  if n[0].isInfixAs() and not isImportedException(n[0][1].typ, c.graph.config):
     let excTypeNode = n[0][1]
-    let actions = newTransNode(nkStmtList, n[1].info, 2)
+    let actions = newTransNode(nkStmtListExpr, n[1], 2)
     # Generating `let exc = (excType)(getCurrentException())`
     # -> getCurrentException()
-    let excCall = PTransNode(callCodegenProc("getCurrentException", ast.emptyNode))
+    let excCall = callCodegenProc(c.graph, "getCurrentException")
     # -> (excType)
     let convNode = newTransNode(nkHiddenSubConv, n[1].info, 2)
-    convNode[0] = PTransNode(ast.emptyNode)
+    convNode[0] = newNodeI(nkEmpty, n.info)
     convNode[1] = excCall
-    PNode(convNode).typ = excTypeNode.typ.toRef()
+    convNode.typ = excTypeNode.typ.toRef(c.idgen)
     # -> let exc = ...
     let identDefs = newTransNode(nkIdentDefs, n[1].info, 3)
-    identDefs[0] = PTransNode(n[0][2])
-    identDefs[1] = PTransNode(ast.emptyNode)
+    identDefs[0] = n[0][2]
+    identDefs[1] = newNodeI(nkEmpty, n.info)
     identDefs[2] = convNode
 
     let letSection = newTransNode(nkLetSection, n[1].info, 1)
     letSection[0] = identDefs
     # Place the let statement and body of the 'except' branch into new stmtList.
     actions[0] = letSection
-    actions[1] = transformSons(c, n[1])
+    actions[1] = transform(c, n[1])
     # Overwrite 'except' branch body with our stmtList.
-    result[1] = actions
-
+    result = newTransNode(nkExceptBranch, n[1].info, 2)
     # Replace the `Exception as foobar` with just `Exception`.
-    result[0] = result[0][1]
-
-proc dontInlineConstant(orig, cnst: PNode): bool {.inline.} =
-  # symbols that expand to a complex constant (array, etc.) should not be
-  # inlined, unless it's the empty array:
-  result = orig.kind == nkSym and cnst.kind in {nkCurly, nkPar, nkBracket} and
-      cnst.len != 0
+    result[0] = transform(c, n[0][1])
+    result[1] = actions
+  else:
+    result = transformSons(c, n)
 
-proc commonOptimizations*(c: PSym, n: PNode): PNode =
+proc commonOptimizations*(g: ModuleGraph; idgen: IdGenerator; c: PSym, n: PNode): PNode =
+  ## Merges adjacent constant expressions of the children of the `&` call into
+  ## a single constant expression. It also inlines constant expressions which are not
+  ## complex.
   result = n
-  for i in 0 .. < n.safeLen:
-    result.sons[i] = commonOptimizations(c, n.sons[i])
+  for i in 0..<n.safeLen:
+    result[i] = commonOptimizations(g, idgen, c, n[i])
   var op = getMergeOp(n)
-  if (op != nil) and (op.magic != mNone) and (sonsLen(n) >= 3):
+  if (op != nil) and (op.magic != mNone) and (n.len >= 3):
     result = newNodeIT(nkCall, n.info, n.typ)
-    add(result, n.sons[0])
+    result.add(n[0])
     var args = newNode(nkArgList)
     flattenTreeAux(args, n, op)
     var j = 0
-    while j < sonsLen(args):
-      var a = args.sons[j]
+    while j < args.len:
+      var a = args[j]
       inc(j)
       if isConstExpr(a):
-        while j < sonsLen(args):
-          let b = args.sons[j]
+        while j < args.len:
+          let b = args[j]
           if not isConstExpr(b): break
-          a = evalOp(op.magic, result, a, b, nil)
+          a = evalOp(op.magic, result, a, b, nil, idgen, g)
           inc(j)
-      add(result, a)
-    if len(result) == 2: result = result[1]
+      result.add(a)
+    if result.len == 2: result = result[1]
   else:
-    var cnst = getConstExpr(c, n)
+    var cnst = getConstExpr(c, n, idgen, g)
     # we inline constants if they are not complex constants:
     if cnst != nil and not dontInlineConstant(n, cnst):
       result = cnst
     else:
       result = n
 
-proc transform(c: PTransf, n: PNode): PTransNode =
+proc transformDerefBlock(c: PTransf, n: PNode): PNode =
+  # We transform (block: x)[] to (block: x[])
+  let e0 = n[0]
+  result = shallowCopy(e0)
+  result.typ = n.typ
+  for i in 0 ..< e0.len - 1:
+    result[i] = e0[i]
+  result[e0.len-1] = newTreeIT(nkHiddenDeref, n.info, n.typ, e0[e0.len-1])
+
+proc transform(c: PTransf, n: PNode): PNode =
   when false:
     var oldDeferAnchor: PNode
     if n.kind in {nkElifBranch, nkOfBranch, nkExceptBranch, nkElifExpr,
@@ -786,31 +1018,29 @@ proc transform(c: PTransf, n: PNode): PTransNode =
                   nkBlockStmt, nkBlockExpr}:
       oldDeferAnchor = c.deferAnchor
       c.deferAnchor = n
-  if n.typ != nil and tfHasAsgn in n.typ.flags:
-    c.needsDestroyPass = true
   case n.kind
   of nkSym:
     result = transformSym(c, n)
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
+  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkComesFrom:
     # nothing to be done for leaves:
-    result = PTransNode(n)
+    result = n
   of nkBracketExpr: result = transformArrayAccess(c, n)
   of procDefs:
-    var s = n.sons[namePos].sym
+    var s = n[namePos].sym
     if n.typ != nil and s.typ.callConv == ccClosure:
-      result = transformSym(c, n.sons[namePos])
+      result = transformSym(c, n[namePos])
       # use the same node as before if still a symbol:
-      if result.PNode.kind == nkSym: result = PTransNode(n)
+      if result.kind == nkSym: result = n
     else:
-      result = PTransNode(n)
+      result = n
   of nkMacroDef:
     # XXX no proper closure support yet:
     when false:
-      if n.sons[genericParamsPos].kind == nkEmpty:
-        var s = n.sons[namePos].sym
-        n.sons[bodyPos] = PNode(transform(c, s.getBody))
+      if n[genericParamsPos].kind == nkEmpty:
+        var s = n[namePos].sym
+        n[bodyPos] = transform(c, s.getBody)
         if n.kind == nkMethodDef: methodDef(s, false)
-    result = PTransNode(n)
+    result = n
   of nkForStmt:
     result = transformFor(c, n)
   of nkParForStmt:
@@ -825,50 +1055,62 @@ proc transform(c: PTransf, n: PNode): PTransNode =
     result = transformSons(c, n)
     when false:
       let deferPart = newNodeI(nkFinally, n.info)
-      deferPart.add n.sons[0]
+      deferPart.add n[0]
       let tryStmt = newNodeI(nkTryStmt, n.info)
       if c.deferAnchor.isNil:
         tryStmt.add c.root
         c.root = tryStmt
-        result = PTransNode(tryStmt)
+        result = tryStmt
       else:
         # modify the corresponding *action*, don't rely on nkStmtList:
-        let L = c.deferAnchor.len-1
-        tryStmt.add c.deferAnchor.sons[L]
-        c.deferAnchor.sons[L] = tryStmt
+        tryStmt.add c.deferAnchor[^1]
+        c.deferAnchor[^1] = tryStmt
         result = newTransNode(nkCommentStmt, n.info, 0)
-      tryStmt.addSon(deferPart)
+      tryStmt.add deferPart
       # disable the original 'defer' statement:
       n.kind = nkEmpty
   of nkContinueStmt:
-    result = PTransNode(newNodeI(nkBreakStmt, n.info))
+    result = newNodeI(nkBreakStmt, n.info)
     var labl = c.contSyms[c.contSyms.high]
-    add(result, PTransNode(newSymNode(labl)))
+    result.add(newSymNode(labl))
   of nkBreakStmt: result = transformBreak(c, n)
   of nkCallKinds:
     result = transformCall(c, n)
-  of nkAddr, nkHiddenAddr:
-    result = transformAddrDeref(c, n, nkDerefExpr, nkHiddenDeref)
-  of nkDerefExpr, nkHiddenDeref:
-    result = transformAddrDeref(c, n, nkAddr, nkHiddenAddr)
+  of nkHiddenAddr:
+    result = transformAddrDeref(c, n, {nkHiddenDeref})
+  of nkAddr:
+    let oldInAddr = c.inAddr
+    c.inAddr = true
+    result = transformAddrDeref(c, n, {nkDerefExpr, nkHiddenDeref})
+    c.inAddr = oldInAddr
+  of nkDerefExpr:
+    result = transformAddrDeref(c, n, {nkAddr, nkHiddenAddr})
+  of nkHiddenDeref:
+    if n[0].kind in {nkBlockExpr, nkBlockStmt}:
+      # bug #20107 bug #21540. Watch out to not deref the pointer too late.
+      let e = transformDerefBlock(c, n)
+      result = transformBlock(c, e)
+    else:
+      result = transformAddrDeref(c, n, {nkAddr, nkHiddenAddr})
   of nkHiddenStdConv, nkHiddenSubConv, nkConv:
     result = transformConv(c, n)
   of nkDiscardStmt:
-    result = PTransNode(n)
-    if n.sons[0].kind != nkEmpty:
+    result = n
+    if n[0].kind != nkEmpty:
       result = transformSons(c, n)
-      if isConstExpr(PNode(result).sons[0]):
+      if isConstExpr(result[0]):
         # ensure that e.g. discard "some comment" gets optimized away
         # completely:
-        result = PTransNode(newNode(nkCommentStmt))
-  of nkCommentStmt, nkTemplateDef:
-    return n.PTransNode
+        result = newNode(nkCommentStmt)
+  of nkCommentStmt, nkTemplateDef, nkImportStmt, nkStaticStmt,
+      nkExportStmt, nkExportExceptStmt:
+    return n
   of nkConstSection:
     # do not replace ``const c = 3`` with ``const 3 = 3``
     return transformConstSection(c, n)
-  of nkTypeSection, nkTypeOfExpr:
+  of nkTypeSection, nkTypeOfExpr, nkMixinStmt, nkBindStmt:
     # no need to transform type sections:
-    return PTransNode(n)
+    return n
   of nkVarSection, nkLetSection:
     if c.inlining > 0:
       # we need to copy the variables for multiple yield statements:
@@ -876,55 +1118,68 @@ proc transform(c: PTransf, n: PNode): PTransNode =
     else:
       result = transformSons(c, n)
   of nkYieldStmt:
-    if c.inlining > 0:
+    if c.inlining > 0 and not c.isIntroducingNewLocalVars:
       result = transformYield(c, n)
     else:
       result = transformSons(c, n)
+  of nkAsgn:
+    result = transformAsgn(c, n)
   of nkIdentDefs, nkConstDef:
-    when true:
-      result = transformSons(c, n)
-    else:
-      result = n.PTransNode
-      let L = n.len-1
-      result[L] = transform(c, n.sons[L])
+    result = newTransNode(n)
+    result[0] = transform(c, skipPragmaExpr(n[0]))
+    # Skip the second son since it only contains an unsemanticized copy of the
+    # variable type used by docgen
+    let last = n.len-1
+    for i in 1..<last: result[i] = n[i]
+    result[last] = transform(c, n[last])
     # XXX comment handling really sucks:
-    if importantComments():
-      PNode(result).comment = n.comment
+    if importantComments(c.graph.config):
+      result.comment = n.comment
   of nkClosure:
     # it can happen that for-loop-inlining produced a fresh
     # set of variables, including some computed environment
     # (bug #2604). We need to patch this environment here too:
     let a = n[1]
     if a.kind == nkSym:
-      n.sons[1] = transformSymAux(c, a)
-    return PTransNode(n)
+      result = copyTree(n)
+      result[1] = transformSymAux(c, a)
+    else:
+      result = n
   of nkExceptBranch:
     result = transformExceptBranch(c, n)
+  of nkCheckedFieldExpr:
+    result = transformSons(c, n)
+    if result[0].kind != nkDotExpr:
+      # simplfied beyond a dot expression --> simplify further.
+      result = result[0]
   else:
     result = transformSons(c, n)
   when false:
     if oldDeferAnchor != nil: c.deferAnchor = oldDeferAnchor
 
-  var cnst = getConstExpr(c.module, PNode(result))
-  # we inline constants if they are not complex constants:
-  if cnst != nil and not dontInlineConstant(n, cnst):
-    result = PTransNode(cnst) # do not miss an optimization
+  # Constants can be inlined here, but only if they cannot result in a cast
+  # in the back-end (e.g. var p: pointer = someProc)
+  let exprIsPointerCast = n.kind in {nkCast, nkConv, nkHiddenStdConv} and
+                          n.typ != nil and
+                          n.typ.kind == tyPointer
+  if not exprIsPointerCast and not c.inAddr:
+    var cnst = getConstExpr(c.module, result, c.idgen, c.graph)
+    # we inline constants if they are not complex constants:
+    if cnst != nil and not dontInlineConstant(n, cnst):
+      result = cnst # do not miss an optimization
 
 proc processTransf(c: PTransf, n: PNode, owner: PSym): PNode =
   # Note: For interactive mode we cannot call 'passes.skipCodegen' and skip
   # this step! We have to rely that the semantic pass transforms too errornous
   # nodes into an empty node.
-  if c.fromCache or nfTransf in n.flags: return n
+  if nfTransf in n.flags: return n
   pushTransCon(c, newTransCon(owner))
-  result = PNode(transform(c, n))
+  result = transform(c, n)
   popTransCon(c)
   incl(result.flags, nfTransf)
 
-proc openTransf(module: PSym, filename: string): PTransf =
-  new(result)
-  result.contSyms = @[]
-  result.breakSyms = @[]
-  result.module = module
+proc openTransf(g: ModuleGraph; module: PSym, filename: string; idgen: IdGenerator; flags: TransformFlags): PTransf =
+  result = PTransf(module: module, graph: g, idgen: idgen, flags: flags)
 
 proc flattenStmts(n: PNode) =
   var goOn = true
@@ -946,66 +1201,75 @@ proc liftDeferAux(n: PNode) =
       goOn = false
       let last = n.len-1
       for i in 0..last:
-        if n.sons[i].kind == nkDefer:
-          let deferPart = newNodeI(nkFinally, n.sons[i].info)
-          deferPart.add n.sons[i].sons[0]
-          var tryStmt = newNodeI(nkTryStmt, n.sons[i].info)
-          var body = newNodeI(n.kind, n.sons[i].info)
+        if n[i].kind == nkDefer:
+          let deferPart = newNodeI(nkFinally, n[i].info)
+          deferPart.add n[i][0]
+          var tryStmt = newNodeIT(nkTryStmt, n[i].info, n.typ)
+          var body = newNodeIT(n.kind, n[i].info, n.typ)
           if i < last:
             body.sons = n.sons[(i+1)..last]
-          tryStmt.addSon(body)
-          tryStmt.addSon(deferPart)
-          n.sons[i] = tryStmt
+          tryStmt.add body
+          tryStmt.add deferPart
+          n[i] = tryStmt
           n.sons.setLen(i+1)
-          n.typ = n.sons[i].typ
+          n.typ = tryStmt.typ
           goOn = true
           break
   for i in 0..n.safeLen-1:
-    liftDeferAux(n.sons[i])
+    liftDeferAux(n[i])
 
 template liftDefer(c, root) =
   if c.deferDetected:
     liftDeferAux(root)
 
-proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
-  if nfTransf in n.flags or prc.kind in {skTemplate}:
-    result = n
+proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: TransformFlags): PNode =
+  assert prc.kind in routineKinds
+
+  if prc.transformedBody != nil:
+    result = prc.transformedBody
+  elif nfTransf in getBody(g, prc).flags or prc.kind in {skTemplate}:
+    result = getBody(g, prc)
   else:
-    var c = openTransf(module, "")
-    result = liftLambdas(prc, n, c.tooEarly)
-    #result = n
+    prc.transformedBody = newNode(nkEmpty) # protects from recursion
+    var c = openTransf(g, prc.getModule, "", idgen, flags)
+    result = liftLambdas(g, prc, getBody(g, prc), c.tooEarly, c.idgen, flags)
     result = processTransf(c, result, prc)
     liftDefer(c, result)
-    #result = liftLambdas(prc, result)
-    when useEffectSystem: trackProc(prc, result)
-    if c.needsDestroyPass and newDestructors:
-      result = injectDestructorCalls(prc, result)
+    result = liftLocalsIfRequested(prc, result, g.cache, g.config, c.idgen)
+
+    if prc.isIterator:
+      result = g.transformClosureIterator(c.idgen, prc, result)
+
     incl(result.flags, nfTransf)
-      #if prc.name.s == "testbody":
-    #  echo renderTree(result)
 
-proc transformStmt*(module: PSym, n: PNode): PNode =
+    if useCache in flags or prc.typ.callConv == ccInline:
+      # genProc for inline procs will be called multiple times from different modules,
+      # it is important to transform exactly once to get sym ids and locations right
+      prc.transformedBody = result
+    else:
+      prc.transformedBody = nil
+    # XXX Rodfile support for transformedBody!
+
+  #if prc.name.s == "main":
+  #  echo "transformed into ", renderTree(result, {renderIds})
+
+proc transformStmt*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode; flags: TransformFlags = {}): PNode =
   if nfTransf in n.flags:
     result = n
   else:
-    var c = openTransf(module, "")
+    var c = openTransf(g, module, "", idgen, flags)
     result = processTransf(c, n, module)
     liftDefer(c, result)
     #result = liftLambdasForTopLevel(module, result)
-    when useEffectSystem: trackTopLevelStmt(module, result)
-    #if n.info ?? "temp.nim":
-    #  echo renderTree(result, {renderIds})
-    if c.needsDestroyPass and newDestructors:
-      result = injectDestructorCalls(module, result)
     incl(result.flags, nfTransf)
 
-proc transformExpr*(module: PSym, n: PNode): PNode =
+proc transformExpr*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode; flags: TransformFlags = {}): PNode =
   if nfTransf in n.flags:
     result = n
   else:
-    var c = openTransf(module, "")
+    var c = openTransf(g, module, "", idgen, flags)
     result = processTransf(c, n, module)
     liftDefer(c, result)
-    if c.needsDestroyPass and newDestructors:
-      result = injectDestructorCalls(module, result)
+    # expressions are not to be injected with destructor calls as that
+    # the list of top level statements needs to be collected before.
     incl(result.flags, nfTransf)