summary refs log tree commit diff stats
path: root/compiler/semstmts.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/semstmts.nim')
-rw-r--r--compiler/semstmts.nim2966
1 files changed, 1991 insertions, 975 deletions
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 292238dc9..f5f8fea0c 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -12,7 +12,7 @@
 
 const
   errNoSymbolToBorrowFromFound = "no symbol to borrow from found"
-  errDiscardValueX = "value of type '$1' has to be discarded"
+  errDiscardValueX = "value of type '$1' has to be used (or discarded)"
   errInvalidDiscard = "statement returns no value that can be discarded"
   errInvalidControlFlowX = "invalid control flow: $1"
   errSelectorMustBeOfCertainTypes = "selector must be of an ordinal type, float or string"
@@ -27,314 +27,441 @@ const
     "it is used as an operand to another routine and the types " &
     "of the generic paramers can be inferred from the expected signature."
   errCannotInferTypeOfTheLiteral = "cannot infer the type of the $1"
-  errCannotInferReturnType = "cannot infer the return type of the proc"
+  errCannotInferReturnType = "cannot infer the return type of '$1'"
   errCannotInferStaticParam = "cannot infer the value of the static param '$1'"
   errProcHasNoConcreteType = "'$1' doesn't have a concrete type, due to unspecified generic parameters."
   errLetNeedsInit = "'let' symbol requires an initialization"
   errThreadvarCannotInit = "a thread var cannot be initialized explicitly; this would only run for the main thread"
   errImplOfXexpected = "implementation of '$1' expected"
   errRecursiveDependencyX = "recursive dependency: '$1'"
+  errRecursiveDependencyIteratorX = "recursion is not supported in iterators: '$1'"
   errPragmaOnlyInHeaderOfProcX = "pragmas are only allowed in the header of a proc; redefinition of $1"
+  errCannotAssignToGlobal = "cannot assign local to global variable"
+
+proc implicitlyDiscardable(n: PNode): bool
+
+proc hasEmpty(typ: PType): bool =
+  if typ.kind in {tySequence, tyArray, tySet}:
+    result = typ.elementType.kind == tyEmpty
+  elif typ.kind == tyTuple:
+    result = false
+    for s in typ.kids:
+      result = result or hasEmpty(s)
+  else:
+    result = false
 
 proc semDiscard(c: PContext, n: PNode): PNode =
   result = n
   checkSonsLen(n, 1, c.config)
-  if n.sons[0].kind != nkEmpty:
-    n.sons[0] = semExprWithType(c, n.sons[0])
-    if isEmptyType(n.sons[0].typ) or n.sons[0].typ.kind == tyNone or n.sons[0].kind == nkTypeOfExpr:
+  if n[0].kind != nkEmpty:
+    n[0] = semExprWithType(c, n[0])
+    let sonType = n[0].typ
+    let sonKind = n[0].kind
+    if isEmptyType(sonType) or hasEmpty(sonType) or
+          sonType.kind in {tyNone, tyTypeDesc} or
+          sonKind == nkTypeOfExpr:
       localError(c.config, n.info, errInvalidDiscard)
+    if sonType.kind == tyProc and sonKind notin nkCallKinds:
+      # tyProc is disallowed to prevent ``discard foo`` to be valid, when ``discard foo()`` is meant.
+      localError(c.config, n.info, "illegal discard proc, did you mean: " & $n[0] & "()")
 
 proc semBreakOrContinue(c: PContext, n: PNode): PNode =
   result = n
   checkSonsLen(n, 1, c.config)
-  if n.sons[0].kind != nkEmpty:
+  if n[0].kind != nkEmpty:
     if n.kind != nkContinueStmt:
-      var s: PSym
-      case n.sons[0].kind
-      of nkIdent: s = lookUp(c, n.sons[0])
-      of nkSym: s = n.sons[0].sym
+      var s: PSym = nil
+      case n[0].kind
+      of nkIdent: s = lookUp(c, n[0])
+      of nkSym: s = n[0].sym
       else: illFormedAst(n, c.config)
       s = getGenSym(c, s)
       if s.kind == skLabel and s.owner.id == c.p.owner.id:
         var x = newSymNode(s)
         x.info = n.info
         incl(s.flags, sfUsed)
-        n.sons[0] = x
-        suggestSym(c.config, x.info, s, c.graph.usageSym)
-        styleCheckUse(x.info, s)
+        n[0] = x
+        suggestSym(c.graph, x.info, s, c.graph.usageSym)
+        onUse(x.info, s)
       else:
         localError(c.config, n.info, errInvalidControlFlowX % s.name.s)
     else:
       localError(c.config, n.info, errGenerated, "'continue' cannot have a label")
-  elif (c.p.nestedLoopCounter <= 0) and (c.p.nestedBlockCounter <= 0):
+  elif c.p.nestedBlockCounter > 0 and n.kind == nkBreakStmt and not c.p.breakInLoop:
+    localError(c.config, n.info, warnUnnamedBreak)
+  elif (c.p.nestedLoopCounter <= 0) and ((c.p.nestedBlockCounter <= 0) or n.kind == nkContinueStmt):
     localError(c.config, n.info, errInvalidControlFlowX %
                renderTree(n, {renderNoComments}))
 
 proc semAsm(c: PContext, n: PNode): PNode =
   checkSonsLen(n, 2, c.config)
-  var marker = pragmaAsm(c, n.sons[0])
+  var marker = pragmaAsm(c, n[0])
   if marker == '\0': marker = '`' # default marker
   result = semAsmOrEmit(c, n, marker)
 
-proc semWhile(c: PContext, n: PNode): PNode =
+proc semWhile(c: PContext, n: PNode; flags: TExprFlags): PNode =
   result = n
   checkSonsLen(n, 2, c.config)
   openScope(c)
-  n.sons[0] = forceBool(c, semExprWithType(c, n.sons[0]))
+  n[0] = forceBool(c, semExprWithType(c, n[0], expectedType = getSysType(c.graph, n.info, tyBool)))
   inc(c.p.nestedLoopCounter)
-  n.sons[1] = semStmt(c, n.sons[1])
+  let oldBreakInLoop = c.p.breakInLoop
+  c.p.breakInLoop = true
+  n[1] = semStmt(c, n[1], flags)
+  c.p.breakInLoop = oldBreakInLoop
   dec(c.p.nestedLoopCounter)
   closeScope(c)
-  if n.sons[1].typ == c.enforceVoidContext:
+  if n[1].typ == c.enforceVoidContext:
     result.typ = c.enforceVoidContext
-
-proc toCover(c: PContext, t: PType): BiggestInt =
-  let t2 = skipTypes(t, abstractVarRange-{tyTypeDesc})
-  if t2.kind == tyEnum and enumHasHoles(t2):
-    result = sonsLen(t2.n)
-  else:
-    result = lengthOrd(c.config, skipTypes(t, abstractVar-{tyTypeDesc}))
+  elif efInTypeof in flags:
+    result.typ = n[1].typ
+  elif implicitlyDiscardable(n[1]):
+    result[1].typ = c.enforceVoidContext
 
 proc semProc(c: PContext, n: PNode): PNode
 
-proc semExprBranch(c: PContext, n: PNode): PNode =
-  result = semExpr(c, n)
+proc semExprBranch(c: PContext, n: PNode; flags: TExprFlags = {}; expectedType: PType = nil): PNode =
+  result = semExpr(c, n, flags, expectedType)
   if result.typ != nil:
     # XXX tyGenericInst here?
     if result.typ.kind in {tyVar, tyLent}: result = newDeref(result)
 
-proc semExprBranchScope(c: PContext, n: PNode): PNode =
+proc semExprBranchScope(c: PContext, n: PNode; expectedType: PType = nil): PNode =
   openScope(c)
-  result = semExprBranch(c, n)
+  result = semExprBranch(c, n, expectedType = expectedType)
   closeScope(c)
 
 const
-  skipForDiscardable = {nkIfStmt, nkIfExpr, nkCaseStmt, nkOfBranch,
-    nkElse, nkStmtListExpr, nkTryStmt, nkFinally, nkExceptBranch,
-    nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr}
+  skipForDiscardable = {nkStmtList, nkStmtListExpr,
+    nkOfBranch, nkElse, nkFinally, nkExceptBranch,
+    nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr,
+    nkHiddenStdConv, nkHiddenSubConv, nkHiddenDeref}
 
 proc implicitlyDiscardable(n: PNode): bool =
-  var n = n
-  while n.kind in skipForDiscardable: n = n.lastSon
-  result = isCallExpr(n) and n.sons[0].kind == nkSym and
-           sfDiscardable in n.sons[0].sym.flags
+  # same traversal as endsInNoReturn
+  template checkBranch(branch) =
+    if not implicitlyDiscardable(branch):
+      return false
+
+  var it = n
+  # skip these beforehand, no special handling needed
+  while it.kind in skipForDiscardable and it.len > 0:
+    it = it.lastSon
+
+  case it.kind
+  of nkIfExpr, nkIfStmt:
+    for branch in it:
+      checkBranch:
+        if branch.len == 2:
+          branch[1]
+        elif branch.len == 1:
+          branch[0]
+        else:
+          raiseAssert "Malformed `if` statement during implicitlyDiscardable"
+    # all branches are discardable
+    result = true
+  of nkCaseStmt:
+    for i in 1 ..< it.len:
+      let branch = it[i]
+      checkBranch:
+        case branch.kind
+        of nkOfBranch:
+          branch[^1]
+        of nkElifBranch:
+          branch[1]
+        of nkElse:
+          branch[0]
+        else:
+          raiseAssert "Malformed `case` statement in implicitlyDiscardable"
+    # all branches are discardable
+    result = true
+  of nkTryStmt:
+    checkBranch(it[0])
+    for i in 1 ..< it.len:
+      let branch = it[i]
+      if branch.kind != nkFinally:
+        checkBranch(branch[^1])
+    # all branches are discardable
+    result = true
+  of nkCallKinds:
+    result = it[0].kind == nkSym and {sfDiscardable, sfNoReturn} * it[0].sym.flags != {}
+  of nkLastBlockStmts:
+    result = true
+  else:
+    result = false
+
+proc endsInNoReturn(n: PNode, returningNode: var PNode; discardableCheck = false): bool =
+  ## check if expr ends the block like raising or call of noreturn procs do
+  result = false # assume it does return
+
+  template checkBranch(branch) =
+    if not endsInNoReturn(branch, returningNode, discardableCheck):
+      # proved a branch returns
+      return false
+
+  var it = n
+  # skip these beforehand, no special handling needed
+  let skips = if discardableCheck: skipForDiscardable else: skipForDiscardable-{nkBlockExpr, nkBlockStmt}
+  while it.kind in skips and it.len > 0:
+    it = it.lastSon
+
+  case it.kind
+  of nkIfExpr, nkIfStmt:
+    var hasElse = false
+    for branch in it:
+      checkBranch:
+        if branch.len == 2:
+          branch[1]
+        elif branch.len == 1:
+          hasElse = true
+          branch[0]
+        else:
+          raiseAssert "Malformed `if` statement during endsInNoReturn"
+    # none of the branches returned
+    result = hasElse # Only truly a no-return when it's exhaustive
+  of nkCaseStmt:
+    let caseTyp = skipTypes(it[0].typ, abstractVar-{tyTypeDesc})
+    # semCase should already have checked for exhaustiveness in this case
+    # effectively the same as having an else
+    var hasElse = caseTyp.shouldCheckCaseCovered()
+
+    # actual noreturn checks
+    for i in 1 ..< it.len:
+      let branch = it[i]
+      checkBranch:
+        case branch.kind
+        of nkOfBranch:
+          branch[^1]
+        of nkElifBranch:
+          branch[1]
+        of nkElse:
+          hasElse = true
+          branch[0]
+        else:
+          raiseAssert "Malformed `case` statement in endsInNoReturn"
+    # Can only guarantee a noreturn if there is an else or it's exhaustive
+    result = hasElse
+  of nkTryStmt:
+    checkBranch(it[0])
+    var lastIndex = it.len - 1
+    if it[lastIndex].kind == nkFinally:
+      # if finally is noreturn, then the entire statement is noreturn
+      if endsInNoReturn(it[lastIndex][^1], returningNode, discardableCheck):
+        return true
+      dec lastIndex
+    for i in 1 .. lastIndex:
+      let branch = it[i]
+      checkBranch(branch[^1])
+    # none of the branches returned
+    result = true
+  of nkLastBlockStmts:
+    result = true
+  of nkCallKinds:
+    result = it[0].kind == nkSym and sfNoReturn in it[0].sym.flags
+    if not result:
+      returningNode = it
+  else:
+    result = false
+    returningNode = it
+
+proc endsInNoReturn(n: PNode): bool =
+  var dummy: PNode = nil
+  result = endsInNoReturn(n, dummy)
 
 proc fixNilType(c: PContext; n: PNode) =
   if isAtom(n):
     if n.kind != nkNilLit and n.typ != nil:
       localError(c.config, n.info, errDiscardValueX % n.typ.typeToString)
   elif n.kind in {nkStmtList, nkStmtListExpr}:
-    n.kind = nkStmtList
+    n.transitionSonsKind(nkStmtList)
     for it in n: fixNilType(c, it)
   n.typ = nil
 
-proc discardCheck(c: PContext, result: PNode) =
-  if c.matchedConcept != nil: return
-  if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}:
+proc discardCheck(c: PContext, result: PNode, flags: TExprFlags) =
+  if c.matchedConcept != nil or efInTypeof in flags: return
+
+  if result.typ != nil and result.typ.kind notin {tyTyped, tyVoid}:
     if implicitlyDiscardable(result):
-      var n = result
-      result.typ = nil
-      while n.kind in skipForDiscardable:
-        n = n.lastSon
-        n.typ = nil
+      var n = newNodeI(nkDiscardStmt, result.info, 1)
+      n[0] = result
+      # notes that it doesn't transform nodes into discard statements
     elif result.typ.kind != tyError and c.config.cmd != cmdInteractive:
-      var n = result
-      while n.kind in skipForDiscardable: n = n.lastSon
-      var s = "expression '" & $n & "' is of type '" &
-          result.typ.typeToString & "' and has to be discarded"
-      if result.info.line != n.info.line or
-          result.info.fileIndex != n.info.fileIndex:
-        s.add "; start of expression here: " & c.config$result.info
-      if result.typ.kind == tyProc:
-        s.add "; for a function call use ()"
-      localError(c.config, n.info, s)
-
-proc semIf(c: PContext, n: PNode): PNode =
+      if result.typ.kind == tyNone:
+        localError(c.config, result.info, "expression has no type: " &
+               renderTree(result, {renderNoComments}))
+      else:
+        # Ignore noreturn procs since they don't have a type
+        var n = result
+        if result.endsInNoReturn(n, discardableCheck = true):
+          return
+
+        var s = "expression '" & $n & "' is of type '" &
+            result.typ.typeToString & "' and has to be used (or discarded)"
+        if result.info.line != n.info.line or
+            result.info.fileIndex != n.info.fileIndex:
+          s.add "; start of expression here: " & c.config$result.info
+        if result.typ.kind == tyProc:
+          s.add "; for a function call use ()"
+        localError(c.config, n.info, s)
+
+proc semIf(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil): PNode =
   result = n
   var typ = commonTypeBegin
+  var expectedType = expectedType
   var hasElse = false
-  for i in countup(0, sonsLen(n) - 1):
-    var it = n.sons[i]
+  for i in 0..<n.len:
+    var it = n[i]
     if it.len == 2:
-      when newScopeForIf: openScope(c)
-      it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0]))
-      when not newScopeForIf: openScope(c)
-      it.sons[1] = semExprBranch(c, it.sons[1])
-      typ = commonType(typ, it.sons[1])
+      openScope(c)
+      it[0] = forceBool(c, semExprWithType(c, it[0], expectedType = getSysType(c.graph, n.info, tyBool)))
+      it[1] = semExprBranch(c, it[1], flags, expectedType)
+      typ = commonType(c, typ, it[1])
+      if not endsInNoReturn(it[1]):
+        expectedType = typ
       closeScope(c)
     elif it.len == 1:
       hasElse = true
-      it.sons[0] = semExprBranchScope(c, it.sons[0])
-      typ = commonType(typ, it.sons[0])
+      it[0] = semExprBranchScope(c, it[0], expectedType)
+      typ = commonType(c, typ, it[0])
+      if not endsInNoReturn(it[0]):
+        expectedType = typ
     else: illFormedAst(it, c.config)
-  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse:
-    for it in n: discardCheck(c, it.lastSon)
-    result.kind = nkIfStmt
+  if isEmptyType(typ) or typ.kind in {tyNil, tyUntyped} or
+      (not hasElse and efInTypeof notin flags):
+    for it in n: discardCheck(c, it.lastSon, flags)
+    result.transitionSonsKind(nkIfStmt)
     # propagate any enforced VoidContext:
     if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext
   else:
     for it in n:
       let j = it.len-1
-      if not endsInNoReturn(it.sons[j]):
-        it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
-    result.kind = nkIfExpr
-    result.typ = typ
-
-proc semCase(c: PContext, n: PNode): PNode =
-  result = n
-  checkMinSonsLen(n, 2, c.config)
-  openScope(c)
-  n.sons[0] = semExprWithType(c, n.sons[0])
-  var chckCovered = false
-  var covered: BiggestInt = 0
-  var typ = commonTypeBegin
-  var hasElse = false
-  let caseTyp = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc})
-  case caseTyp.kind
-  of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool:
-    chckCovered = true
-  of tyFloat..tyFloat128, tyString, tyError:
-    discard
-  else:
-    localError(c.config, n.info, errSelectorMustBeOfCertainTypes)
-    return
-  for i in countup(1, sonsLen(n) - 1):
-    var x = n.sons[i]
-    when defined(nimsuggest):
-      if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, x.info) and caseTyp.kind == tyEnum:
-        suggestEnum(c, x, caseTyp)
-    case x.kind
-    of nkOfBranch:
-      checkMinSonsLen(x, 2, c.config)
-      semCaseBranch(c, n, x, i, covered)
-      var last = sonsLen(x)-1
-      x.sons[last] = semExprBranchScope(c, x.sons[last])
-      typ = commonType(typ, x.sons[last])
-    of nkElifBranch:
-      chckCovered = false
-      checkSonsLen(x, 2, c.config)
-      when newScopeForIf: openScope(c)
-      x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0]))
-      when not newScopeForIf: openScope(c)
-      x.sons[1] = semExprBranch(c, x.sons[1])
-      typ = commonType(typ, x.sons[1])
-      closeScope(c)
-    of nkElse:
-      chckCovered = false
-      checkSonsLen(x, 1, c.config)
-      x.sons[0] = semExprBranchScope(c, x.sons[0])
-      typ = commonType(typ, x.sons[0])
-      hasElse = true
-    else:
-      illFormedAst(x, c.config)
-  if chckCovered:
-    if covered == toCover(c, n.sons[0].typ):
-      hasElse = true
-    else:
-      localError(c.config, n.info, "not all cases are covered")
-  closeScope(c)
-  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse:
-    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
-    # propagate any enforced VoidContext:
-    if typ == c.enforceVoidContext:
-      result.typ = c.enforceVoidContext
-  else:
-    for i in 1..n.len-1:
-      var it = n.sons[i]
-      let j = it.len-1
-      if not endsInNoReturn(it.sons[j]):
-        it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
+      if not endsInNoReturn(it[j]):
+        it[j] = fitNode(c, typ, it[j], it[j].info)
+    result.transitionSonsKind(nkIfExpr)
     result.typ = typ
 
-proc semTry(c: PContext, n: PNode): PNode =
-
+proc semTry(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil): PNode =
   var check = initIntSet()
   template semExceptBranchType(typeNode: PNode): bool =
     # returns true if exception type is imported type
     let typ = semTypeNode(c, typeNode, nil).toObject()
-    var is_imported = false
+    var isImported = false
     if isImportedException(typ, c.config):
-      is_imported = true
+      isImported = true
     elif not isException(typ):
       localError(c.config, typeNode.info, errExprCannotBeRaised)
+    elif not isDefectOrCatchableError(typ):
+      message(c.config, a.info, warnBareExcept, "catch a more precise Exception deriving from CatchableError or Defect.")
 
     if containsOrIncl(check, typ.id):
       localError(c.config, typeNode.info, errExceptionAlreadyHandled)
     typeNode = newNodeIT(nkType, typeNode.info, typ)
-    is_imported
+    isImported
 
   result = n
-  inc c.p.inTryStmt
   checkMinSonsLen(n, 2, c.config)
 
   var typ = commonTypeBegin
-  n[0] = semExprBranchScope(c, n[0])
-  typ = commonType(typ, n[0].typ)
+  var expectedType = expectedType
+  n[0] = semExprBranchScope(c, n[0], expectedType)
+  if not endsInNoReturn(n[0]):
+    typ = commonType(c, typ, n[0].typ)
+    expectedType = typ
 
-  var last = sonsLen(n) - 1
-  for i in countup(1, last):
-    let a = n.sons[i]
+  var last = n.len - 1
+  var catchAllExcepts = 0
+
+  for i in 1..last:
+    let a = n[i]
     checkMinSonsLen(a, 1, c.config)
     openScope(c)
     if a.kind == nkExceptBranch:
 
       if a.len == 2 and a[0].kind == nkBracket:
         # rewrite ``except [a, b, c]: body`` -> ```except a, b, c: body```
-        a.sons[0..0] = a[0].sons
+        a.sons[0..0] = move a[0].sons
 
       if a.len == 2 and a[0].isInfixAs():
         # support ``except Exception as ex: body``
-        let is_imported = semExceptBranchType(a[0][1])
+        let isImported = semExceptBranchType(a[0][1])
         let symbol = newSymG(skLet, a[0][2], c)
-        symbol.typ = if is_imported: a[0][1].typ
-                     else: a[0][1].typ.toRef()
+        symbol.typ = if isImported: a[0][1].typ
+                     else: a[0][1].typ.toRef(c.idgen)
         addDecl(c, symbol)
         # Overwrite symbol in AST with the symbol in the symbol table.
         a[0][2] = newSymNode(symbol, a[0][2].info)
 
+      elif a.len == 1:
+        # count number of ``except: body`` blocks
+        inc catchAllExcepts
+        message(c.config, a.info, warnBareExcept,
+                  "The bare except clause is deprecated; use `except CatchableError:` instead")
       else:
         # support ``except KeyError, ValueError, ... : body``
-        var is_native, is_imported: bool
-        for j in 0..a.len-2:
+        if catchAllExcepts > 0:
+          # if ``except: body`` already encountered,
+          # cannot be followed by a ``except KeyError, ... : body`` block
+          inc catchAllExcepts
+        var isNative, isImported: bool = false
+        for j in 0..<a.len-1:
           let tmp = semExceptBranchType(a[j])
-          if tmp: is_imported = true
-          else: is_native = true
+          if tmp: isImported = true
+          else: isNative = true
 
-        if is_native and is_imported:
+        if isNative and isImported:
           localError(c.config, a[0].info, "Mix of imported and native exception types is not allowed in one except branch")
 
-    elif a.kind != nkFinally:
+    elif a.kind == nkFinally:
+      if i != n.len-1:
+        localError(c.config, a.info, "Only one finally is allowed after all other branches")
+
+    else:
       illFormedAst(n, c.config)
 
+    if catchAllExcepts > 1:
+      # if number of ``except: body`` blocks is greater than 1
+      # or more specific exception follows a general except block, it is invalid
+      localError(c.config, a.info, "Only one general except clause is allowed after more specific exceptions")
+
     # last child of an nkExcept/nkFinally branch is a statement:
-    a[^1] = semExprBranchScope(c, a[^1])
-    if a.kind != nkFinally: typ = commonType(typ, a[^1])
-    else: dec last
+    if a.kind != nkFinally:
+      a[^1] = semExprBranchScope(c, a[^1], expectedType)
+      typ = commonType(c, typ, a[^1])
+      if not endsInNoReturn(a[^1]):
+        expectedType = typ
+    else:
+      a[^1] = semExprBranchScope(c, a[^1])
+      dec last
     closeScope(c)
 
-  dec c.p.inTryStmt
-  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr}:
-    discardCheck(c, n.sons[0])
-    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
+  if isEmptyType(typ) or typ.kind in {tyNil, tyUntyped}:
+    discardCheck(c, n[0], flags)
+    for i in 1..<n.len: discardCheck(c, n[i].lastSon, flags)
     if typ == c.enforceVoidContext:
       result.typ = c.enforceVoidContext
   else:
-    if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon)
-    n.sons[0] = fitNode(c, typ, n.sons[0], n.sons[0].info)
+    if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon, flags)
+    if not endsInNoReturn(n[0]):
+      n[0] = fitNode(c, typ, n[0], n[0].info)
     for i in 1..last:
-      var it = n.sons[i]
+      var it = n[i]
       let j = it.len-1
-      it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
+      if not endsInNoReturn(it[j]):
+        it[j] = fitNode(c, typ, it[j], it[j].info)
     result.typ = typ
 
 proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode =
   result = fitNode(c, typ, n, n.info)
   if result.kind in {nkHiddenStdConv, nkHiddenSubConv}:
-    let r1 = result.sons[1]
+    let r1 = result[1]
     if r1.kind in {nkCharLit..nkUInt64Lit} and typ.skipTypes(abstractRange).kind in {tyFloat..tyFloat128}:
       result = newFloatNode(nkFloatLit, BiggestFloat r1.intVal)
       result.info = n.info
       result.typ = typ
+      if not floatRangeCheck(result.floatVal, typ):
+        localError(c.config, n.info, errFloatToString % [$result.floatVal, typeToString(typ)])
+    elif r1.kind == nkSym and typ.skipTypes(abstractRange).kind == tyCstring:
+      discard "keep nkHiddenStdConv for cstring conversions"
     else:
       changeType(c, r1, typ, check=true)
       result = r1
@@ -342,20 +469,20 @@ proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode =
     changeType(c, result, typ, check=false)
 
 proc findShadowedVar(c: PContext, v: PSym): PSym =
-  for scope in walkScopes(c.currentScope.parent):
-    if scope == c.topLevelScope: break
+  result = nil
+  for scope in localScopesFrom(c, c.currentScope.parent):
     let shadowed = strTableGet(scope.symbols, v.name)
     if shadowed != nil and shadowed.kind in skLocalVars:
       return shadowed
 
 proc identWithin(n: PNode, s: PIdent): bool =
-  for i in 0 .. n.safeLen-1:
-    if identWithin(n.sons[i], s): return true
+  for i in 0..n.safeLen-1:
+    if identWithin(n[i], s): return true
   result = n.kind == nkSym and n.sym.name.id == s.id
 
-proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
+proc semIdentDef(c: PContext, n: PNode, kind: TSymKind, reportToNimsuggest = true): PSym =
   if isTopLevel(c):
-    result = semIdentWithPragma(c, kind, n, {sfExported})
+    result = semIdentWithPragma(c, kind, n, {sfExported}, fromTopLevel = true)
     incl(result.flags, sfGlobal)
     #if kind in {skVar, skLet}:
     #  echo "global variable here ", n.info, " ", result.name.s
@@ -363,22 +490,38 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
     result = semIdentWithPragma(c, kind, n, {})
     if result.owner.kind == skModule:
       incl(result.flags, sfGlobal)
-  suggestSym(c.config, n.info, result, c.graph.usageSym)
-  styleCheckDef(c.config, result)
+  result.options = c.config.options
+
+  proc getLineInfo(n: PNode): TLineInfo =
+    case n.kind
+    of nkPostfix:
+      if len(n) > 1:
+        return getLineInfo(n[1])
+    of nkAccQuoted, nkPragmaExpr:
+      if len(n) > 0:
+        return getLineInfo(n[0])
+    else:
+      discard
+    result = n.info
+  let info = getLineInfo(n)
+  if reportToNimsuggest:
+    suggestSym(c.graph, info, result, c.graph.usageSym)
 
 proc checkNilable(c: PContext; v: PSym) =
-  if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and
-      {tfNotNil, tfNeedsInit} * v.typ.flags != {}:
-    if v.ast.isNil:
+  if {sfGlobal, sfImportc} * v.flags == {sfGlobal} and v.typ.requiresInit:
+    if v.astdef.isNil:
       message(c.config, v.info, warnProveInit, v.name.s)
-    elif tfNotNil in v.typ.flags and tfNotNil notin v.ast.typ.flags:
+    elif tfNotNil in v.typ.flags and not v.astdef.typ.isNil and tfNotNil notin v.astdef.typ.flags:
       message(c.config, v.info, warnProveInit, v.name.s)
 
-include semasgn
+#include liftdestructors
+
+proc addToVarSection(c: PContext; result: var PNode; n: PNode) =
+  if result.kind != nkStmtList:
+    result = makeStmtList(result)
+  result.add n
 
-proc addToVarSection(c: PContext; result: PNode; orig, identDefs: PNode) =
-  let L = identDefs.len
-  let value = identDefs[L-1]
+proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) =
   if result.kind == nkStmtList:
     let o = copyNode(orig)
     o.add identDefs
@@ -387,55 +530,68 @@ proc addToVarSection(c: PContext; result: PNode; orig, identDefs: PNode) =
     result.add identDefs
 
 proc isDiscardUnderscore(v: PSym): bool =
-  if v.name.s == "_":
+  if v.name.id == ord(wUnderscore):
     v.flags.incl(sfGenSym)
     result = true
+  else:
+    result = false
 
 proc semUsing(c: PContext; n: PNode): PNode =
   result = c.graph.emptyNode
   if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "using")
-  for i in countup(0, sonsLen(n)-1):
-    var a = n.sons[i]
+  for i in 0..<n.len:
+    var a = n[i]
     if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
     if a.kind == nkCommentStmt: continue
     if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
     checkMinSonsLen(a, 3, c.config)
-    var length = sonsLen(a)
-    if a.sons[length-2].kind != nkEmpty:
-      let typ = semTypeNode(c, a.sons[length-2], nil)
-      for j in countup(0, length-3):
-        let v = semIdentDef(c, a.sons[j], skParam)
+    if a[^2].kind != nkEmpty:
+      let typ = semTypeNode(c, a[^2], nil)
+      for j in 0..<a.len-2:
+        let v = semIdentDef(c, a[j], skParam)
+        styleCheckDef(c, v)
+        onDef(a[j].info, v)
         v.typ = typ
         strTableIncl(c.signatures, v)
     else:
       localError(c.config, a.info, "'using' section must have a type")
     var def: PNode
-    if a.sons[length-1].kind != nkEmpty:
+    if a[^1].kind != nkEmpty:
       localError(c.config, a.info, "'using' sections cannot contain assignments")
 
-proc hasEmpty(typ: PType): bool =
-  if typ.kind in {tySequence, tyArray, tySet}:
-    result = typ.lastSon.kind == tyEmpty
-  elif typ.kind == tyTuple:
-    for s in typ.sons:
-      result = result or hasEmpty(s)
+proc hasUnresolvedParams(n: PNode; flags: TExprFlags): bool =
+  result = tfUnresolved in n.typ.flags
+  when false:
+    case n.kind
+    of nkSym:
+      result = isGenericRoutineStrict(n.sym)
+    of nkSymChoices:
+      for ch in n:
+        if hasUnresolvedParams(ch, flags):
+          return true
+      result = false
+    else:
+      result = false
+    if efOperand in flags:
+      if tfUnresolved notin n.typ.flags:
+        result = false
 
 proc makeDeref(n: PNode): PNode =
   var t = n.typ
   if t.kind in tyUserTypeClasses and t.isResolvedUserTypeClass:
-    t = t.lastSon
-  t = skipTypes(t, {tyGenericInst, tyAlias, tySink})
+    t = t.last
+  t = skipTypes(t, {tyGenericInst, tyAlias, tySink, tyOwned})
   result = n
   if t.kind in {tyVar, tyLent}:
-    result = newNodeIT(nkHiddenDeref, n.info, t.sons[0])
-    addSon(result, n)
-    t = skipTypes(t.sons[0], {tyGenericInst, tyAlias, tySink})
+    result = newNodeIT(nkHiddenDeref, n.info, t.elementType)
+    result.add n
+    t = skipTypes(t.elementType, {tyGenericInst, tyAlias, tySink, tyOwned})
   while t.kind in {tyPtr, tyRef}:
     var a = result
-    let baseTyp = t.lastSon
+    let baseTyp = t.elementType
     result = newNodeIT(nkHiddenDeref, n.info, baseTyp)
-    addSon(result, a)
-    t = skipTypes(baseTyp, {tyGenericInst, tyAlias, tySink})
+    result.add a
+    t = skipTypes(baseTyp, {tyGenericInst, tyAlias, tySink, tyOwned})
 
 proc fillPartialObject(c: PContext; n: PNode; typ: PType) =
   if n.len == 2:
@@ -443,12 +599,12 @@ proc fillPartialObject(c: PContext; n: PNode; typ: PType) =
     let y = considerQuotedIdent(c, n[1])
     let obj = x.typ.skipTypes(abstractPtrs)
     if obj.kind == tyObject and tfPartial in obj.flags:
-      let field = newSym(skField, getIdent(c.cache, y.s), obj.sym, n[1].info)
-      field.typ = skipIntLit(typ)
-      field.position = sonsLen(obj.n)
-      addSon(obj.n, newSymNode(field))
-      n.sons[0] = makeDeref x
-      n.sons[1] = newSymNode(field)
+      let field = newSym(skField, getIdent(c.cache, y.s), c.idgen, obj.sym, n[1].info)
+      field.typ = skipIntLit(typ, c.idgen)
+      field.position = obj.n.len
+      obj.n.add newSymNode(field)
+      n[0] = makeDeref x
+      n[1] = newSymNode(field)
       n.typ = field.typ
     else:
       localError(c.config, n.info, "implicit object field construction " &
@@ -459,33 +615,233 @@ proc fillPartialObject(c: PContext; n: PNode; typ: PType) =
 proc setVarType(c: PContext; v: PSym, typ: PType) =
   if v.typ != nil and not sameTypeOrNil(v.typ, typ):
     localError(c.config, v.info, "inconsistent typing for reintroduced symbol '" &
-        v.name.s & "': previous type was: " & typeToString(v.typ) &
-        "; new type is: " & typeToString(typ))
+        v.name.s & "': previous type was: " & typeToString(v.typ, preferDesc) &
+        "; new type is: " & typeToString(typ, preferDesc))
   v.typ = typ
 
+proc isPossibleMacroPragma(c: PContext, it: PNode, key: PNode): bool =
+  # make sure it's not a normal pragma, and calls an identifier
+  # considerQuotedIdent below will fail on non-identifiers
+  result = whichPragma(it) == wInvalid and key.kind in nkIdentKinds+{nkDotExpr}
+  if result:
+    # make sure it's not a user pragma
+    if key.kind != nkDotExpr:
+      let ident = considerQuotedIdent(c, key)
+      result = strTableGet(c.userPragmas, ident) == nil
+    if result:
+      # make sure it's not a custom pragma
+      let sym = qualifiedLookUp(c, key, {})
+      result = sym == nil or sfCustomPragma notin sym.flags
+
+proc copyExcept(n: PNode, i: int): PNode =
+  result = copyNode(n)
+  for j in 0..<n.len:
+    if j != i: result.add(n[j])
+
+proc semVarMacroPragma(c: PContext, a: PNode, n: PNode): PNode =
+  # Mirrored with semProcAnnotation
+  result = nil
+  # a, b {.prag.}: int = 3 not allowed
+  const lhsPos = 0
+  if a.len == 3 and a[lhsPos].kind == nkPragmaExpr:
+    var b = a[lhsPos]
+    const
+      namePos = 0
+      pragmaPos = 1
+    let pragmas = b[pragmaPos]
+    for i in 0 ..< pragmas.len:
+      let it = pragmas[i]
+      let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it
+
+      trySuggestPragmas(c, key)
+
+      if isPossibleMacroPragma(c, it, key):
+        # we transform ``var p {.m, rest.}`` into ``m(do: var p {.rest.})`` and
+        # let the semantic checker deal with it:
+        var x = newNodeI(nkCall, key.info)
+        x.add(key)
+
+        if it.kind in nkPragmaCallKinds and it.len > 1:
+          # pass pragma arguments to the macro too:
+          for i in 1..<it.len:
+            x.add(it[i])
+
+        # Drop the pragma from the list, this prevents getting caught in endless
+        # recursion when the nkCall is semanticized
+        let oldExpr = a[lhsPos]
+        let newPragmas = copyExcept(pragmas, i)
+        if newPragmas.kind != nkEmpty and newPragmas.len == 0:
+          a[lhsPos] = oldExpr[namePos]
+        else:
+          a[lhsPos] = copyNode(oldExpr)
+          a[lhsPos].add(oldExpr[namePos])
+          a[lhsPos].add(newPragmas)
+
+        var unarySection = newNodeI(n.kind, a.info)
+        unarySection.add(a)
+        x.add(unarySection)
+
+        # recursion assures that this works for multiple macro annotations too:
+        var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared})
+        if r == nil:
+          # Restore the old list of pragmas since we couldn't process this
+          a[lhsPos] = oldExpr
+          # No matching macro was found but there's always the possibility this may
+          # be a .pragma. template instead
+          continue
+
+        doAssert r[0].kind == nkSym
+        let m = r[0].sym
+        case m.kind
+        of skMacro: result = semMacroExpr(c, r, r, m, {})
+        of skTemplate: result = semTemplateExpr(c, r, m, {})
+        else:
+          a[lhsPos] = oldExpr
+          continue
+
+        doAssert result != nil
+
+        return result
+
+template isLocalSym(sym: PSym): bool =
+  sym.kind in {skVar, skLet, skParam} and not
+    ({sfGlobal, sfPure} * sym.flags != {} or
+      sym.typ.kind == tyTypeDesc or
+      sfCompileTime in sym.flags) or
+      sym.kind in {skProc, skFunc, skIterator} and
+      sfGlobal notin sym.flags
+
+template isLocalVarSym(n: PNode): bool =
+  n.kind == nkSym and isLocalSym(n.sym)
+
+proc usesLocalVar(n: PNode): bool =
+  result = false
+  for z in 1 ..< n.len:
+    if n[z].isLocalVarSym:
+      return true
+    elif n[z].kind in nkCallKinds:
+      if usesLocalVar(n[z]):
+        return true
+
+proc globalVarInitCheck(c: PContext, n: PNode) =
+  if n.isLocalVarSym or n.kind in nkCallKinds and usesLocalVar(n):
+    localError(c.config, n.info, errCannotAssignToGlobal)
+
+const
+  errTupleUnpackingTupleExpected = "tuple expected for tuple unpacking, but got '$1'"
+  errTupleUnpackingDifferentLengths = "tuple with $1 elements expected, but got '$2' with $3 elements"
+
+proc makeVarTupleSection(c: PContext, n, a, def: PNode, typ: PType, symkind: TSymKind, origResult: var PNode): PNode =
+  ## expand tuple unpacking assignments into new var/let/const section
+  ##
+  ## mirrored with semexprs.makeTupleAssignments
+  if typ.kind != tyTuple:
+    localError(c.config, a.info, errTupleUnpackingTupleExpected %
+      [typeToString(typ, preferDesc)])
+  elif a.len-2 != typ.len:
+    localError(c.config, a.info, errTupleUnpackingDifferentLengths %
+      [$(a.len-2), typeToString(typ, preferDesc), $typ.len])
+  var
+    tempNode: PNode = nil
+    lastDef: PNode
+  let defkind = if symkind == skConst: nkConstDef else: nkIdentDefs
+  # temporary not needed if not const and RHS is tuple literal
+  # const breaks with seqs without temporary
+  let useTemp = def.kind notin {nkPar, nkTupleConstr} or symkind == skConst
+  if useTemp:
+    # use same symkind for compatibility with original section
+    let temp = newSym(symkind, getIdent(c.cache, "tmpTuple"), c.idgen, getCurrOwner(c), n.info)
+    temp.typ = typ
+    temp.flags.incl(sfGenSym)
+    lastDef = newNodeI(defkind, a.info)
+    newSons(lastDef, 3)
+    lastDef[0] = newSymNode(temp)
+    # NOTE: at the moment this is always ast.emptyNode, see parser.nim
+    lastDef[1] = a[^2]
+    lastDef[2] = def
+    temp.ast = lastDef
+    addToVarSection(c, origResult, n, lastDef)
+    tempNode = newSymNode(temp)
+  result = newNodeI(n.kind, a.info)
+  for j in 0..<a.len-2:
+    let name = a[j]
+    if useTemp and name.kind == nkIdent and name.ident.id == ord(wUnderscore):
+      # skip _ assignments if we are using a temp as they are already evaluated
+      continue
+    if name.kind == nkVarTuple:
+      # nested tuple
+      lastDef = newNodeI(nkVarTuple, name.info)
+      newSons(lastDef, name.len)
+      for k in 0..<name.len-2:
+        lastDef[k] = name[k]
+    else:
+      lastDef = newNodeI(defkind, name.info)
+      newSons(lastDef, 3)
+      lastDef[0] = name
+    lastDef[^2] = c.graph.emptyNode
+    if useTemp:
+      lastDef[^1] = newTupleAccessRaw(tempNode, j)
+    else:
+      var val = def[j]
+      if val.kind == nkExprColonExpr: val = val[1]
+      lastDef[^1] = val
+    result.add(lastDef)
+
 proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
   var b: PNode
   result = copyNode(n)
-  var hasCompileTime = false
-  for i in countup(0, sonsLen(n)-1):
-    var a = n.sons[i]
+
+  # transform var x, y = 12 into var x = 12; var y = 12
+  # bug #18104; transformation should be finished before templates expansion
+  # TODO: move warnings for tuple here
+  var transformed = copyNode(n)
+  for i in 0..<n.len:
+    var a = n[i]
+    if a.kind == nkIdentDefs and a.len > 3 and a[^1].kind != nkEmpty:
+      for j in 0..<a.len-2:
+        var b = newNodeI(nkIdentDefs, a.info)
+        b.add a[j]
+        b.add a[^2]
+        b.add copyTree(a[^1])
+        transformed.add b
+    else:
+      transformed.add a
+  let n = transformed
+
+  for i in 0..<n.len:
+    var a = n[i]
     if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
     if a.kind == nkCommentStmt: continue
-    if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
+    if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config)
     checkMinSonsLen(a, 3, c.config)
-    var length = sonsLen(a)
-    var typ: PType
-    if a.sons[length-2].kind != nkEmpty:
-      typ = semTypeNode(c, a.sons[length-2], nil)
-    else:
-      typ = nil
+
+    b = semVarMacroPragma(c, a, n)
+    if b != nil:
+      addToVarSection(c, result, b)
+      continue
+
+    var hasUserSpecifiedType = false
+    var typ: PType = nil
+    if a[^2].kind != nkEmpty:
+      typ = semTypeNode(c, a[^2], nil)
+      hasUserSpecifiedType = true
+
+    var typFlags: TTypeAllowedFlags = {}
+
     var def: PNode = c.graph.emptyNode
-    if a.sons[length-1].kind != nkEmpty:
-      def = semExprWithType(c, a.sons[length-1], {efAllowDestructor})
-      if def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro:
-        # prevent the all too common 'var x = int' bug:
-        localError(c.config, def.info, "'typedesc' metatype is not valid here; typed '=' instead of ':'?")
-        def.typ = errorType(c)
+    if typ != nil and typ.kind == tyRange and
+        c.graph.config.isDefined("nimPreviewRangeDefault") and
+        a[^1].kind == nkEmpty:
+      a[^1] = firstRange(c.config, typ)
+
+    if a[^1].kind != nkEmpty:
+      def = semExprWithType(c, a[^1], {efTypeAllowed}, typ)
+
+      if def.kind == nkSym and def.sym.kind in {skTemplate, skMacro}:
+        typFlags.incl taIsTemplateOrMacro
+      elif def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro:
+        typFlags.incl taProcContextIsNotMacro
+
       if typ != nil:
         if typ.isMetaType:
           def = inferWithMetatype(c, typ, def)
@@ -493,175 +849,323 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
         else:
           # BUGFIX: ``fitNode`` is needed here!
           # check type compatibility between def.typ and typ
-          def = fitNode(c, typ, def, def.info)
+          def = fitNodeConsiderViewType(c, typ, def, def.info)
           #changeType(def.skipConv, typ, check=true)
       else:
-        typ = def.typ.skipTypes({tyStatic}).skipIntLit
+        typ = def.typ.skipTypes({tyStatic, tySink}).skipIntLit(c.idgen)
         if typ.kind in tyUserTypeClasses and typ.isResolvedUserTypeClass:
-          typ = typ.lastSon
+          typ = typ.last
         if hasEmpty(typ):
-          localError(c.config, def.info, errCannotInferTypeOfTheLiteral %
-                     ($typ.kind).substr(2).toLowerAscii)
-        elif typ.kind == tyProc and tfUnresolved in typ.flags:
-          localError(c.config, def.info, errProcHasNoConcreteType % def.renderTree)
-    else:
-      if symkind == skLet: localError(c.config, a.info, errLetNeedsInit)
+          localError(c.config, def.info, errCannotInferTypeOfTheLiteral % typ.kind.toHumanStr)
+        elif typ.kind == tyProc and def.kind == nkSym and isGenericRoutine(def.sym.ast):
+          let owner = typ.owner
+          let err =
+            # consistent error message with evaltempl/semMacroExpr
+            if owner != nil and owner.kind in {skTemplate, skMacro}:
+              errMissingGenericParamsForTemplate % def.renderTree
+            else:
+              errProcHasNoConcreteType % def.renderTree
+          localError(c.config, def.info, err)
+        when false:
+          # XXX This typing rule is neither documented nor complete enough to
+          # justify it. Instead use the newer 'unowned x' until we figured out
+          # a more general solution.
+          if symkind == skVar and typ.kind == tyOwned and def.kind notin nkCallKinds:
+            # special type inference rule: 'var it = ownedPointer' is turned
+            # into an unowned pointer.
+            typ = typ.lastSon
 
     # this can only happen for errornous var statements:
     if typ == nil: continue
-    typeAllowedCheck(c.config, a.info, typ, symkind, if c.matchedConcept != nil: {taConcept} else: {})
-    liftTypeBoundOps(c, typ, a.info)
+
+    if c.matchedConcept != nil:
+      typFlags.incl taConcept
+    typeAllowedCheck(c, a.info, typ, symkind, typFlags)
+
     var tup = skipTypes(typ, {tyGenericInst, tyAlias, tySink})
     if a.kind == nkVarTuple:
-      if tup.kind != tyTuple:
-        localError(c.config, a.info, errXExpected, "tuple")
-      elif length-2 != sonsLen(tup):
-        localError(c.config, a.info, errWrongNumberOfVariables)
-      b = newNodeI(nkVarTuple, a.info)
-      newSons(b, length)
-      b.sons[length-2] = a.sons[length-2] # keep type desc for doc generator
-      b.sons[length-1] = def
-      addToVarSection(c, result, n, b)
-    elif tup.kind == tyTuple and def.kind in {nkPar, nkTupleConstr} and
-        a.kind == nkIdentDefs and a.len > 3:
-      message(c.config, a.info, warnEachIdentIsTuple)
-
-    for j in countup(0, length-3):
-      if a[j].kind == nkDotExpr:
-        fillPartialObject(c, a[j],
-          if a.kind != nkVarTuple: typ else: tup.sons[j])
-        addToVarSection(c, result, n, a)
-        continue
-      var v = semIdentDef(c, a.sons[j], symkind)
-      if sfGenSym notin v.flags and not isDiscardUnderscore(v):
-        addInterfaceDecl(c, v)
-      when oKeepVariableNames:
-        if c.inUnrolledContext > 0: v.flags.incl(sfShadowed)
+      # generate new section from tuple unpacking and embed it into this one
+      let assignments = makeVarTupleSection(c, n, a, def, tup, symkind, result)
+      let resSection = semVarOrLet(c, assignments, symkind)
+      for resDef in resSection:
+        addToVarSection(c, result, n, resDef)
+    else:
+      if tup.kind == tyTuple and def.kind in {nkPar, nkTupleConstr} and
+          a.len > 3:
+        # var a, b = (1, 2)
+        message(c.config, a.info, warnEachIdentIsTuple)
+
+      for j in 0..<a.len-2:
+        if a[j].kind == nkDotExpr:
+          fillPartialObject(c, a[j], typ)
+          addToVarSection(c, result, n, a)
+          continue
+        var v = semIdentDef(c, a[j], symkind, false)
+        when defined(nimsuggest):
+          v.hasUserSpecifiedType = hasUserSpecifiedType
+        styleCheckDef(c, v)
+        onDef(a[j].info, v)
+        if sfGenSym notin v.flags:
+          if not isDiscardUnderscore(v): addInterfaceDecl(c, v)
         else:
-          let shadowed = findShadowedVar(c, v)
-          if shadowed != nil:
-            shadowed.flags.incl(sfShadowed)
-            if shadowed.kind == skResult and sfGenSym notin v.flags:
-              message(c.config, a.info, warnResultShadowed)
-            # a shadowed variable is an error unless it appears on the right
-            # side of the '=':
-            if warnShadowIdent in c.config.notes and not identWithin(def, v.name):
-              message(c.config, a.info, warnShadowIdent, v.name.s)
-      if a.kind != nkVarTuple:
+          if v.owner == nil: v.owner = c.p.owner
+        when oKeepVariableNames:
+          if c.inUnrolledContext > 0: v.flags.incl(sfShadowed)
+          else:
+            let shadowed = findShadowedVar(c, v)
+            if shadowed != nil:
+              shadowed.flags.incl(sfShadowed)
+              if shadowed.kind == skResult and sfGenSym notin v.flags:
+                message(c.config, a.info, warnResultShadowed)
         if def.kind != nkEmpty:
-          # this is needed for the evaluation pass and for the guard checking:
-          v.ast = def
           if sfThread in v.flags: localError(c.config, def.info, errThreadvarCannotInit)
         setVarType(c, v, typ)
+        # this is needed for the evaluation pass, guard checking
+        #  and custom pragmas:
         b = newNodeI(nkIdentDefs, a.info)
         if importantComments(c.config):
           # keep documentation information:
           b.comment = a.comment
-        addSon(b, newSymNode(v))
-        addSon(b, a.sons[length-2])      # keep type desc for doc generator
-        addSon(b, copyTree(def))
+        # postfix not generated here (to generate, get rid of it in transf)
+        if a[j].kind == nkPragmaExpr:
+          var p = newNodeI(nkPragmaExpr, a.info)
+          p.add newSymNode(v)
+          p.add a[j][1]
+          b.add p
+        else:
+          b.add newSymNode(v)
+        # keep type desc for doc generator
+        b.add a[^2]
+        b.add copyTree(def)
         addToVarSection(c, result, n, b)
-      else:
-        if def.kind in {nkPar, nkTupleConstr}: v.ast = def[j]
-        # bug #7663, for 'nim check' this can be a non-tuple:
-        if tup.kind == tyTuple: setVarType(c, v, tup.sons[j])
-        else: v.typ = tup
-        b.sons[j] = newSymNode(v)
-      checkNilable(c, v)
-      if sfCompileTime in v.flags: hasCompileTime = true
-      if v.flags * {sfGlobal, sfThread} == {sfGlobal}:
-        message(c.config, v.info, hintGlobalVar)
-  if hasCompileTime:
-    vm.setupCompileTimeVar(c.module, c.graph, result)
-    # handled by the VM codegen:
-    #c.graph.recordStmt(c.graph, c.module, result)
+        v.ast = b
+        if def.kind == nkEmpty:
+          let actualType = v.typ.skipTypes({tyGenericInst, tyAlias,
+                                            tyUserTypeClassInst})
+          if actualType.kind in {tyObject, tyDistinct} and
+            actualType.requiresInit:
+            defaultConstructionError(c, v.typ, v.info)
+          else:
+            checkNilable(c, v)
+          # allow let to not be initialised if imported from C:
+          if v.kind == skLet and sfImportc notin v.flags and (strictDefs notin c.features or not isLocalSym(v)):
+            localError(c.config, a.info, errLetNeedsInit)
+        if sfCompileTime in v.flags:
+          var x = newNodeI(result.kind, v.info)
+          x.add result[i]
+          vm.setupCompileTimeVar(c.module, c.idgen, c.graph, x)
+        if v.flags * {sfGlobal, sfThread} == {sfGlobal}:
+          message(c.config, v.info, hintGlobalVar)
+        if {sfGlobal, sfPure} <= v.flags:
+          globalVarInitCheck(c, def)
+        suggestSym(c.graph, v.info, v, c.graph.usageSym)
 
 proc semConst(c: PContext, n: PNode): PNode =
   result = copyNode(n)
-  for i in countup(0, sonsLen(n) - 1):
-    var a = n.sons[i]
+  inc c.inStaticContext
+  var b: PNode
+  for i in 0..<n.len:
+    var a = n[i]
     if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
     if a.kind == nkCommentStmt: continue
-    if a.kind != nkConstDef: illFormedAst(a, c.config)
-    checkSonsLen(a, 3, c.config)
-    var v = semIdentDef(c, a.sons[0], skConst)
-    var typ: PType = nil
-    if a.sons[1].kind != nkEmpty: typ = semTypeNode(c, a.sons[1], nil)
+    if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config)
+    checkMinSonsLen(a, 3, c.config)
 
-    var def = semConstExpr(c, a.sons[2])
-    if def == nil:
-      localError(c.config, a.sons[2].info, errConstExprExpected)
+    b = semVarMacroPragma(c, a, n)
+    if b != nil:
+      addToVarSection(c, result, b)
       continue
+
+    var hasUserSpecifiedType = false
+    var typ: PType = nil
+    if a[^2].kind != nkEmpty:
+      typ = semTypeNode(c, a[^2], nil)
+      hasUserSpecifiedType = true
+
+    var typFlags: TTypeAllowedFlags = {}
+
+    # don't evaluate here since the type compatibility check below may add a converter
+    openScope(c)
+    var def = semExprWithType(c, a[^1], {efTypeAllowed}, typ)
+
+    if def.kind == nkSym and def.sym.kind in {skTemplate, skMacro}:
+      typFlags.incl taIsTemplateOrMacro
+    elif def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro:
+      typFlags.incl taProcContextIsNotMacro
+
     # check type compatibility between def.typ and typ:
     if typ != nil:
-      def = fitRemoveHiddenConv(c, typ, def)
+      if typ.isMetaType:
+        def = inferWithMetatype(c, typ, def)
+        typ = def.typ
+      else:
+        def = fitRemoveHiddenConv(c, typ, def)
     else:
       typ = def.typ
-    if typ == nil:
-      localError(c.config, a.sons[2].info, errConstExprExpected)
-      continue
-    if typeAllowed(typ, skConst) != nil and def.kind != nkNilLit:
-      localError(c.config, a.info, "invalid type for const: " & typeToString(typ))
+
+    # evaluate the node
+    def = semConstExpr(c, def)
+    if def == nil:
+      localError(c.config, a[^1].info, errConstExprExpected)
       continue
-    setVarType(c, v, typ)
-    v.ast = def               # no need to copy
-    if sfGenSym notin v.flags: addInterfaceDecl(c, v)
-    var b = newNodeI(nkConstDef, a.info)
-    if importantComments(c.config): b.comment = a.comment
-    addSon(b, newSymNode(v))
-    addSon(b, a.sons[1])
-    addSon(b, copyTree(def))
-    addSon(result, b)
+    if def.kind != nkNilLit:
+      if c.matchedConcept != nil:
+        typFlags.incl taConcept
+      typeAllowedCheck(c, a.info, typ, skConst, typFlags)
+    closeScope(c)
+
+    if a.kind == nkVarTuple:
+      # generate new section from tuple unpacking and embed it into this one
+      let assignments = makeVarTupleSection(c, n, a, def, typ, skConst, result)
+      let resSection = semConst(c, assignments)
+      for resDef in resSection:
+        addToVarSection(c, result, n, resDef)
+    else:
+      for j in 0..<a.len-2:
+        var v = semIdentDef(c, a[j], skConst)
+        when defined(nimsuggest):
+          v.hasUserSpecifiedType = hasUserSpecifiedType
+        if sfGenSym notin v.flags: addInterfaceDecl(c, v)
+        elif v.owner == nil: v.owner = getCurrOwner(c)
+        styleCheckDef(c, v)
+        onDef(a[j].info, v)
+
+        var fillSymbol = true
+        if v.typ != nil:
+          # symbol already has type and probably value
+          # don't mutate
+          fillSymbol = false
+        else:
+          setVarType(c, v, typ)
+        b = newNodeI(nkConstDef, a.info)
+        if importantComments(c.config): b.comment = a.comment
+        # postfix not generated here (to generate, get rid of it in transf)
+        if a[j].kind == nkPragmaExpr:
+          var p = newNodeI(nkPragmaExpr, a.info)
+          p.add newSymNode(v)
+          p.add a[j][1].copyTree
+          b.add p
+        else:
+          b.add newSymNode(v)
+        b.add a[1]
+        b.add copyTree(def)
+        if fillSymbol:
+          v.ast = b
+        addToVarSection(c, result, n, b)
+  dec c.inStaticContext
 
 include semfields
 
-proc addForVarDecl(c: PContext, v: PSym) =
-  if warnShadowIdent in c.config.notes:
-    let shadowed = findShadowedVar(c, v)
-    if shadowed != nil:
-      # XXX should we do this here?
-      #shadowed.flags.incl(sfShadowed)
-      message(c.config, v.info, warnShadowIdent, v.name.s)
-  addDecl(c, v)
 
 proc symForVar(c: PContext, n: PNode): PSym =
-  let m = if n.kind == nkPragmaExpr: n.sons[0] else: n
+  let m = if n.kind == nkPragmaExpr: n[0] else: n
   result = newSymG(skForVar, m, c)
-  styleCheckDef(c.config, result)
+  styleCheckDef(c, result)
+  onDef(n.info, result)
+  if n.kind == nkPragmaExpr:
+    pragma(c, result, n[1], forVarPragmas)
 
-proc semForVars(c: PContext, n: PNode): PNode =
+proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
   result = n
-  var length = sonsLen(n)
-  let iterBase = n.sons[length-2].typ
-  var iter = skipTypes(iterBase, {tyGenericInst, tyAlias, tySink})
-  # length == 3 means that there is one for loop variable
+  let iterBase = n[^2].typ
+  var iter = skipTypes(iterBase, {tyGenericInst, tyAlias, tySink, tyOwned})
+  var iterAfterVarLent = iter.skipTypes({tyGenericInst, tyAlias, tyLent, tyVar})
+  # n.len == 3 means that there is one for loop variable
   # and thus no tuple unpacking:
-  if iter.kind != tyTuple or length == 3:
-    if length == 3:
-      var v = symForVar(c, n.sons[0])
-      if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
-      # BUGFIX: don't use `iter` here as that would strip away
-      # the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
-      # for an example:
-      v.typ = iterBase
-      n.sons[0] = newSymNode(v)
-      if sfGenSym notin v.flags: addForVarDecl(c, v)
+  if iterAfterVarLent.kind == tyEmpty:
+    localError(c.config, n[^2].info, "cannot infer element type of $1" %
+               renderTree(n[^2], {renderNoComments}))
+  if iterAfterVarLent.kind != tyTuple or n.len == 3:
+    if n.len == 3:
+      if n[0].kind == nkVarTuple:
+        if iterAfterVarLent.kind != tyTuple:
+          return localErrorNode(c, n, n[0].info, errTupleUnpackingTupleExpected %
+              [typeToString(n[1].typ, preferDesc)])
+        elif n[0].len-1 != iterAfterVarLent.len:
+          return localErrorNode(c, n, n[0].info, errWrongNumberOfVariables)
+
+        for i in 0..<n[0].len-1:
+          var v = symForVar(c, n[0][i])
+          if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
+          case iter.kind
+          of tyVar, tyLent:
+            v.typ = newTypeS(iter.kind, c)
+            v.typ.add iterAfterVarLent[i]
+            if tfVarIsPtr in iter.flags:
+              v.typ.flags.incl tfVarIsPtr
+          else:
+            v.typ = iter[i]
+          n[0][i] = newSymNode(v)
+          if sfGenSym notin v.flags and not isDiscardUnderscore(v): addDecl(c, v)
+          elif v.owner == nil: v.owner = getCurrOwner(c)
+      else:
+        var v = symForVar(c, n[0])
+        if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
+        # BUGFIX: don't use `iter` here as that would strip away
+        # the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
+        # for an example:
+        v.typ = iterBase
+        n[0] = newSymNode(v)
+        if sfGenSym notin v.flags and not isDiscardUnderscore(v): addDecl(c, v)
+        elif v.owner == nil: v.owner = getCurrOwner(c)
     else:
       localError(c.config, n.info, errWrongNumberOfVariables)
-  elif length-2 != sonsLen(iter):
+  elif n.len-2 != iterAfterVarLent.len:
     localError(c.config, n.info, errWrongNumberOfVariables)
   else:
-    for i in countup(0, length - 3):
-      var v = symForVar(c, n.sons[i])
-      if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
-      v.typ = iter.sons[i]
-      n.sons[i] = newSymNode(v)
-      if sfGenSym notin v.flags and not isDiscardUnderscore(v):
-        addForVarDecl(c, v)
+    for i in 0..<n.len - 2:
+      if n[i].kind == nkVarTuple:
+        var mutable = false
+        var isLent = false
+        case iter[i].kind
+        of tyVar:
+          mutable = true
+          iter[i] = iter[i].skipTypes({tyVar})
+        of tyLent:
+          isLent = true
+          iter[i] = iter[i].skipTypes({tyLent})
+        else: discard
+
+        if n[i].len-1 != iter[i].len:
+          localError(c.config, n[i].info, errWrongNumberOfVariables)
+        for j in 0..<n[i].len-1:
+          var v = symForVar(c, n[i][j])
+          if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
+          if mutable:
+            v.typ = newTypeS(tyVar, c)
+            v.typ.add iter[i][j]
+          elif isLent:
+            v.typ = newTypeS(tyLent, c)
+            v.typ.add iter[i][j]
+          else:
+            v.typ = iter[i][j]
+          n[i][j] = newSymNode(v)
+          if not isDiscardUnderscore(v): addDecl(c, v)
+          elif v.owner == nil: v.owner = getCurrOwner(c)
+      else:
+        var v = symForVar(c, n[i])
+        if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
+        case iter.kind
+        of tyVar, tyLent:
+          v.typ = newTypeS(iter.kind, c)
+          v.typ.add iterAfterVarLent[i]
+          if tfVarIsPtr in iter.flags:
+            v.typ.flags.incl tfVarIsPtr
+        else:
+          v.typ = iter[i]
+        n[i] = newSymNode(v)
+        if sfGenSym notin v.flags:
+          if not isDiscardUnderscore(v): addDecl(c, v)
+        elif v.owner == nil: v.owner = getCurrOwner(c)
   inc(c.p.nestedLoopCounter)
+  let oldBreakInLoop = c.p.breakInLoop
+  c.p.breakInLoop = true
   openScope(c)
-  n.sons[length-1] = semStmt(c, n.sons[length-1])
+  n[^1] = semExprBranch(c, n[^1], flags)
+  if efInTypeof notin flags:
+    discardCheck(c, n[^1], flags)
   closeScope(c)
+  c.p.breakInLoop = oldBreakInLoop
   dec(c.p.nestedLoopCounter)
 
 proc implicitIterator(c: PContext, it: string, arg: PNode): PNode =
@@ -674,34 +1178,34 @@ proc implicitIterator(c: PContext, it: string, arg: PNode): PNode =
   result = semExprNoDeref(c, result, {efWantIterator})
 
 proc isTrivalStmtExpr(n: PNode): bool =
-  for i in 0 .. n.len-2:
+  for i in 0..<n.len-1:
     if n[i].kind notin {nkEmpty, nkCommentStmt}:
       return false
   result = true
 
-proc handleForLoopMacro(c: PContext; n: PNode): PNode =
-  let iterExpr = n[^2]
-  if iterExpr.kind in nkCallKinds:
+proc handleStmtMacro(c: PContext; n, selector: PNode; magicType: string;
+                     flags: TExprFlags): PNode =
+  if selector.kind in nkCallKinds:
     # we transform
     # n := for a, b, c in m(x, y, z): Y
     # to
     # m(n)
-    let forLoopStmt = magicsys.getCompilerProc(c.graph, "ForLoopStmt")
-    if forLoopStmt == nil: return
+    let maType = magicsys.getCompilerProc(c.graph, magicType)
+    if maType == nil: return
 
-    let headSymbol = iterExpr[0]
-    var o: TOverloadIter
+    let headSymbol = selector[0]
+    var o: TOverloadIter = default(TOverloadIter)
     var match: PSym = nil
     var symx = initOverloadIter(o, c, headSymbol)
     while symx != nil:
       if symx.kind in {skTemplate, skMacro}:
-        if symx.typ.len == 2 and symx.typ[1] == forLoopStmt.typ:
+        if symx.typ.len == 2 and symx.typ.firstParamType == maType.typ:
           if match == nil:
             match = symx
           else:
             localError(c.config, n.info, errAmbiguousCallXYZ % [
               getProcHeader(c.config, match),
-              getProcHeader(c.config, symx), $iterExpr])
+              getProcHeader(c.config, symx), $selector])
       symx = nextOverloadIter(o, c, headSymbol)
 
     if match == nil: return
@@ -709,146 +1213,300 @@ proc handleForLoopMacro(c: PContext; n: PNode): PNode =
     callExpr.add newSymNode(match)
     callExpr.add n
     case match.kind
-    of skMacro: result = semMacroExpr(c, callExpr, callExpr, match, {})
-    of skTemplate: result = semTemplateExpr(c, callExpr, match, {})
+    of skMacro: result = semMacroExpr(c, callExpr, callExpr, match, flags)
+    of skTemplate: result = semTemplateExpr(c, callExpr, match, flags)
     else: result = nil
+  else:
+    result = nil
+
+proc handleForLoopMacro(c: PContext; n: PNode; flags: TExprFlags): PNode =
+  result = handleStmtMacro(c, n, n[^2], "ForLoopStmt", flags)
+
+proc handleCaseStmtMacro(c: PContext; n: PNode; flags: TExprFlags): PNode =
+  # n[0] has been sem'checked and has a type. We use this to resolve
+  # '`case`(n[0])' but then we pass 'n' to the `case` macro. This seems to
+  # be the best solution.
+  var toResolve = newNodeI(nkCall, n.info)
+  toResolve.add newIdentNode(getIdent(c.cache, "case"), n.info)
+  toResolve.add n[0]
+
+  var errors: CandidateErrors = @[]
+  var r = resolveOverloads(c, toResolve, toResolve, {skTemplate, skMacro}, {efNoUndeclared},
+                           errors, false)
+  if r.state == csMatch:
+    var match = r.calleeSym
+    markUsed(c, n[0].info, match)
+    onUse(n[0].info, match)
+
+    # but pass 'n' to the `case` macro, not 'n[0]':
+    r.call[1] = n
+    let toExpand = semResolvedCall(c, r, r.call, {})
+    case match.kind
+    of skMacro: result = semMacroExpr(c, toExpand, toExpand, match, flags)
+    of skTemplate: result = semTemplateExpr(c, toExpand, match, flags)
+    else: result = errorNode(c, n[0])
+  else:
+    result = errorNode(c, n[0])
+  if result.kind == nkEmpty:
+    localError(c.config, n[0].info, errSelectorMustBeOfCertainTypes)
+  # this would be the perfectly consistent solution with 'for loop macros',
+  # but it kinda sucks for pattern matching as the matcher is not attached to
+  # a type then:
+  when false:
+    result = handleStmtMacro(c, n, n[0], "CaseStmt")
 
-proc semFor(c: PContext, n: PNode): PNode =
+proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
   checkMinSonsLen(n, 3, c.config)
-  var length = sonsLen(n)
-  result = handleForLoopMacro(c, n)
+  result = handleForLoopMacro(c, n, flags)
   if result != nil: return result
   openScope(c)
   result = n
-  n.sons[length-2] = semExprNoDeref(c, n.sons[length-2], {efWantIterator})
-  var call = n.sons[length-2]
-  if call.kind == nkStmtListExpr and isTrivalStmtExpr(call):
+  n[^2] = semExprNoDeref(c, n[^2], {efWantIterator})
+  var call = n[^2]
+
+  if call.kind == nkStmtListExpr and (isTrivalStmtExpr(call) or (call.lastSon.kind in nkCallKinds and call.lastSon[0].sym.kind == skIterator)):
     call = call.lastSon
-    n.sons[length-2] = call
+    n[^2] = call
   let isCallExpr = call.kind in nkCallKinds
   if isCallExpr and call[0].kind == nkSym and
       call[0].sym.magic in {mFields, mFieldPairs, mOmpParFor}:
-    if call.sons[0].sym.magic == mOmpParFor:
-      result = semForVars(c, n)
-      result.kind = nkParForStmt
+    if call[0].sym.magic == mOmpParFor:
+      result = semForVars(c, n, flags)
+      result.transitionSonsKind(nkParForStmt)
     else:
-      result = semForFields(c, n, call.sons[0].sym.magic)
-  elif isCallExpr and call.sons[0].typ.callConv == ccClosure and
-      tfIterator in call.sons[0].typ.flags:
+      result = semForFields(c, n, call[0].sym.magic)
+  elif isCallExpr and isClosureIterator(call[0].typ.skipTypes(abstractInst)):
     # first class iterator:
-    result = semForVars(c, n)
-  elif not isCallExpr or call.sons[0].kind != nkSym or
-      call.sons[0].sym.kind != skIterator:
-    if length == 3:
-      n.sons[length-2] = implicitIterator(c, "items", n.sons[length-2])
-    elif length == 4:
-      n.sons[length-2] = implicitIterator(c, "pairs", n.sons[length-2])
+    result = semForVars(c, n, flags)
+  elif not isCallExpr or call[0].kind != nkSym or
+      call[0].sym.kind != skIterator:
+    if n.len == 3:
+      n[^2] = implicitIterator(c, "items", n[^2])
+    elif n.len == 4:
+      n[^2] = implicitIterator(c, "pairs", n[^2])
     else:
-      localError(c.config, n.sons[length-2].info, "iterator within for loop context expected")
-    result = semForVars(c, n)
+      localError(c.config, n[^2].info, "iterator within for loop context expected")
+    result = semForVars(c, n, flags)
   else:
-    result = semForVars(c, n)
+    result = semForVars(c, n, flags)
   # propagate any enforced VoidContext:
-  if n.sons[length-1].typ == c.enforceVoidContext:
+  if n[^1].typ == c.enforceVoidContext:
     result.typ = c.enforceVoidContext
+  elif efInTypeof in flags:
+    result.typ = result.lastSon.typ
   closeScope(c)
 
+proc semCase(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil): PNode =
+  result = n
+  checkMinSonsLen(n, 2, c.config)
+  openScope(c)
+  pushCaseContext(c, n)
+  n[0] = semExprWithType(c, n[0])
+  var covered: Int128 = toInt128(0)
+  var typ = commonTypeBegin
+  var expectedType = expectedType
+  var hasElse = false
+  let caseTyp = skipTypes(n[0].typ, abstractVar-{tyTypeDesc})
+  var chckCovered = caseTyp.shouldCheckCaseCovered()
+  case caseTyp.kind
+  of tyFloat..tyFloat128, tyString, tyCstring, tyError, shouldChckCovered, tyRange:
+    discard
+  else:
+    popCaseContext(c)
+    closeScope(c)
+    return handleCaseStmtMacro(c, n, flags)
+  template invalidOrderOfBranches(n: PNode) =
+    localError(c.config, n.info, "invalid order of case branches")
+    break
+
+  for i in 1..<n.len:
+    setCaseContextIdx(c, i)
+    var x = n[i]
+    when defined(nimsuggest):
+      if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, x.info) and caseTyp.kind == tyEnum:
+        suggestEnum(c, x, caseTyp)
+    case x.kind
+    of nkOfBranch:
+      if hasElse: invalidOrderOfBranches(x)
+      checkMinSonsLen(x, 2, c.config)
+      semCaseBranch(c, n, x, i, covered)
+      var last = x.len-1
+      x[last] = semExprBranchScope(c, x[last], expectedType)
+      typ = commonType(c, typ, x[last])
+      if not endsInNoReturn(x[last]):
+        expectedType = typ
+    of nkElifBranch:
+      if hasElse: invalidOrderOfBranches(x)
+      chckCovered = false
+      checkSonsLen(x, 2, c.config)
+      openScope(c)
+      x[0] = forceBool(c, semExprWithType(c, x[0], expectedType = getSysType(c.graph, n.info, tyBool)))
+      x[1] = semExprBranch(c, x[1], expectedType = expectedType)
+      typ = commonType(c, typ, x[1])
+      if not endsInNoReturn(x[1]):
+        expectedType = typ
+      closeScope(c)
+    of nkElse:
+      checkSonsLen(x, 1, c.config)
+      x[0] = semExprBranchScope(c, x[0], expectedType)
+      typ = commonType(c, typ, x[0])
+      if not endsInNoReturn(x[0]):
+        expectedType = typ
+      if (chckCovered and covered == toCover(c, n[0].typ)) or hasElse:
+        message(c.config, x.info, warnUnreachableElse)
+      hasElse = true
+      chckCovered = false
+    else:
+      illFormedAst(x, c.config)
+  if chckCovered:
+    if covered == toCover(c, n[0].typ):
+      hasElse = true
+    elif n[0].typ.skipTypes(abstractRange).kind in {tyEnum, tyChar}:
+      localError(c.config, n.info, "not all cases are covered; missing: $1" %
+                 formatMissingEnums(c, n))
+    else:
+      localError(c.config, n.info, "not all cases are covered")
+  popCaseContext(c)
+  closeScope(c)
+  if isEmptyType(typ) or typ.kind in {tyNil, tyUntyped} or
+      (not hasElse and efInTypeof notin flags):
+    for i in 1..<n.len: discardCheck(c, n[i].lastSon, flags)
+    # propagate any enforced VoidContext:
+    if typ == c.enforceVoidContext:
+      result.typ = c.enforceVoidContext
+  else:
+    for i in 1..<n.len:
+      var it = n[i]
+      let j = it.len-1
+      if not endsInNoReturn(it[j]):
+        it[j] = fitNode(c, typ, it[j], it[j].info)
+    result.typ = typ
+
 proc semRaise(c: PContext, n: PNode): PNode =
   result = n
   checkSonsLen(n, 1, c.config)
   if n[0].kind != nkEmpty:
     n[0] = semExprWithType(c, n[0])
-    let typ = n[0].typ
+    var typ = n[0].typ
     if not isImportedException(typ, c.config):
-      if typ.kind != tyRef or typ.lastSon.kind != tyObject:
+      typ = typ.skipTypes({tyAlias, tyGenericInst, tyOwned})
+      if typ.kind != tyRef:
         localError(c.config, n.info, errExprCannotBeRaised)
-      if not isException(typ.lastSon):
-        localError(c.config, n.info, "raised object of type $1 does not inherit from Exception",
-                          [typeToString(typ)])
+      if typ.len > 0 and not isException(typ.elementType):
+        localError(c.config, n.info, "raised object of type $1 does not inherit from Exception" % typeToString(typ))
 
 proc addGenericParamListToScope(c: PContext, n: PNode) =
   if n.kind != nkGenericParams: illFormedAst(n, c.config)
-  for i in countup(0, sonsLen(n)-1):
-    var a = n.sons[i]
+  for i in 0..<n.len:
+    var a = n[i]
     if a.kind == nkSym: addDecl(c, a.sym)
     else: illFormedAst(a, c.config)
 
 proc typeSectionTypeName(c: PContext; n: PNode): PNode =
   if n.kind == nkPragmaExpr:
     if n.len == 0: illFormedAst(n, c.config)
-    result = n.sons[0]
+    result = n[0]
   else:
     result = n
+  if result.kind == nkPostfix:
+    if result.len != 2: illFormedAst(n, c.config)
+    result = result[1]
   if result.kind != nkSym: illFormedAst(n, c.config)
 
+proc typeDefLeftSidePass(c: PContext, typeSection: PNode, i: int) =
+  let typeDef = typeSection[i]
+  checkSonsLen(typeDef, 3, c.config)
+  var name = typeDef[0]
+  var s: PSym = nil
+  if name.kind == nkDotExpr and typeDef[2].kind == nkObjectTy:
+    let pkgName = considerQuotedIdent(c, name[0])
+    let typName = considerQuotedIdent(c, name[1])
+    let pkg = c.graph.packageSyms.strTableGet(pkgName)
+    if pkg.isNil or pkg.kind != skPackage:
+      localError(c.config, name.info, "unknown package name: " & pkgName.s)
+    else:
+      let typsym = c.graph.packageTypes.strTableGet(typName)
+      if typsym.isNil:
+        s = semIdentDef(c, name[1], skType)
+        onDef(name[1].info, s)
+        s.typ = newTypeS(tyObject, c)
+        s.typ.sym = s
+        s.flags.incl sfForward
+        c.graph.packageTypes.strTableAdd s
+        addInterfaceDecl(c, s)
+      elif typsym.kind == skType and sfForward in typsym.flags:
+        s = typsym
+        addInterfaceDecl(c, s)
+        # PRTEMP no onDef here?
+      else:
+        localError(c.config, name.info, typsym.name.s & " is not a type that can be forwarded")
+        s = typsym
+  else:
+    s = semIdentDef(c, name, skType)
+    onDef(name.info, s)
+    s.typ = newTypeS(tyForward, c)
+    s.typ.sym = s             # process pragmas:
+    if name.kind == nkPragmaExpr:
+      let rewritten = applyTypeSectionPragmas(c, name[1], typeDef)
+      if rewritten != nil:
+        case rewritten.kind
+        of nkTypeDef:
+          typeSection[i] = rewritten
+        of nkTypeSection:
+          typeSection.sons[i .. i] = rewritten.sons
+        else: illFormedAst(rewritten, c.config)
+        typeDefLeftSidePass(c, typeSection, i)
+        return
+      pragma(c, s, name[1], typePragmas)
+    if sfForward in s.flags:
+      # check if the symbol already exists:
+      let pkg = c.module.owner
+      if not isTopLevel(c) or pkg.isNil:
+        localError(c.config, name.info, "only top level types in a package can be 'package'")
+      else:
+        let typsym = c.graph.packageTypes.strTableGet(s.name)
+        if typsym != nil:
+          if sfForward notin typsym.flags or sfNoForward notin typsym.flags:
+            typeCompleted(typsym)
+            typsym.info = s.info
+          else:
+            localError(c.config, name.info, "cannot complete type '" & s.name.s & "' twice; " &
+                    "previous type completion was here: " & c.config$typsym.info)
+          s = typsym
+    # add it here, so that recursive types are possible:
+    if sfGenSym notin s.flags: addInterfaceDecl(c, s)
+    elif s.owner == nil: s.owner = getCurrOwner(c)
+
+  if name.kind == nkPragmaExpr:
+    if name[0].kind == nkPostfix:
+      typeDef[0][0][1] = newSymNode(s)
+    else:
+      typeDef[0][0] = newSymNode(s)
+  else:
+    if name.kind == nkPostfix:
+      typeDef[0][1] = newSymNode(s)
+    else:
+      typeDef[0] = newSymNode(s)
 
 proc typeSectionLeftSidePass(c: PContext, n: PNode) =
   # process the symbols on the left side for the whole type section, before
   # we even look at the type definitions on the right
-  for i in countup(0, sonsLen(n) - 1):
-    var a = n.sons[i]
+  var i = 0
+  while i < n.len: # n may grow due to type pragma macros
+    var a = n[i]
     when defined(nimsuggest):
       if c.config.cmd == cmdIdeTools:
         inc c.inTypeContext
         suggestStmt(c, a)
         dec c.inTypeContext
-    if a.kind == nkCommentStmt: continue
-    if a.kind != nkTypeDef: illFormedAst(a, c.config)
-    checkSonsLen(a, 3, c.config)
-    let name = a.sons[0]
-    var s: PSym
-    if name.kind == nkDotExpr and a[2].kind == nkObjectTy:
-      let pkgName = considerQuotedIdent(c, name[0])
-      let typName = considerQuotedIdent(c, name[1])
-      let pkg = c.graph.packageSyms.strTableGet(pkgName)
-      if pkg.isNil or pkg.kind != skPackage:
-        localError(c.config, name.info, "unknown package name: " & pkgName.s)
-      else:
-        let typsym = pkg.tab.strTableGet(typName)
-        if typsym.isNil:
-          s = semIdentDef(c, name[1], skType)
-          s.typ = newTypeS(tyObject, c)
-          s.typ.sym = s
-          s.flags.incl sfForward
-          pkg.tab.strTableAdd s
-          addInterfaceDecl(c, s)
-        elif typsym.kind == skType and sfForward in typsym.flags:
-          s = typsym
-          addInterfaceDecl(c, s)
-        else:
-          localError(c.config, name.info, typsym.name.s & " is not a type that can be forwarded")
-          s = typsym
-    else:
-      s = semIdentDef(c, name, skType)
-      s.typ = newTypeS(tyForward, c)
-      s.typ.sym = s             # process pragmas:
-      if name.kind == nkPragmaExpr:
-        pragma(c, s, name.sons[1], typePragmas)
-      if sfForward in s.flags:
-        # check if the symbol already exists:
-        let pkg = c.module.owner
-        if not isTopLevel(c) or pkg.isNil:
-          localError(c.config, name.info, "only top level types in a package can be 'package'")
-        else:
-          let typsym = pkg.tab.strTableGet(s.name)
-          if typsym != nil:
-            if sfForward notin typsym.flags or sfNoForward notin typsym.flags:
-              typeCompleted(typsym)
-              typsym.info = s.info
-            else:
-              localError(c.config, name.info, "cannot complete type '" & s.name.s & "' twice; " &
-                      "previous type completion was here: " & c.config$typsym.info)
-            s = typsym
-      # add it here, so that recursive types are possible:
-      if sfGenSym notin s.flags: addInterfaceDecl(c, s)
-
-    if name.kind == nkPragmaExpr:
-      a.sons[0].sons[0] = newSymNode(s)
-    else:
-      a.sons[0] = newSymNode(s)
+    case a.kind
+    of nkCommentStmt: discard
+    of nkTypeDef: typeDefLeftSidePass(c, n, i)
+    else: illFormedAst(a, c.config)
+    inc i
 
 proc checkCovariantParamsUsages(c: PContext; genericType: PType) =
-  var body = genericType[^1]
+  var body = genericType.typeBodyImpl
 
   proc traverseSubTypes(c: PContext; t: PType): bool =
     template error(msg) = localError(c.config, genericType.sym.info, msg)
@@ -865,18 +1523,18 @@ proc checkCovariantParamsUsages(c: PContext; genericType: PType) =
       for field in t.n:
         subresult traverseSubTypes(c, field.typ)
     of tyArray:
-      return traverseSubTypes(c, t[1])
+      return traverseSubTypes(c, t.elementType)
     of tyProc:
-      for subType in t.sons:
+      for subType in t.signature:
         if subType != nil:
           subresult traverseSubTypes(c, subType)
       if result:
         error("non-invariant type param used in a proc type: " & $t)
     of tySequence:
-      return traverseSubTypes(c, t[0])
+      return traverseSubTypes(c, t.elementType)
     of tyGenericInvocation:
-      let targetBody = t[0]
-      for i in 1 ..< t.len:
+      let targetBody = t.genericHead
+      for i in 1..<t.len:
         let param = t[i]
         if param.kind == tyGenericParam:
           if tfCovariant in param.flags:
@@ -900,13 +1558,13 @@ proc checkCovariantParamsUsages(c: PContext; genericType: PType) =
     of tyUserTypeClass, tyUserTypeClassInst:
       error("non-invariant type parameters are not supported in concepts")
     of tyTuple:
-      for fieldType in t.sons:
+      for fieldType in t.kids:
         subresult traverseSubTypes(c, fieldType)
     of tyPtr, tyRef, tyVar, tyLent:
-      if t.base.kind == tyGenericParam: return true
-      return traverseSubTypes(c, t.base)
-    of tyDistinct, tyAlias, tySink:
-      return traverseSubTypes(c, t.lastSon)
+      if t.elementType.kind == tyGenericParam: return true
+      return traverseSubTypes(c, t.elementType)
+    of tyDistinct, tyAlias, tySink, tyOwned:
+      return traverseSubTypes(c, t.skipModifier)
     of tyGenericInst:
       internalAssert c.config, false
     else:
@@ -914,17 +1572,18 @@ proc checkCovariantParamsUsages(c: PContext; genericType: PType) =
   discard traverseSubTypes(c, body)
 
 proc typeSectionRightSidePass(c: PContext, n: PNode) =
-  for i in countup(0, sonsLen(n) - 1):
-    var a = n.sons[i]
+  for i in 0..<n.len:
+    var a = n[i]
     if a.kind == nkCommentStmt: continue
     if a.kind != nkTypeDef: illFormedAst(a, c.config)
     checkSonsLen(a, 3, c.config)
-    let name = typeSectionTypeName(c, a.sons[0])
+    let name = typeSectionTypeName(c, a[0])
     var s = name.sym
-    if s.magic == mNone and a.sons[2].kind == nkEmpty:
+    if s.magic == mNone and a[2].kind == nkEmpty:
       localError(c.config, a.info, errImplOfXexpected % s.name.s)
     if s.magic != mNone: processMagicType(c, s)
-    if a.sons[1].kind != nkEmpty:
+    let oldFlags = s.typ.flags
+    if a[1].kind != nkEmpty:
       # We have a generic type declaration here. In generic types,
       # symbol lookup needs to be done here.
       openScope(c)
@@ -937,21 +1596,32 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
       #   TGObj[T] = object
       #   TAlias[T] = TGObj[T]
       #
-      s.typ.n = semGenericParamList(c, a.sons[1], s.typ)
-      a.sons[1] = s.typ.n
+      s.typ.n = semGenericParamList(c, a[1], s.typ)
+      a[1] = s.typ.n
       s.typ.size = -1 # could not be computed properly
       # we fill it out later. For magic generics like 'seq', it won't be filled
       # so we use tyNone instead of nil to not crash for strange conversions
       # like: mydata.seq
-      rawAddSon(s.typ, newTypeS(tyNone, c))
+      if s.typ.kind in {tyOpenArray, tyVarargs} and s.typ.len == 1:
+        # XXX investigate why `tySequence` cannot be added here for now.
+        discard
+      else:
+        rawAddSon(s.typ, newTypeS(tyNone, c))
       s.ast = a
       inc c.inGenericContext
-      var body = semTypeNode(c, a.sons[2], nil)
+      var body = semTypeNode(c, a[2], s.typ)
       dec c.inGenericContext
       if body != nil:
         body.sym = s
         body.size = -1 # could not be computed properly
-        s.typ.sons[sonsLen(s.typ) - 1] = body
+        if body.kind == tyObject:
+          # add flags applied to generic type to object (nominal) type
+          incl(body.flags, oldFlags)
+          # {.inheritable, final.} is already disallowed, but
+          # object might have been assumed to be final
+          if tfInheritable in oldFlags and tfFinal in body.flags:
+            excl(body.flags, tfFinal)
+        s.typ[^1] = body
         if tfCovariant in s.typ.flags:
           checkCovariantParamsUsages(c, s.typ)
           # XXX: This is a temporary limitation:
@@ -963,17 +1633,17 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
           # possibilities such as instantiating C++ generic types with
           # garbage collected Nim types.
           if sfImportc in s.flags:
-            var body = s.typ.lastSon
+            var body = s.typ.last
             if body.kind == tyObject:
               # erases all declared fields
-              body.n.sons = nil
+              body.n.sons = @[]
 
       popOwner(c)
       closeScope(c)
-    elif a.sons[2].kind != nkEmpty:
+    elif a[2].kind != nkEmpty:
       # process the type's body:
       pushOwner(c, s)
-      var t = semTypeNode(c, a.sons[2], s.typ)
+      var t = semTypeNode(c, a[2], s.typ)
       if s.typ == nil:
         s.typ = t
       elif t != s.typ and (s.typ == nil or s.typ.kind != tyAlias):
@@ -982,73 +1652,145 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
         #debug s.typ
       s.ast = a
       popOwner(c)
-    let aa = a.sons[2]
+      # If the right hand side expression was a macro call we replace it with
+      # its evaluated result here so that we don't execute it once again in the
+      # final pass
+      if a[2].kind in nkCallKinds:
+        incl a[2].flags, nfSem # bug #10548
+    if sfExportc in s.flags:
+      if s.typ.kind == tyAlias:
+        localError(c.config, name.info, "{.exportc.} not allowed for type aliases")
+      elif s.typ.kind == tyGenericBody:
+        localError(c.config, name.info, "{.exportc.} not allowed for generic types")
+
+    if tfBorrowDot in s.typ.flags:
+      let body = s.typ.skipTypes({tyGenericBody})
+      if body.kind != tyDistinct:
+        # flag might be copied from alias/instantiation:
+        let t = body.skipTypes({tyAlias, tyGenericInst})
+        if not (t.kind == tyDistinct and tfBorrowDot in t.flags):
+          excl s.typ.flags, tfBorrowDot
+          localError(c.config, name.info, "only a 'distinct' type can borrow `.`")
+    let aa = a[2]
     if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and
-       aa.sons[0].kind == nkObjectTy:
+       aa[0].kind == nkObjectTy:
       # give anonymous object a dummy symbol:
       var st = s.typ
-      if st.kind == tyGenericBody: st = st.lastSon
+      if st.kind == tyGenericBody: st = st.typeBodyImpl
       internalAssert c.config, st.kind in {tyPtr, tyRef}
-      internalAssert c.config, st.lastSon.sym == nil
+      internalAssert c.config, st.last.sym == nil
       incl st.flags, tfRefsAnonObj
+      let objTy = st.last
+      # add flags for `ref object` etc to underlying `object`
+      incl(objTy.flags, oldFlags)
+      # {.inheritable, final.} is already disallowed, but
+      # object might have been assumed to be final
+      if tfInheritable in oldFlags and tfFinal in objTy.flags:
+        excl(objTy.flags, tfFinal)
       let obj = newSym(skType, getIdent(c.cache, s.name.s & ":ObjectType"),
-                              getCurrOwner(c), s.info)
-      obj.typ = st.lastSon
-      st.lastSon.sym = obj
-
-
-proc checkForMetaFields(c: PContext; n: PNode) =
-  template checkMeta(t) =
-    if t != nil and t.isMetaType and tfGenericTypeParam notin t.flags:
-      localError(c.config, n.info, errTIsNotAConcreteType % t.typeToString)
+                       c.idgen, getCurrOwner(c), s.info)
+      obj.flags.incl sfGeneratedType
+      let symNode = newSymNode(obj)
+      obj.ast = a.shallowCopy
+      case a[0].kind
+      of nkSym: obj.ast[0] = symNode
+      of nkPragmaExpr:
+        obj.ast[0] = a[0].shallowCopy
+        if a[0][0].kind == nkPostfix:
+          obj.ast[0][0] = a[0][0].shallowCopy
+          obj.ast[0][0][1] = symNode
+        else:
+          obj.ast[0][0] = symNode
+        obj.ast[0][1] = a[0][1]
+      of nkPostfix:
+        obj.ast[0] = a[0].shallowCopy
+        obj.ast[0][1] = symNode
+      else: assert(false)
+      obj.ast[1] = a[1]
+      obj.ast[2] = a[2][0]
+      if sfPure in s.flags:
+        obj.flags.incl sfPure
+      obj.typ = objTy
+      objTy.sym = obj
+  for sk in c.skipTypes:
+    discard semTypeNode(c, sk, nil)
+  c.skipTypes = @[]
+
+proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) =
+  proc checkMeta(c: PContext; n: PNode; t: PType; hasError: var bool; parent: PType) =
+    if t != nil and (t.isMetaType or t.kind == tyNone) and tfGenericTypeParam notin t.flags:
+      if t.kind == tyBuiltInTypeClass and t.len == 1 and t.elementType.kind == tyProc:
+        localError(c.config, n.info, ("'$1' is not a concrete type; " &
+          "for a callback without parameters use 'proc()'") % t.typeToString)
+      elif t.kind == tyNone and parent != nil:
+        # TODO: openarray has the `tfGenericTypeParam` flag & generics
+        # TODO: handle special cases (sink etc.) and views
+        localError(c.config, n.info, errTIsNotAConcreteType % parent.typeToString)
+      else:
+        localError(c.config, n.info, errTIsNotAConcreteType % t.typeToString)
+      hasError = true
 
   if n.isNil: return
   case n.kind
   of nkRecList, nkRecCase:
-    for s in n: checkForMetaFields(c, s)
+    for s in n: checkForMetaFields(c, s, hasError)
   of nkOfBranch, nkElse:
-    checkForMetaFields(c, n.lastSon)
+    checkForMetaFields(c, n.lastSon, hasError)
   of nkSym:
     let t = n.sym.typ
     case t.kind
     of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyLent, tyPtr, tyRef,
-       tyProc, tyGenericInvocation, tyGenericInst, tyAlias, tySink:
-      let start = int ord(t.kind in {tyGenericInvocation, tyGenericInst})
-      for i in start ..< t.sons.len:
-        checkMeta(t.sons[i])
+       tyProc, tyGenericInvocation, tyGenericInst, tyAlias, tySink, tyOwned:
+      let start = ord(t.kind in {tyGenericInvocation, tyGenericInst})
+      for i in start..<t.len:
+        checkMeta(c, n, t[i], hasError, t)
     else:
-      checkMeta(t)
+      checkMeta(c, n, t, hasError, nil)
   else:
     internalAssert c.config, false
 
 proc typeSectionFinalPass(c: PContext, n: PNode) =
-  for i in countup(0, sonsLen(n) - 1):
-    var a = n.sons[i]
+  for i in 0..<n.len:
+    var a = n[i]
     if a.kind == nkCommentStmt: continue
-    let name = typeSectionTypeName(c, a.sons[0])
+    let name = typeSectionTypeName(c, a[0])
     var s = name.sym
+    # check the style here after the pragmas have been processed:
+    styleCheckDef(c, s)
     # compute the type's size and check for illegal recursions:
-    if a.sons[1].kind == nkEmpty:
+    if a[1].kind == nkEmpty:
       var x = a[2]
-      while x.kind in {nkStmtList, nkStmtListExpr} and x.len > 0:
-        x = x.lastSon
-      if x.kind notin {nkObjectTy, nkDistinctTy, nkEnumTy, nkEmpty} and
-          s.typ.kind notin {tyObject, tyEnum}:
-        # type aliases are hard:
-        var t = semTypeNode(c, x, nil)
-        assert t != nil
-        if s.typ != nil and s.typ.kind notin {tyAlias, tySink}:
-          if t.kind in {tyProc, tyGenericInst} and not t.isMetaType:
-            assignType(s.typ, t)
-            s.typ.id = t.id
-          elif t.kind in {tyObject, tyEnum, tyDistinct}:
-            assert s.typ != nil
-            assignType(s.typ, t)
-            s.typ.id = t.id     # same id
-      checkConstructedType(c.config, s.info, s.typ)
-      if s.typ.kind in {tyObject, tyTuple} and not s.typ.n.isNil:
-        checkForMetaFields(c, s.typ.n)
-  instAllTypeBoundOp(c, n.info)
+      if x.kind in nkCallKinds and nfSem in x.flags:
+        discard "already semchecked, see line marked with bug #10548"
+      else:
+        while x.kind in {nkStmtList, nkStmtListExpr} and x.len > 0:
+          x = x.lastSon
+        # we need the 'safeSkipTypes' here because illegally recursive types
+        # can enter at this point, see bug #13763
+        if x.kind notin {nkObjectTy, nkDistinctTy, nkEnumTy, nkEmpty} and
+            s.typ.safeSkipTypes(abstractPtrs).kind notin {tyObject, tyEnum}:
+          # type aliases are hard:
+          var t = semTypeNode(c, x, nil)
+          assert t != nil
+          if s.typ != nil and s.typ.kind notin {tyAlias, tySink}:
+            if t.kind in {tyProc, tyGenericInst} and not t.isMetaType:
+              assignType(s.typ, t)
+              s.typ.itemId = t.itemId
+            elif t.kind in {tyObject, tyEnum, tyDistinct}:
+              assert s.typ != nil
+              assignType(s.typ, t)
+              s.typ.itemId = t.itemId     # same id
+        var hasError = false
+        let baseType = s.typ.safeSkipTypes(abstractPtrs)
+        if baseType.kind in {tyObject, tyTuple} and not baseType.n.isNil and
+          (x.kind in {nkObjectTy, nkTupleTy} or
+           (x.kind in {nkRefTy, nkPtrTy} and x.len == 1 and
+           x[0].kind in {nkObjectTy, nkTupleTy})
+          ):
+          checkForMetaFields(c, baseType.n, hasError)
+        if not hasError:
+          checkConstructedType(c.config, s.info, s.typ)
+  #instAllTypeBoundOp(c, n.info)
 
 
 proc semAllTypeSections(c: PContext; n: PNode): PNode =
@@ -1056,17 +1798,17 @@ proc semAllTypeSections(c: PContext; n: PNode): PNode =
     case n.kind
     of nkIncludeStmt:
       for i in 0..<n.len:
-        var f = checkModuleName(c.config, n.sons[i])
-        if f != InvalidFileIDX:
+        var f = checkModuleName(c.config, n[i])
+        if f != InvalidFileIdx:
           if containsOrIncl(c.includedFiles, f.int):
-            localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f))
+            localError(c.config, n.info, errRecursiveDependencyX % toMsgFilename(c.config, f))
           else:
             let code = c.graph.includeFileCallback(c.graph, c.module, f)
             gatherStmts c, code, result
             excl(c.includedFiles, f.int)
     of nkStmtList:
-      for i in 0 ..< n.len:
-        gatherStmts(c, n.sons[i], result)
+      for i in 0..<n.len:
+        gatherStmts(c, n[i], result)
     of nkTypeSection:
       incl n.flags, nfSem
       typeSectionLeftSidePass(c, n)
@@ -1078,7 +1820,7 @@ proc semAllTypeSections(c: PContext; n: PNode): PNode =
   gatherStmts(c, n, result)
 
   template rec(name) =
-    for i in 0 ..< result.len:
+    for i in 0..<result.len:
       if result[i].kind == nkTypeSection:
         name(c, result[i])
 
@@ -1092,8 +1834,8 @@ proc semAllTypeSections(c: PContext; n: PNode): PNode =
           when setbit: incl n.flags, nfSem
           name(c, n)
         elif n.kind == nkStmtList:
-          for i in 0 ..< n.len:
-            `name rec`(c, n.sons[i])
+          for i in 0..<n.len:
+            `name rec`(c, n[i])
       `name rec`(c, n)
     rec typeSectionLeftSidePass, true
     rec typeSectionRightSidePass
@@ -1113,158 +1855,124 @@ proc semTypeSection(c: PContext, n: PNode): PNode =
 
 proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) =
   s.typ = semProcTypeNode(c, n, genericParams, nil, s.kind)
-  if s.kind notin {skMacro, skTemplate}:
-    if s.typ.sons[0] != nil and s.typ.sons[0].kind == tyStmt:
-      localError(c.config, n.info, "invalid return type: 'stmt'")
 
 proc addParams(c: PContext, n: PNode, kind: TSymKind) =
-  for i in countup(1, sonsLen(n)-1):
-    if n.sons[i].kind == nkSym: addParamOrResult(c, n.sons[i].sym, kind)
+  for i in 1..<n.len:
+    if n[i].kind == nkSym: addParamOrResult(c, n[i].sym, kind)
     else: illFormedAst(n, c.config)
 
 proc semBorrow(c: PContext, n: PNode, s: PSym) =
   # search for the correct alias:
-  var b = searchForBorrowProc(c, c.currentScope.parent, s)
-  if b != nil:
+  var (b, state) = searchForBorrowProc(c, c.currentScope.parent, s)
+  case state
+  of bsMatch:
     # store the alias:
-    n.sons[bodyPos] = newSymNode(b)
+    n[bodyPos] = newSymNode(b)
+    # Carry over the original symbol magic, this is necessary in order to ensure
+    # the semantic pass is correct
+    s.magic = b.magic
+    if b.typ != nil and b.typ.len > 0:
+      s.typ.n[0] = b.typ.n[0]
+    s.typ.flags = b.typ.flags
+  of bsNoDistinct:
+    localError(c.config, n.info, "borrow proc without distinct type parameter is meaningless")
+  of bsReturnNotMatch:
+    localError(c.config, n.info, "borrow from proc return type mismatch: '$1'" % typeToString(b.typ.returnType))
+  of bsGeneric:
+    localError(c.config, n.info, "borrow with generic parameter is not supported")
+  of bsNotSupported:
+    localError(c.config, n.info, "borrow from '$1' is not supported" % $b.name.s)
   else:
     localError(c.config, n.info, errNoSymbolToBorrowFromFound)
 
-proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) =
-  if t != nil:
-    var s = newSym(skResult, getIdent(c.cache, "result"), getCurrOwner(c), info)
+proc swapResult(n: PNode, sRes: PSym, dNode: PNode) =
+  ## Swap nodes that are (skResult) symbols to d(estination)Node.
+  for i in 0..<n.safeLen:
+    if n[i].kind == nkSym and n[i].sym == sRes:
+        n[i] = dNode
+    swapResult(n[i], sRes, dNode)
+
+proc addResult(c: PContext, n: PNode, t: PType, owner: TSymKind) =
+  template genResSym(s) =
+    var s = newSym(skResult, getIdent(c.cache, "result"), c.idgen,
+                   getCurrOwner(c), n.info)
     s.typ = t
     incl(s.flags, sfUsed)
-    addParamOrResult(c, s, owner)
-    c.p.resultSym = s
 
-proc addResultNode(c: PContext, n: PNode) =
-  if c.p.resultSym != nil: addSon(n, newSymNode(c.p.resultSym))
-
-proc copyExcept(n: PNode, i: int): PNode =
-  result = copyNode(n)
-  for j in 0..<n.len:
-    if j != i: result.add(n.sons[j])
-
-proc lookupMacro(c: PContext, n: PNode): PSym =
-  if n.kind == nkSym:
-    result = n.sym
-    if result.kind notin {skMacro, skTemplate}: result = nil
-  else:
-    result = searchInScopes(c, considerQuotedIdent(c, n), {skMacro, skTemplate})
+  if owner == skMacro or t != nil:
+    if n.len > resultPos and n[resultPos] != nil:
+      if n[resultPos].sym.kind != skResult:
+        localError(c.config, n.info, "incorrect result proc symbol")
+      if n[resultPos].sym.owner != getCurrOwner(c):
+        # re-write result with new ownership, and re-write the proc accordingly
+        let sResSym = n[resultPos].sym
+        genResSym(s)
+        n[resultPos] = newSymNode(s)
+        swapResult(n, sResSym, n[resultPos])
+      c.p.resultSym = n[resultPos].sym
+    else:
+      genResSym(s)
+      c.p.resultSym = s
+      n.add newSymNode(c.p.resultSym)
+    addParamOrResult(c, c.p.resultSym, owner)
 
 proc semProcAnnotation(c: PContext, prc: PNode;
                        validPragmas: TSpecialWords): PNode =
-  var n = prc.sons[pragmasPos]
+  # Mirrored with semVarMacroPragma
+  result = nil
+  var n = prc[pragmasPos]
   if n == nil or n.kind == nkEmpty: return
-  for i in countup(0, n.len-1):
-    var it = n.sons[i]
-    var key = if it.kind in nkPragmaCallKinds and it.len >= 1: it.sons[0] else: it
-    let m = lookupMacro(c, key)
-    if m == nil:
-      if key.kind == nkIdent and key.ident.id == ord(wDelegator):
-        if considerQuotedIdent(c, prc.sons[namePos]).s == "()":
-          prc.sons[namePos] = newIdentNode(c.cache.idDelegator, prc.info)
-          prc.sons[pragmasPos] = copyExcept(n, i)
-        else:
-          localError(c.config, prc.info, "only a call operator can be a delegator")
-      continue
-    elif sfCustomPragma in m.flags:
-      continue # semantic check for custom pragma happens later in semProcAux
-
-    # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and
-    # let the semantic checker deal with it:
-    var x = newNodeI(nkCall, n.info)
-    x.add(newSymNode(m))
-    prc.sons[pragmasPos] = copyExcept(n, i)
-    if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0:
-      prc.sons[pragmasPos] = c.graph.emptyNode
-
-    if it.kind in nkPragmaCallKinds and it.len > 1:
-      # pass pragma arguments to the macro too:
-      for i in 1..<it.len:
-        x.add(it.sons[i])
-    x.add(prc)
-
-    # recursion assures that this works for multiple macro annotations too:
-    result = semExpr(c, x)
-    # since a proc annotation can set pragmas, we process these here again.
-    # This is required for SqueakNim-like export pragmas.
-    if result.kind in procDefs and result[namePos].kind == nkSym and
-        result[pragmasPos].kind != nkEmpty:
-      pragma(c, result[namePos].sym, result[pragmasPos], validPragmas)
-    return
-
-proc setGenericParamsMisc(c: PContext; n: PNode): PNode =
-  let orig = n.sons[genericParamsPos]
-  # we keep the original params around for better error messages, see
-  # issue https://github.com/nim-lang/Nim/issues/1713
-  result = semGenericParamList(c, orig)
-  if n.sons[miscPos].kind == nkEmpty:
-    n.sons[miscPos] = newTree(nkBracket, c.graph.emptyNode, orig)
-  else:
-    n.sons[miscPos].sons[1] = orig
-  n.sons[genericParamsPos] = result
+  for i in 0..<n.len:
+    let it = n[i]
+    let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it
+
+    trySuggestPragmas(c, key)
+
+    if isPossibleMacroPragma(c, it, key):
+      # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and
+      # let the semantic checker deal with it:
+      var x = newNodeI(nkCall, key.info)
+      x.add(key)
+
+      if it.kind in nkPragmaCallKinds and it.len > 1:
+        # pass pragma arguments to the macro too:
+        for i in 1..<it.len:
+          x.add(it[i])
+
+      # Drop the pragma from the list, this prevents getting caught in endless
+      # recursion when the nkCall is semanticized
+      prc[pragmasPos] = copyExcept(n, i)
+      if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0:
+        prc[pragmasPos] = c.graph.emptyNode
+
+      x.add(prc)
+
+      # recursion assures that this works for multiple macro annotations too:
+      var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared})
+      if r == nil:
+        # Restore the old list of pragmas since we couldn't process this
+        prc[pragmasPos] = n
+        # No matching macro was found but there's always the possibility this may
+        # be a .pragma. template instead
+        continue
 
-proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
-  # XXX semProcAux should be good enough for this now, we will eventually
-  # remove semLambda
-  result = semProcAnnotation(c, n, lambdaPragmas)
-  if result != nil: return result
-  result = n
-  checkSonsLen(n, bodyPos + 1, c.config)
-  var s: PSym
-  if n[namePos].kind != nkSym:
-    s = newSym(skProc, c.cache.idAnon, getCurrOwner(c), n.info)
-    s.ast = n
-    n.sons[namePos] = newSymNode(s)
-  else:
-    s = n[namePos].sym
-  pushOwner(c, s)
-  openScope(c)
-  var gp: PNode
-  if n.sons[genericParamsPos].kind != nkEmpty:
-    gp = setGenericParamsMisc(c, n)
-  else:
-    gp = newNodeI(nkGenericParams, n.info)
-
-  if n.sons[paramsPos].kind != nkEmpty:
-    semParamList(c, n.sons[paramsPos], gp, s)
-    # paramsTypeCheck(c, s.typ)
-    if sonsLen(gp) > 0 and n.sons[genericParamsPos].kind == nkEmpty:
-      # we have a list of implicit type parameters:
-      n.sons[genericParamsPos] = gp
-  else:
-    s.typ = newProcType(c, n.info)
-  if n.sons[pragmasPos].kind != nkEmpty:
-    pragma(c, s, n.sons[pragmasPos], lambdaPragmas)
-  s.options = c.config.options
-  if n.sons[bodyPos].kind != nkEmpty:
-    if sfImportc in s.flags:
-      localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s)
-    #if efDetermineType notin flags:
-    # XXX not good enough; see tnamedparamanonproc.nim
-    if gp.len == 0 or (gp.len == 1 and tfRetType in gp[0].typ.flags):
-      pushProcCon(c, s)
-      addResult(c, s.typ.sons[0], n.info, skProc)
-      addResultNode(c, n)
-      let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
-      n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s)
-      popProcCon(c)
-    elif efOperand notin flags:
-      localError(c.config, n.info, errGenericLambdaNotAllowed)
-    sideEffectsCheck(c, s)
-  else:
-    localError(c.config, n.info, errImplOfXexpected % s.name.s)
-  closeScope(c)           # close scope for parameters
-  popOwner(c)
-  result.typ = s.typ
+      doAssert r[0].kind == nkSym
+      let m = r[0].sym
+      case m.kind
+      of skMacro: result = semMacroExpr(c, r, r, m, {})
+      of skTemplate: result = semTemplateExpr(c, r, m, {})
+      else:
+        prc[pragmasPos] = n
+        continue
 
-proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
-  var n = n
+      doAssert result != nil
 
-  let original = n.sons[namePos].sym
+      return result
+
+proc semInferredLambda(c: PContext, pt: TypeMapping, n: PNode): PNode =
+  ## used for resolving 'auto' in lambdas based on their callsite
+  var n = n
+  let original = n[namePos].sym
   let s = original #copySym(original, false)
   #incl(s.flags, sfFromGeneric)
   #s.owner = original
@@ -1272,11 +1980,10 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
   n = replaceTypesInBody(c, pt, n, original)
   result = n
   s.ast = result
-  n.sons[namePos].sym = s
-  n.sons[genericParamsPos] = c.graph.emptyNode
+  n[namePos].sym = s
+  n[genericParamsPos] = c.graph.emptyNode
   # for LL we need to avoid wrong aliasing
   let params = copyTree n.typ.n
-  n.sons[paramsPos] = params
   s.typ = n.typ
   for i in 1..<params.len:
     if params[i].typ.kind in {tyTypeDesc, tyGenericParam,
@@ -1288,14 +1995,14 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
   pushOwner(c, s)
   addParams(c, params, skProc)
   pushProcCon(c, s)
-  addResult(c, n.typ.sons[0], n.info, skProc)
-  addResultNode(c, n)
-  let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
-  n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s)
+  addResult(c, n, n.typ.returnType, skProc)
+  s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos], n.typ.returnType))
+  trackProc(c, s, s.ast[bodyPos])
   popProcCon(c)
   popOwner(c)
   closeScope(c)
-
+  if optOwnedRefs in c.config.globalOptions and result.typ != nil:
+    result.typ = makeVarType(c, result.typ, tyOwned)
   # alternative variant (not quite working):
   # var prc = arg[0].sym
   # let inferred = c.semGenerateInstance(c, prc, m.bindings, arg.info)
@@ -1311,96 +2018,233 @@ proc activate(c: PContext, n: PNode) =
     of nkLambdaKinds:
       discard semLambda(c, n, {})
     of nkCallKinds:
-      for i in 1 ..< n.len: activate(c, n[i])
+      for i in 1..<n.len: activate(c, n[i])
     else:
       discard
 
 proc maybeAddResult(c: PContext, s: PSym, n: PNode) =
-  if s.typ.sons[0] != nil and not
-      (s.kind == skIterator and s.typ.callConv != ccClosure):
-    addResult(c, s.typ.sons[0], n.info, s.kind)
-    addResultNode(c, n)
+  if s.kind == skMacro:
+    let resultType = sysTypeFromName(c.graph, n.info, "NimNode")
+    addResult(c, n, resultType, s.kind)
+  elif s.typ.returnType != nil and not isInlineIterator(s.typ):
+    addResult(c, n, s.typ.returnType, s.kind)
+
+proc canonType(c: PContext, t: PType): PType =
+  if t.kind == tySequence:
+    result = c.graph.sysTypes[tySequence]
+  else:
+    result = t
 
-proc semOverride(c: PContext, s: PSym, n: PNode) =
-  case s.name.s.normalize
-  of "=destroy":
-    let t = s.typ
-    var noError = false
-    if t.len == 2 and t.sons[0] == nil and t.sons[1].kind == tyVar:
-      var obj = t.sons[1].sons[0]
-      while true:
-        incl(obj.flags, tfHasAsgn)
-        if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon
-        elif obj.kind == tyGenericInvocation: obj = obj.sons[0]
-        else: break
-      if obj.kind in {tyObject, tyDistinct}:
-        if obj.destructor.isNil:
-          obj.destructor = s
-        else:
-          localError(c.config, n.info, errGenerated,
-            "cannot bind another '" & s.name.s & "' to: " & typeToString(obj))
-        noError = true
-    if not noError and sfSystemModule notin s.owner.flags:
+proc prevDestructor(c: PContext; prevOp: PSym; obj: PType; info: TLineInfo) =
+  var msg = "cannot bind another '" & prevOp.name.s & "' to: " & typeToString(obj)
+  if sfOverridden notin prevOp.flags:
+    msg.add "; previous declaration was constructed here implicitly: " & (c.config $ prevOp.info)
+  else:
+    msg.add "; previous declaration was here: " & (c.config $ prevOp.info)
+  localError(c.config, info, errGenerated, msg)
+
+proc whereToBindTypeHook(c: PContext; t: PType): PType =
+  result = t
+  while true:
+    if result.kind in {tyGenericBody, tyGenericInst}: result = result.skipModifier
+    elif result.kind == tyGenericInvocation: result = result[0]
+    else: break
+  if result.kind in {tyObject, tyDistinct, tySequence, tyString}:
+    result = canonType(c, result)
+
+proc bindDupHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
+  let t = s.typ
+  var noError = false
+  let cond = t.len == 2 and t.returnType != nil
+
+  if cond:
+    var obj = t.firstParamType
+    while true:
+      incl(obj.flags, tfHasAsgn)
+      if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier
+      elif obj.kind == tyGenericInvocation: obj = obj.genericHead
+      else: break
+
+    var res = t.returnType
+    while true:
+      if res.kind in {tyGenericBody, tyGenericInst}: res = res.skipModifier
+      elif res.kind == tyGenericInvocation: res = res.genericHead
+      else: break
+
+    if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, res):
+      obj = canonType(c, obj)
+      let ao = getAttachedOp(c.graph, obj, op)
+      if ao == s:
+        discard "forward declared destructor"
+      elif ao.isNil and tfCheckedForDestructor notin obj.flags:
+        setAttachedOp(c.graph, c.module.position, obj, op, s)
+      else:
+        prevDestructor(c, ao, obj, n.info)
+      noError = true
+      if obj.owner.getModule != s.getModule:
+        localError(c.config, n.info, errGenerated,
+          "type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
+
+  if not noError and sfSystemModule notin s.owner.flags:
+    localError(c.config, n.info, errGenerated,
+      "signature for '=dup' must be proc[T: object](x: T): T")
+
+  incl(s.flags, sfUsed)
+  incl(s.flags, sfOverridden)
+
+proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp; suppressVarDestructorWarning = false) =
+  let t = s.typ
+  var noError = false
+  let cond = case op
+             of attachedWasMoved:
+               t.len == 2 and t.returnType == nil and t.firstParamType.kind == tyVar
+             of attachedTrace:
+               t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar and t[2].kind == tyPointer
+             of attachedDestructor:
+               if c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
+                 t.len == 2 and t.returnType == nil
+               else:
+                 t.len == 2 and t.returnType == nil and t.firstParamType.kind == tyVar
+             else:
+               t.len >= 2 and t.returnType == nil
+
+  if cond:
+    var obj = t.firstParamType.skipTypes({tyVar})
+    while true:
+      incl(obj.flags, tfHasAsgn)
+      if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier
+      elif obj.kind == tyGenericInvocation: obj = obj.genericHead
+      else: break
+    if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
+      if (not suppressVarDestructorWarning) and op == attachedDestructor and t.firstParamType.kind == tyVar and
+          c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
+        message(c.config, n.info, warnDeprecated, "A custom '=destroy' hook which takes a 'var T' parameter is deprecated; it should take a 'T' parameter")
+      obj = canonType(c, obj)
+      let ao = getAttachedOp(c.graph, obj, op)
+      if ao == s:
+        discard "forward declared destructor"
+      elif ao.isNil and tfCheckedForDestructor notin obj.flags:
+        setAttachedOp(c.graph, c.module.position, obj, op, s)
+      else:
+        prevDestructor(c, ao, obj, n.info)
+      noError = true
+      if obj.owner.getModule != s.getModule:
+        localError(c.config, n.info, errGenerated,
+          "type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
+  if not noError and sfSystemModule notin s.owner.flags:
+    case op
+    of attachedTrace:
+      localError(c.config, n.info, errGenerated,
+        "signature for '=trace' must be proc[T: object](x: var T; env: pointer)")
+    of attachedDestructor:
+      if c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
+        localError(c.config, n.info, errGenerated,
+          "signature for '=destroy' must be proc[T: object](x: var T) or proc[T: object](x: T)")
+      else:
+        localError(c.config, n.info, errGenerated,
+          "signature for '=destroy' must be proc[T: object](x: var T)")
+    else:
       localError(c.config, n.info, errGenerated,
         "signature for '" & s.name.s & "' must be proc[T: object](x: var T)")
-    incl(s.flags, sfUsed)
+  incl(s.flags, sfUsed)
+  incl(s.flags, sfOverridden)
+
+proc semOverride(c: PContext, s: PSym, n: PNode) =
+  let name = s.name.s.normalize
+  case name
+  of "=destroy":
+    bindTypeHook(c, s, n, attachedDestructor)
+    if s.ast != nil:
+      if s.ast[pragmasPos].kind == nkEmpty:
+        s.ast[pragmasPos] = newNodeI(nkPragma, s.info)
+      s.ast[pragmasPos].add newTree(nkExprColonExpr,
+          newIdentNode(c.cache.getIdent("raises"),  s.info), newNodeI(nkBracket, s.info))
   of "deepcopy", "=deepcopy":
     if s.typ.len == 2 and
-        s.typ.sons[1].skipTypes(abstractInst).kind in {tyRef, tyPtr} and
-        sameType(s.typ.sons[1], s.typ.sons[0]):
+        s.typ.firstParamType.skipTypes(abstractInst).kind in {tyRef, tyPtr} and
+        sameType(s.typ.firstParamType, s.typ.returnType):
       # Note: we store the deepCopy in the base of the pointer to mitigate
       # the problem that pointers are structural types:
-      var t = s.typ.sons[1].skipTypes(abstractInst).lastSon.skipTypes(abstractInst)
+      var t = s.typ.firstParamType.skipTypes(abstractInst).elementType.skipTypes(abstractInst)
       while true:
-        if t.kind == tyGenericBody: t = t.lastSon
-        elif t.kind == tyGenericInvocation: t = t.sons[0]
+        if t.kind == tyGenericBody: t = t.typeBodyImpl
+        elif t.kind == tyGenericInvocation: t = t.genericHead
         else: break
-      if t.kind in {tyObject, tyDistinct, tyEnum}:
-        if t.deepCopy.isNil: t.deepCopy = s
+      if t.kind in {tyObject, tyDistinct, tyEnum, tySequence, tyString}:
+        if getAttachedOp(c.graph, t, attachedDeepCopy).isNil:
+          setAttachedOp(c.graph, c.module.position, t, attachedDeepCopy, s)
         else:
           localError(c.config, n.info, errGenerated,
                      "cannot bind another 'deepCopy' to: " & typeToString(t))
       else:
         localError(c.config, n.info, errGenerated,
                    "cannot bind 'deepCopy' to: " & typeToString(t))
+
+      if t.owner.getModule != s.getModule:
+        localError(c.config, n.info, errGenerated,
+          "type bound operation `" & name & "` can be defined only in the same module with its type (" & t.typeToString() & ")")
+
     else:
       localError(c.config, n.info, errGenerated,
                  "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T")
     incl(s.flags, sfUsed)
-  of "=", "=sink":
+    incl(s.flags, sfOverridden)
+  of "=", "=copy", "=sink":
     if s.magic == mAsgn: return
     incl(s.flags, sfUsed)
+    incl(s.flags, sfOverridden)
+    if name == "=":
+      message(c.config, n.info, warnDeprecated, "Overriding `=` hook is deprecated; Override `=copy` hook instead")
     let t = s.typ
-    if t.len == 3 and t.sons[0] == nil and t.sons[1].kind == tyVar:
-      var obj = t.sons[1].sons[0]
+    if t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar:
+      var obj = t.firstParamType.elementType
       while true:
         incl(obj.flags, tfHasAsgn)
-        if obj.kind == tyGenericBody: obj = obj.lastSon
-        elif obj.kind == tyGenericInvocation: obj = obj.sons[0]
+        if obj.kind == tyGenericBody: obj = obj.skipModifier
+        elif obj.kind == tyGenericInvocation: obj = obj.genericHead
         else: break
-      var objB = t.sons[2]
+      var objB = t[2]
       while true:
-        if objB.kind == tyGenericBody: objB = objB.lastSon
+        if objB.kind == tyGenericBody: objB = objB.skipModifier
         elif objB.kind in {tyGenericInvocation, tyGenericInst}:
-          objB = objB.sons[0]
+          objB = objB.genericHead
         else: break
-      if obj.kind in {tyObject, tyDistinct} and sameType(obj, objB):
-        let opr = if s.name.s == "=": addr(obj.assignment) else: addr(obj.sink)
-        if opr[].isNil:
-          opr[] = s
+      if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB):
+        # attach these ops to the canonical tySequence
+        obj = canonType(c, obj)
+        #echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj)
+        let k = if name == "=" or name == "=copy": attachedAsgn else: attachedSink
+        let ao = getAttachedOp(c.graph, obj, k)
+        if ao == s:
+          discard "forward declared op"
+        elif ao.isNil and tfCheckedForDestructor notin obj.flags:
+          setAttachedOp(c.graph, c.module.position, obj, k, s)
         else:
+          prevDestructor(c, ao, obj, n.info)
+        if obj.owner.getModule != s.getModule:
           localError(c.config, n.info, errGenerated,
-                     "cannot bind another '" & s.name.s & "' to: " & typeToString(obj))
+            "type bound operation `" & name & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
+
         return
     if sfSystemModule notin s.owner.flags:
       localError(c.config, n.info, errGenerated,
                 "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)")
+  of "=trace":
+    if s.magic != mTrace:
+      bindTypeHook(c, s, n, attachedTrace)
+  of "=wasmoved":
+    if s.magic != mWasMoved:
+      bindTypeHook(c, s, n, attachedWasMoved)
+  of "=dup":
+    if s.magic != mDup:
+      bindDupHook(c, s, n, attachedDup)
   else:
-    if sfOverriden in s.flags:
+    if sfOverridden in s.flags:
       localError(c.config, n.info, errGenerated,
                  "'destroy' or 'deepCopy' expected for 'override'")
 
 proc cursorInProcAux(conf: ConfigRef; n: PNode): bool =
+  result = false
   if inCheckpoint(n.info, conf.m.trackPos) != cpNone: return true
   for i in 0..<n.safeLen:
     if cursorInProcAux(conf, n[i]): return true
@@ -1408,70 +2252,123 @@ proc cursorInProcAux(conf: ConfigRef; n: PNode): bool =
 proc cursorInProc(conf: ConfigRef; n: PNode): bool =
   if n.info.fileIndex == conf.m.trackPos.fileIndex:
     result = cursorInProcAux(conf, n)
-
-type
-  TProcCompilationSteps = enum
-    stepRegisterSymbol,
-    stepDetermineType,
+  else:
+    result = false
 
 proc hasObjParam(s: PSym): bool =
+  result = false
   var t = s.typ
-  for col in countup(1, sonsLen(t)-1):
-    if skipTypes(t.sons[col], skipPtrs).kind == tyObject:
+  for col in 1..<t.len:
+    if skipTypes(t[col], skipPtrs).kind == tyObject:
       return true
 
 proc finishMethod(c: PContext, s: PSym) =
   if hasObjParam(s):
-    methodDef(c.graph, s, false)
+    methodDef(c.graph, c.idgen, s)
+
+proc semCppMember(c: PContext; s: PSym; n: PNode) =
+  if sfImportc notin s.flags:
+    let isVirtual = sfVirtual in s.flags
+    let isCtor = sfConstructor in s.flags
+    let pragmaName = if isVirtual: "virtual" elif isCtor: "constructor" else: "member"
+    if c.config.backend == backendCpp:
+      if s.typ.len < 2 and not isCtor:
+        localError(c.config, n.info, pragmaName & " must have at least one parameter")
+      for son in s.typ.signature:
+        if son!=nil and son.isMetaType:
+          localError(c.config, n.info, pragmaName & " unsupported for generic routine")
+      var typ: PType
+      if isCtor:
+        typ = s.typ.returnType
+        if typ == nil or typ.kind != tyObject:
+          localError(c.config, n.info, "constructor must return an object")
+        if sfImportc in typ.sym.flags:
+          localError(c.config, n.info, "constructor in an imported type needs importcpp pragma")
+      else:
+        typ = s.typ.firstParamType
+      if typ.kind == tyPtr and not isCtor:
+        typ = typ.elementType
+      if typ.kind != tyObject:
+        localError(c.config, n.info, pragmaName & " must be either ptr to object or object type.")
+      if typ.owner.id == s.owner.id and c.module.id == s.owner.id:
+        c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s
+      else:
+        localError(c.config, n.info,
+          pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope")
+    else:
+      localError(c.config, n.info, pragmaName & " procs are only supported in C++")
+  else:
+    var typ = s.typ.returnType
+    if typ != nil and typ.kind == tyObject and typ.itemId notin c.graph.initializersPerType:
+      var initializerCall = newTree(nkCall, newSymNode(s))
+      var isInitializer = n[paramsPos].len > 1
+      for i in  1..<n[paramsPos].len:
+        let p = n[paramsPos][i]
+        let val = p[^1]
+        if val.kind == nkEmpty:
+          isInitializer = false
+          break
+        var j = 0
+        while p[j].sym.kind == skParam:
+          initializerCall.add val
+          inc j
+      if isInitializer:
+        c.graph.initializersPerType[typ.itemId] = initializerCall
 
 proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
-  if isGenericRoutine(s):
+  if s.isGenericRoutine:
     let tt = s.typ
     var foundObj = false
     # we start at 1 for now so that tparsecombnum continues to compile.
     # XXX Revisit this problem later.
-    for col in countup(1, sonsLen(tt)-1):
-      let t = tt.sons[col]
+    for col in 1..<tt.len:
+      let t = tt[col]
       if t != nil and t.kind == tyGenericInvocation:
-        var x = skipTypes(t.sons[0], {tyVar, tyLent, tyPtr, tyRef, tyGenericInst,
-                                      tyGenericInvocation, tyGenericBody,
-                                      tyAlias, tySink})
-        if x.kind == tyObject and t.len-1 == n.sons[genericParamsPos].len:
+        var x = skipTypes(t.genericHead, {tyVar, tyLent, tyPtr, tyRef, tyGenericInst,
+                                 tyGenericInvocation, tyGenericBody,
+                                 tyAlias, tySink, tyOwned})
+        if x.kind == tyObject and t.len-1 == n[genericParamsPos].len:
           foundObj = true
-          x.methods.safeAdd((col,s))
-    if not foundObj:
-      message(c.config, n.info, warnDeprecated, "generic method not attachable to object type")
+          addMethodToGeneric(c.graph, c.module.position, x, col, s)
+    message(c.config, n.info, warnDeprecated, "generic methods are deprecated")
+    #if not foundObj:
+    #  message(c.config, n.info, warnDeprecated, "generic method not attachable to object type is deprecated")
   else:
     # why check for the body? bug #2400 has none. Checking for sfForward makes
     # no sense either.
-    # and result.sons[bodyPos].kind != nkEmpty:
+    # and result[bodyPos].kind != nkEmpty:
     if hasObjParam(s):
-      methodDef(c.graph, s, fromCache=false)
+      methodDef(c.graph, c.idgen, s)
     else:
       localError(c.config, n.info, "'method' needs a parameter that has an object type")
 
 proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
-                validPragmas: TSpecialWords,
-                phase = stepRegisterSymbol): PNode =
+                validPragmas: TSpecialWords, flags: TExprFlags = {}): PNode =
   result = semProcAnnotation(c, n, validPragmas)
   if result != nil: return result
   result = n
-  checkSonsLen(n, bodyPos + 1, c.config)
+  checkMinSonsLen(n, bodyPos + 1, c.config)
+
+  let
+    isAnon = n[namePos].kind == nkEmpty
+    isHighlight = c.config.ideCmd == ideHighlight
+
   var s: PSym
-  var typeIsDetermined = false
-  var isAnon = false
-  if n[namePos].kind != nkSym:
-    assert phase == stepRegisterSymbol
-
-    if n[namePos].kind == nkEmpty:
-      s = newSym(kind, c.cache.idAnon, getCurrOwner(c), n.info)
-      incl(s.flags, sfUsed)
-      isAnon = true
-    else:
-      s = semIdentDef(c, n.sons[0], kind)
-    n.sons[namePos] = newSymNode(s)
-    s.ast = n
-    #s.scope = c.currentScope
+
+  case n[namePos].kind
+  of nkEmpty:
+    s = newSym(kind, c.cache.idAnon, c.idgen, c.getCurrOwner, n.info)
+    s.flags.incl sfUsed
+    n[namePos] = newSymNode(s)
+  of nkSym:
+    s = n[namePos].sym
+    s.owner = c.getCurrOwner
+  else:
+    # Highlighting needs to be done early so the position for
+    # name isn't changed (see taccent_highlight). We don't want to check if this is the
+    # defintion yet since we are missing some info (comments, side effects)
+    s = semIdentDef(c, n[namePos], kind, reportToNimsuggest=isHighlight)
+    n[namePos] = newSymNode(s)
     when false:
       # disable for now
       if sfNoForward in c.module.flags and
@@ -1479,191 +2376,270 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
         addInterfaceOverloadableSymAt(c, c.currentScope, s)
         s.flags.incl sfForward
         return
-  else:
-    s = n[namePos].sym
-    s.owner = getCurrOwner(c)
-    typeIsDetermined = s.typ == nil
-    s.ast = n
-    #s.scope = c.currentScope
 
+  assert s.kind in skProcKinds
+
+  s.ast = n
   s.options = c.config.options
+  #s.scope = c.currentScope
+  if s.kind in {skMacro, skTemplate}:
+    # push noalias flag at first to prevent unwanted recursive calls:
+    incl(s.flags, sfNoalias)
 
-  # before compiling the proc body, set as current the scope
+  # before compiling the proc params & body, set as current the scope
   # where the proc was declared
-  let oldScope = c.currentScope
-  #c.currentScope = s.scope
+  let declarationScope = c.currentScope
   pushOwner(c, s)
   openScope(c)
-  var gp: PNode
-  if n.sons[genericParamsPos].kind != nkEmpty:
-    gp = setGenericParamsMisc(c, n)
-  else:
-    gp = newNodeI(nkGenericParams, n.info)
+
   # process parameters:
-  if n.sons[paramsPos].kind != nkEmpty:
-    semParamList(c, n.sons[paramsPos], gp, s)
-    if sonsLen(gp) > 0:
-      if n.sons[genericParamsPos].kind == nkEmpty:
-        # we have a list of implicit type parameters:
-        n.sons[genericParamsPos] = gp
-        # check for semantics again:
-        # semParamList(c, n.sons[ParamsPos], nil, s)
+  # generic parameters, parameters, and also the implicit generic parameters
+  # within are analysed. This is often the entirety of their semantic analysis
+  # but later we will have to do a check for forward declarations, which can by
+  # way of pragmas, default params, and so on invalidate this parsing.
+  # Nonetheless, we need to carry out this analysis to perform the search for a
+  # potential forward declaration.
+  setGenericParamsMisc(c, n)
+
+  if n[paramsPos].kind != nkEmpty:
+    semParamList(c, n[paramsPos], n[genericParamsPos], s)
   else:
     s.typ = newProcType(c, n.info)
+
+  if n[genericParamsPos].safeLen == 0:
+    # if there exist no explicit or implicit generic parameters, then this is
+    # at most a nullary generic (generic with no type params). Regardless of
+    # whether it's a nullary generic or non-generic, we restore the original.
+    # In the case of `nkEmpty` it's non-generic and an empty `nkGeneircParams`
+    # is a nullary generic.
+    #
+    # Remarks about nullary generics vs non-generics:
+    # The difference between a non-generic and nullary generic is minor in
+    # most cases but there are subtle and significant differences as well.
+    # Due to instantiation that generic procs go through, a static echo in the
+    # body of a nullary  generic will not be executed immediately, as it's
+    # instantiated and not immediately evaluated.
+    n[genericParamsPos] = n[miscPos][1]
+    n[miscPos] = c.graph.emptyNode
+
   if tfTriggersCompileTime in s.typ.flags: incl(s.flags, sfCompileTime)
-  if n.sons[patternPos].kind != nkEmpty:
-    n.sons[patternPos] = semPattern(c, n.sons[patternPos])
+  if n[patternPos].kind != nkEmpty:
+    n[patternPos] = semPattern(c, n[patternPos], s)
   if s.kind == skIterator:
     s.typ.flags.incl(tfIterator)
   elif s.kind == skFunc:
     incl(s.flags, sfNoSideEffect)
     incl(s.typ.flags, tfNoSideEffect)
-  var proto = searchForProc(c, oldScope, s)
-  if proto == nil or isAnon:
-    if s.kind == skIterator:
-      if s.typ.callConv != ccClosure:
-        s.typ.callConv = if isAnon: ccClosure else: ccInline
-    else:
+
+  var (proto, comesFromShadowScope) =
+      if isAnon: (nil, false)
+      else: searchForProc(c, declarationScope, s)
+  if proto == nil and sfForward in s.flags and n[bodyPos].kind != nkEmpty:
+    ## In cases such as a macro generating a proc with a gensymmed name we
+    ## know `searchForProc` will not find it and sfForward will be set. In
+    ## such scenarios the sym is shared between forward declaration and we
+    ## can treat the `s` as the proto.
+    ## To differentiate between that happening and a macro just returning a
+    ## forward declaration that has been typed before we check if the body
+    ## is not empty. This has the sideeffect of allowing multiple forward
+    ## declarations if they share the same sym.
+    ## See the "doubly-typed forward decls" case in tmacros_issues.nim
+    proto = s
+  let hasProto = proto != nil
+
+  # set the default calling conventions
+  case s.kind
+  of skIterator:
+    if s.typ.callConv != ccClosure:
+      s.typ.callConv = if isAnon: ccClosure else: ccInline
+  of skMacro, skTemplate:
+    # we don't bother setting calling conventions for macros and templates
+    discard
+  else:
+    # NB: procs with a forward decl have theirs determined by the forward decl
+    if not hasProto:
+      # in this case we're either a forward declaration or we're an impl without
+      # a forward decl. We set the calling convention or will be set during
+      # pragma analysis further down.
       s.typ.callConv = lastOptionEntry(c).defaultCC
-    # add it here, so that recursive procs are possible:
-    if sfGenSym in s.flags: discard
-    elif kind in OverloadableSyms:
-      if not typeIsDetermined:
-        addInterfaceOverloadableSymAt(c, oldScope, s)
-    else:
-      if not typeIsDetermined:
-        addInterfaceDeclAt(c, oldScope, s)
-    if n.sons[pragmasPos].kind != nkEmpty:
-      pragma(c, s, n.sons[pragmasPos], validPragmas)
+
+  if not hasProto and sfGenSym notin s.flags: #and not isAnon:
+    if s.kind in OverloadableSyms:
+      addInterfaceOverloadableSymAt(c, declarationScope, s)
     else:
-      implicitPragmas(c, s, n, validPragmas)
+      addInterfaceDeclAt(c, declarationScope, s)
+
+  pragmaCallable(c, s, n, validPragmas)
+  if not hasProto:
+    implicitPragmas(c, s, n.info, validPragmas)
+
+  if n[pragmasPos].kind != nkEmpty and sfBorrow notin s.flags:
+    setEffectsForProcType(c.graph, s.typ, n[pragmasPos], s)
+  s.typ.flags.incl tfEffectSystemWorkaround
+
+  # To ease macro generation that produce forwarded .async procs we now
+  # allow a bit redundancy in the pragma declarations. The rule is
+  # a prototype's pragma list must be a superset of the current pragma
+  # list.
+  # XXX This needs more checks eventually, for example that external
+  # linking names do agree:
+  if hasProto and (
+      # calling convention mismatch
+      tfExplicitCallConv in s.typ.flags and proto.typ.callConv != s.typ.callConv or
+      # implementation has additional pragmas
+      proto.typ.flags < s.typ.flags):
+    localError(c.config, n[pragmasPos].info, errPragmaOnlyInHeaderOfProcX %
+      ("'" & proto.name.s & "' from " & c.config$proto.info &
+        " '" & s.name.s & "' from " & c.config$s.info))
+
+  styleCheckDef(c, s)
+  if hasProto:
+    onDefResolveForward(n[namePos].info, proto)
   else:
-    if n.sons[pragmasPos].kind != nkEmpty:
-      pragma(c, s, n.sons[pragmasPos], validPragmas)
-      # To ease macro generation that produce forwarded .async procs we now
-      # allow a bit redudancy in the pragma declarations. The rule is
-      # a prototype's pragma list must be a superset of the current pragma
-      # list.
-      # XXX This needs more checks eventually, for example that external
-      # linking names do agree:
-      if proto.typ.callConv != s.typ.callConv or proto.typ.flags < s.typ.flags:
-        localError(c.config, n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProcX %
-          ("'" & proto.name.s & "' from " & c.config$proto.info))
-    if sfForward notin proto.flags:
-      wrongRedefinition(c, n.info, proto.name.s)
-    excl(proto.flags, sfForward)
+    onDef(n[namePos].info, s)
+
+  if hasProto:
+    if sfForward notin proto.flags and proto.magic == mNone:
+      wrongRedefinition(c, n.info, proto.name.s, proto.info)
+    if not comesFromShadowScope:
+      excl(proto.flags, sfForward)
+      incl(proto.flags, sfWasForwarded)
+    suggestSym(c.graph, s.info, proto, c.graph.usageSym)
     closeScope(c)         # close scope with wrong parameter symbols
     openScope(c)          # open scope for old (correct) parameter symbols
-    if proto.ast.sons[genericParamsPos].kind != nkEmpty:
-      addGenericParamListToScope(c, proto.ast.sons[genericParamsPos])
+    if proto.ast[genericParamsPos].isGenericParams:
+      addGenericParamListToScope(c, proto.ast[genericParamsPos])
     addParams(c, proto.typ.n, proto.kind)
     proto.info = s.info       # more accurate line information
-    s.typ = proto.typ
     proto.options = s.options
     s = proto
-    n.sons[genericParamsPos] = proto.ast.sons[genericParamsPos]
-    n.sons[paramsPos] = proto.ast.sons[paramsPos]
-    n.sons[pragmasPos] = proto.ast.sons[pragmasPos]
-    if n.sons[namePos].kind != nkSym: internalError(c.config, n.info, "semProcAux")
-    n.sons[namePos].sym = proto
-    if importantComments(c.config) and not isNil(proto.ast.comment):
+    n[genericParamsPos] = proto.ast[genericParamsPos]
+    n[paramsPos] = proto.ast[paramsPos]
+    n[pragmasPos] = proto.ast[pragmasPos]
+    if n[namePos].kind != nkSym: internalError(c.config, n.info, "semProcAux")
+    n[namePos].sym = proto
+    if importantComments(c.config) and proto.ast.comment.len > 0:
       n.comment = proto.ast.comment
     proto.ast = n             # needed for code generation
     popOwner(c)
     pushOwner(c, s)
 
-  if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n)
-  if s.name.s[0] in {'.', '('}:
-    if s.name.s in [".", ".()", ".="] and {destructor, dotOperators} * c.features == {}:
-      localError(c.config, n.info, "the overloaded " & s.name.s &
-        " operator has to be enabled with {.experimental: \"dotOperators\".}")
-    elif s.name.s == "()" and callOperator notin c.features:
-      localError(c.config, n.info, "the overloaded " & s.name.s &
-        " operator has to be enabled with {.experimental: \"callOperator\".}")
-
-  if n.sons[bodyPos].kind != nkEmpty:
-    # for DLL generation it is annoying to check for sfImportc!
-    if sfBorrow in s.flags:
-      localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s)
-    let usePseudoGenerics = kind in {skMacro, skTemplate}
-    # Macros and Templates can have generic parameters, but they are
-    # only used for overload resolution (there is no instantiation of
-    # the symbol, so we must process the body now)
-    if not usePseudoGenerics and c.config.ideCmd in {ideSug, ideCon} and not
-        cursorInProc(c.config, n.sons[bodyPos]):
-      discard "speed up nimsuggest"
+  if not isAnon:
+    if sfOverridden in s.flags or s.name.s[0] == '=': semOverride(c, s, n)
+    elif s.name.s[0] in {'.', '('}:
+      if s.name.s in [".", ".()", ".="] and {Feature.destructor, dotOperators} * c.features == {}:
+        localError(c.config, n.info, "the overloaded " & s.name.s &
+          " operator has to be enabled with {.experimental: \"dotOperators\".}")
+      elif s.name.s == "()" and callOperator notin c.features:
+        localError(c.config, n.info, "the overloaded " & s.name.s &
+          " operator has to be enabled with {.experimental: \"callOperator\".}")
+
+  if sfBorrow in s.flags and c.config.cmd notin cmdDocLike:
+    result[bodyPos] = c.graph.emptyNode
+
+  if sfCppMember * s.flags != {} and sfWasForwarded notin s.flags:
+    semCppMember(c, s, n)
+
+  if n[bodyPos].kind != nkEmpty and sfError notin s.flags:
+    # for DLL generation we allow sfImportc to have a body, for use in VM
+    if c.config.ideCmd in {ideSug, ideCon} and s.kind notin {skMacro, skTemplate} and not
+        cursorInProc(c.config, n[bodyPos]):
+      # speed up nimsuggest
       if s.kind == skMethod: semMethodPrototype(c, s, n)
+    elif isAnon:
+      let gp = n[genericParamsPos]
+      if gp.kind == nkEmpty or (gp.len == 1 and tfRetType in gp[0].typ.flags):
+        # absolutely no generics (empty) or a single generic return type are
+        # allowed, everything else, including a nullary generic is an error.
+        pushProcCon(c, s)
+        addResult(c, n, s.typ.returnType, skProc)
+        s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos], s.typ.returnType))
+        trackProc(c, s, s.ast[bodyPos])
+        popProcCon(c)
+      elif efOperand notin flags:
+        localError(c.config, n.info, errGenericLambdaNotAllowed)
     else:
       pushProcCon(c, s)
-      if n.sons[genericParamsPos].kind == nkEmpty or usePseudoGenerics:
-        if not usePseudoGenerics: paramsTypeCheck(c, s.typ)
-
-        c.p.wasForwarded = proto != nil
+      if n[genericParamsPos].kind == nkEmpty or s.kind in {skMacro, skTemplate}:
+        # Macros and Templates can have generic parameters, but they are only
+        # used for overload resolution (there is no instantiation of the symbol)
+        if s.kind notin {skMacro, skTemplate} and s.magic == mNone: paramsTypeCheck(c, s.typ)
         maybeAddResult(c, s, n)
-        if s.kind == skMethod: semMethodPrototype(c, s, n)
-
-        if lfDynamicLib notin s.loc.flags:
-          # no semantic checking for importc:
-          let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
-          # unfortunately we cannot skip this step when in 'system.compiles'
-          # context as it may even be evaluated in 'system.compiles':
-          n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s)
+        let resultType =
+          if s.kind == skMacro:
+            sysTypeFromName(c.graph, n.info, "NimNode")
+          elif not isInlineIterator(s.typ):
+            s.typ.returnType
+          else:
+            nil
+        # semantic checking also needed with importc in case used in VM
+        s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos], resultType))
+        # unfortunately we cannot skip this step when in 'system.compiles'
+        # context as it may even be evaluated in 'system.compiles':
+        trackProc(c, s, s.ast[bodyPos])
       else:
-        if s.typ.sons[0] != nil and kind != skIterator:
-          addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nil, n.info))
+        if (s.typ.returnType != nil and s.kind != skIterator):
+          addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), c.idgen, s, n.info))
 
         openScope(c)
-        n.sons[bodyPos] = semGenericStmt(c, n.sons[bodyPos])
+        n[bodyPos] = semGenericStmt(c, n[bodyPos])
         closeScope(c)
-        fixupInstantiatedSymbols(c, s)
-        if s.kind == skMethod: semMethodPrototype(c, s, n)
-      if sfImportc in s.flags:
-        # so we just ignore the body after semantic checking for importc:
-        n.sons[bodyPos] = c.graph.emptyNode
+        if s.magic == mNone:
+          fixupInstantiatedSymbols(c, s)
+      if s.kind == skMethod: semMethodPrototype(c, s, n)
       popProcCon(c)
   else:
     if s.kind == skMethod: semMethodPrototype(c, s, n)
-    if proto != nil: localError(c.config, n.info, errImplOfXexpected % proto.name.s)
-    if {sfImportc, sfBorrow} * s.flags == {} and s.magic == mNone:
+    if hasProto: localError(c.config, n.info, errImplOfXexpected % proto.name.s)
+    if {sfImportc, sfBorrow, sfError} * s.flags == {} and s.magic == mNone:
+      # this is a forward declaration and we're building the prototype
+      if s.kind in {skProc, skFunc} and s.typ.returnType != nil and s.typ.returnType.kind == tyAnything:
+        localError(c.config, n[paramsPos][0].info, "return type 'auto' cannot be used in forward declarations")
+
       incl(s.flags, sfForward)
+      incl(s.flags, sfWasForwarded)
     elif sfBorrow in s.flags: semBorrow(c, n, s)
   sideEffectsCheck(c, s)
+
   closeScope(c)           # close scope for parameters
   # c.currentScope = oldScope
   popOwner(c)
-  if n.sons[patternPos].kind != nkEmpty:
+  if n[patternPos].kind != nkEmpty:
     c.patterns.add(s)
   if isAnon:
-    n.kind = nkLambda
+    n.transitionSonsKind(nkLambda)
     result.typ = s.typ
-  if isTopLevel(c) and s.kind != skIterator and
-      s.typ.callConv == ccClosure:
+    if optOwnedRefs in c.config.globalOptions:
+      result.typ = makeVarType(c, result.typ, tyOwned)
+  elif isTopLevel(c) and s.kind != skIterator and s.typ.callConv == ccClosure:
     localError(c.config, s.info, "'.closure' calling convention for top level routines is invalid")
 
+  # Prevent double highlights. We already highlighted before.
+  # When not highlighting we still need to allow for suggestions though
+  if not isHighlight:
+    suggestSym(c.graph, s.info, s, c.graph.usageSym)
+
 proc determineType(c: PContext, s: PSym) =
   if s.typ != nil: return
   #if s.magic != mNone: return
   #if s.ast.isNil: return
-  discard semProcAux(c, s.ast, s.kind, {}, stepDetermineType)
+  discard semProcAux(c, s.ast, s.kind, {})
 
 proc semIterator(c: PContext, n: PNode): PNode =
   # gensym'ed iterator?
-  let isAnon = n[namePos].kind == nkEmpty
   if n[namePos].kind == nkSym:
     # gensym'ed iterators might need to become closure iterators:
     n[namePos].sym.owner = getCurrOwner(c)
-    n[namePos].sym.kind = skIterator
+    n[namePos].sym.transitionRoutineSymKind(skIterator)
   result = semProcAux(c, n, skIterator, iteratorPragmas)
   # bug #7093: if after a macro transformation we don't have an
   # nkIteratorDef aynmore, return. The iterator then might have been
   # sem'checked already. (Or not, if the macro skips it.)
   if result.kind != n.kind: return
-  var s = result.sons[namePos].sym
+  var s = result[namePos].sym
   var t = s.typ
-  if t.sons[0] == nil and s.typ.callConv != ccClosure:
+  if t.returnType == nil and s.typ.callConv != ccClosure:
     localError(c.config, n.info, "iterator needs a return type")
-  if isAnon and s.typ.callConv == ccInline:
-    localError(c.config, n.info, "inline iterators are not first-class / cannot be assigned to variables")
   # iterators are either 'inline' or 'closure'; for backwards compatibility,
   # we require first class iterators to be marked with 'closure' explicitly
   # -- at least for 0.9.2.
@@ -1671,14 +2647,19 @@ proc semIterator(c: PContext, n: PNode): PNode =
     incl(s.typ.flags, tfCapturesEnv)
   else:
     s.typ.callConv = ccInline
-  if n.sons[bodyPos].kind == nkEmpty and s.magic == mNone:
+  if n[bodyPos].kind == nkEmpty and s.magic == mNone and c.inConceptDecl == 0:
     localError(c.config, n.info, errImplOfXexpected % s.name.s)
+  if optOwnedRefs in c.config.globalOptions and result.typ != nil:
+    result.typ = makeVarType(c, result.typ, tyOwned)
+    result.typ.callConv = ccClosure
 
 proc semProc(c: PContext, n: PNode): PNode =
   result = semProcAux(c, n, skProc, procPragmas)
 
 proc semFunc(c: PContext, n: PNode): PNode =
-  result = semProcAux(c, n, skFunc, procPragmas)
+  let validPragmas = if n[namePos].kind != nkEmpty: procPragmas
+                     else: lambdaPragmas
+  result = semProcAux(c, n, skFunc, validPragmas)
 
 proc semMethod(c: PContext, n: PNode): PNode =
   if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "method")
@@ -1689,21 +2670,20 @@ proc semMethod(c: PContext, n: PNode): PNode =
   # nkIteratorDef aynmore, return. The iterator then might have been
   # sem'checked already. (Or not, if the macro skips it.)
   if result.kind != nkMethodDef: return
-  var s = result.sons[namePos].sym
+  var s = result[namePos].sym
   # we need to fix the 'auto' return type for the dispatcher here (see tautonotgeneric
   # test case):
   let disp = getDispatcher(s)
   # auto return type?
-  if disp != nil and disp.typ.sons[0] != nil and disp.typ.sons[0].kind == tyExpr:
-    let ret = s.typ.sons[0]
-    disp.typ.sons[0] = ret
+  if disp != nil and disp.typ.returnType != nil and disp.typ.returnType.kind == tyUntyped:
+    let ret = s.typ.returnType
+    disp.typ.setReturnType ret
     if disp.ast[resultPos].kind == nkSym:
-      if isEmptyType(ret): disp.ast.sons[resultPos] = c.graph.emptyNode
+      if isEmptyType(ret): disp.ast[resultPos] = c.graph.emptyNode
       else: disp.ast[resultPos].sym.typ = ret
 
 proc semConverterDef(c: PContext, n: PNode): PNode =
   if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "converter")
-  checkSonsLen(n, bodyPos + 1, c.config)
   result = semProcAux(c, n, skConverter, converterPragmas)
   # macros can transform converters to nothing:
   if namePos >= result.safeLen: return result
@@ -1711,14 +2691,13 @@ proc semConverterDef(c: PContext, n: PNode): PNode =
   # nkIteratorDef aynmore, return. The iterator then might have been
   # sem'checked already. (Or not, if the macro skips it.)
   if result.kind != nkConverterDef: return
-  var s = result.sons[namePos].sym
+  var s = result[namePos].sym
   var t = s.typ
-  if t.sons[0] == nil: localError(c.config, n.info, errXNeedsReturnType % "converter")
-  if sonsLen(t) != 2: localError(c.config, n.info, "a converter takes exactly one argument")
-  addConverter(c, s)
+  if t.returnType == nil: localError(c.config, n.info, errXNeedsReturnType % "converter")
+  if t.len != 2: localError(c.config, n.info, "a converter takes exactly one argument")
+  addConverterDef(c, LazySym(sym: s))
 
 proc semMacroDef(c: PContext, n: PNode): PNode =
-  checkSonsLen(n, bodyPos + 1, c.config)
   result = semProcAux(c, n, skMacro, macroPragmas)
   # macros can transform macros to nothing:
   if namePos >= result.safeLen: return result
@@ -1726,62 +2705,105 @@ proc semMacroDef(c: PContext, n: PNode): PNode =
   # nkIteratorDef aynmore, return. The iterator then might have been
   # sem'checked already. (Or not, if the macro skips it.)
   if result.kind != nkMacroDef: return
-  var s = result.sons[namePos].sym
+  var s = result[namePos].sym
   var t = s.typ
   var allUntyped = true
-  for i in 1 .. t.n.len-1:
-    let param = t.n.sons[i].sym
-    if param.typ.kind != tyExpr: allUntyped = false
+  var nullary = true
+  for i in 1..<t.n.len:
+    let param = t.n[i].sym
+    if param.typ.kind != tyUntyped: allUntyped = false
+    # no default value, parameters required in call
+    if param.ast == nil: nullary = false
   if allUntyped: incl(s.flags, sfAllUntyped)
-  if t.sons[0] == nil: localError(c.config, n.info, "macro needs a return type")
-  if n.sons[bodyPos].kind == nkEmpty:
+  if nullary and n[genericParamsPos].kind == nkEmpty:
+    # macro can be called with alias syntax, remove pushed noalias flag
+    excl(s.flags, sfNoalias)
+  if n[bodyPos].kind == nkEmpty:
     localError(c.config, n.info, errImplOfXexpected % s.name.s)
 
+proc incMod(c: PContext, n: PNode, it: PNode, includeStmtResult: PNode) =
+  var f = checkModuleName(c.config, it)
+  if f != InvalidFileIdx:
+    addIncludeFileDep(c, f)
+    onProcessing(c.graph, f, "include", c.module)
+    if containsOrIncl(c.includedFiles, f.int):
+      localError(c.config, n.info, errRecursiveDependencyX % toMsgFilename(c.config, f))
+    else:
+      includeStmtResult.add semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f), {})
+      excl(c.includedFiles, f.int)
+
 proc evalInclude(c: PContext, n: PNode): PNode =
   result = newNodeI(nkStmtList, n.info)
-  addSon(result, n)
-  for i in countup(0, sonsLen(n) - 1):
-    var f = checkModuleName(c.config, n.sons[i])
-    if f != InvalidFileIDX:
-      if containsOrIncl(c.includedFiles, f.int):
-        localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f))
+  result.add n
+  template checkAs(it: PNode) =
+    if it.kind == nkInfix and it.len == 3:
+      let op = it[0].getPIdent
+      if op != nil and op.id == ord(wAs):
+        localError(c.config, it.info, "Cannot use '" & it[0].renderTree & "' in 'include'.")
+  for i in 0..<n.len:
+    let it = n[i]
+    checkAs(it)
+    if it.kind in {nkInfix, nkPrefix} and it[^1].kind == nkBracket:
+      let lastPos = it.len - 1
+      var imp = copyNode(it)
+      newSons(imp, it.len)
+      for i in 0 ..< lastPos: imp[i] = it[i]
+      imp[lastPos] = imp[0] # dummy entry, replaced in the loop
+      for x in it[lastPos]:
+        checkAs(x)
+        imp[lastPos] = x
+        incMod(c, n, imp, result)
+    else:
+      incMod(c, n, it, result)
+
+proc recursiveSetFlag(n: PNode, flag: TNodeFlag) =
+  if n != nil:
+    for i in 0..<n.safeLen: recursiveSetFlag(n[i], flag)
+    incl(n.flags, flag)
+
+proc semPragmaBlock(c: PContext, n: PNode; expectedType: PType = nil): PNode =
+  checkSonsLen(n, 2, c.config)
+  let pragmaList = n[0]
+  pragma(c, nil, pragmaList, exprPragmas, isStatement = true)
+
+  var inUncheckedAssignSection = 0
+  for p in pragmaList:
+    if whichPragma(p) == wCast:
+      case whichPragma(p[1])
+      of wGcSafe, wNoSideEffect, wTags, wForbids, wRaises:
+        discard "handled in sempass2"
+      of wUncheckedAssign:
+        inUncheckedAssignSection = 1
       else:
-        addSon(result, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f)))
-        excl(c.includedFiles, f.int)
-
-proc setLine(n: PNode, info: TLineInfo) =
-  for i in 0 ..< safeLen(n): setLine(n.sons[i], info)
-  n.info = info
-
-proc semPragmaBlock(c: PContext, n: PNode): PNode =
-  let pragmaList = n.sons[0]
-  pragma(c, nil, pragmaList, exprPragmas)
-  result = semExpr(c, n.sons[1])
-  n.sons[1] = result
-  for i in 0 ..< pragmaList.len:
-    case whichPragma(pragmaList.sons[i])
-    of wLine: setLine(result, pragmaList.sons[i].info)
-    of wLocks, wGcSafe:
-      result = n
-      result.typ = n.sons[1].typ
-    of wNoRewrite:
-      incl(result.flags, nfNoRewrite)
+        localError(c.config, p.info, "invalid pragma block: " & $p)
+
+  inc c.inUncheckedAssignSection, inUncheckedAssignSection
+  n[1] = semExpr(c, n[1], expectedType = expectedType)
+  dec c.inUncheckedAssignSection, inUncheckedAssignSection
+  result = n
+  result.typ = n[1].typ
+  for i in 0..<pragmaList.len:
+    case whichPragma(pragmaList[i])
+    of wLine: setInfoRecursive(result, pragmaList[i].info)
+    of wNoRewrite: recursiveSetFlag(result, nfNoRewrite)
     else: discard
 
 proc semStaticStmt(c: PContext, n: PNode): PNode =
   #echo "semStaticStmt"
   #writeStackTrace()
   inc c.inStaticContext
-  let a = semStmt(c, n.sons[0])
+  openScope(c)
+  let a = semStmt(c, n[0], {})
+  closeScope(c)
   dec c.inStaticContext
-  n.sons[0] = a
-  evalStaticStmt(c.module, c.graph, a, c.p.owner)
+  n[0] = a
+  evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
   when false:
     # for incremental replays, keep the AST as required for replays:
     result = n
   else:
     result = newNodeI(nkDiscardStmt, n.info, 1)
-    result.sons[0] = c.graph.emptyNode
+    result[0] = c.graph.emptyNode
 
 proc usesResult(n: PNode): bool =
   # nkStmtList(expr) properly propagates the void context,
@@ -1793,8 +2815,11 @@ proc usesResult(n: PNode): bool =
     elif n.kind == nkReturnStmt:
       result = true
     else:
+      result = false
       for c in n:
         if usesResult(c): return true
+  else:
+    result = false
 
 proc inferConceptStaticParam(c: PContext, inferred, n: PNode) =
   var typ = inferred.typ
@@ -1802,67 +2827,62 @@ proc inferConceptStaticParam(c: PContext, inferred, n: PNode) =
   if not sameType(res.typ, typ.base):
     localError(c.config, n.info,
       "cannot infer the concept parameter '%s', due to a type mismatch. " &
-      "attempt to equate '%s' and '%s'.",
-      [inferred.renderTree, $res.typ, $typ.base])
+      "attempt to equate '%s' and '%s'." % [inferred.renderTree, $res.typ, $typ.base])
   typ.n = res
 
-proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
-  # these must be last statements in a block:
-  const
-    LastBlockStmts = {nkRaiseStmt, nkReturnStmt, nkBreakStmt, nkContinueStmt}
+proc semStmtList(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType = nil): PNode =
   result = n
-  result.kind = nkStmtList
-  var length = sonsLen(n)
+  result.transitionSonsKind(nkStmtList)
   var voidContext = false
-  var last = length-1
+  var last = n.len-1
   # by not allowing for nkCommentStmt etc. we ensure nkStmtListExpr actually
   # really *ends* in the expression that produces the type: The compiler now
   # relies on this fact and it's too much effort to change that. And arguably
   #  'R(); #comment' shouldn't produce R's type anyway.
-  #while last > 0 and n.sons[last].kind in {nkPragma, nkCommentStmt,
+  #while last > 0 and n[last].kind in {nkPragma, nkCommentStmt,
   #                                         nkNilLit, nkEmpty}:
   #  dec last
-  for i in countup(0, length - 1):
-    var expr = semExpr(c, n.sons[i], flags)
-    n.sons[i] = expr
-    if c.matchedConcept != nil and expr.typ != nil and
+  for i in 0..<n.len:
+    var x = semExpr(c, n[i], flags, if i == n.len - 1: expectedType else: nil)
+    n[i] = x
+    if c.matchedConcept != nil and x.typ != nil and
         (nfFromTemplate notin n.flags or i != last):
-      case expr.typ.kind
+      case x.typ.kind
       of tyBool:
-        if expr.kind == nkInfix and
-            expr[0].kind == nkSym and
-            expr[0].sym.name.s == "==":
-          if expr[1].typ.isUnresolvedStatic:
-            inferConceptStaticParam(c, expr[1], expr[2])
+        if x.kind == nkInfix and
+            x[0].kind == nkSym and
+            x[0].sym.name.s == "==":
+          if x[1].typ.isUnresolvedStatic:
+            inferConceptStaticParam(c, x[1], x[2])
             continue
-          elif expr[2].typ.isUnresolvedStatic:
-            inferConceptStaticParam(c, expr[2], expr[1])
+          elif x[2].typ.isUnresolvedStatic:
+            inferConceptStaticParam(c, x[2], x[1])
             continue
 
         let verdict = semConstExpr(c, n[i])
-        if verdict.intVal == 0:
+        if verdict == nil or verdict.kind != nkIntLit or verdict.intVal == 0:
           localError(c.config, result.info, "concept predicate failed")
-      of tyUnknown: continue
+      of tyFromExpr: continue
       else: discard
-    if n.sons[i].typ == c.enforceVoidContext: #or usesResult(n.sons[i]):
+    if n[i].typ == c.enforceVoidContext: #or usesResult(n[i]):
       voidContext = true
       n.typ = c.enforceVoidContext
-    if i == last and (length == 1 or efWantValue in flags):
-      n.typ = n.sons[i].typ
-      if not isEmptyType(n.typ): n.kind = nkStmtListExpr
+    if i == last and (n.len == 1 or ({efWantValue, efInTypeof} * flags != {})):
+      n.typ = n[i].typ
+      if not isEmptyType(n.typ): n.transitionSonsKind(nkStmtListExpr)
     elif i != last or voidContext:
-      discardCheck(c, n.sons[i])
+      discardCheck(c, n[i], flags)
     else:
-      n.typ = n.sons[i].typ
-      if not isEmptyType(n.typ): n.kind = nkStmtListExpr
-    if n.sons[i].kind in LastBlockStmts or
-        n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and
-        sfNoReturn in n.sons[i][0].sym.flags:
-      for j in countup(i + 1, length - 1):
-        case n.sons[j].kind
-        of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr,
-            nkBlockStmt, nkState: discard
-        else: localError(c.config, n.sons[j].info, "unreachable statement after 'return'")
+      n.typ = n[i].typ
+      if not isEmptyType(n.typ): n.transitionSonsKind(nkStmtListExpr)
+    var m = n[i]
+    while m.kind in {nkStmtListExpr, nkStmtList} and m.len > 0: # from templates
+      m = m.lastSon
+    if endsInNoReturn(m):
+      for j in i + 1..<n.len:
+        case n[j].kind
+        of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkState: discard
+        else: message(c.config, n[j].info, warnUnreachableCode)
     else: discard
 
   if result.len == 1 and
@@ -1871,15 +2891,11 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
      # also, don't make life complicated for macros.
      # they will always expect a proper stmtlist:
      nfBlockArg notin n.flags and
-     result.sons[0].kind != nkDefer:
-    result = result.sons[0]
-
-  when defined(nimfix):
-    if result.kind == nkCommentStmt and not result.comment.isNil and
-        not (result.comment[0] == '#' and result.comment[1] == '#'):
-      # it is an old-style comment statement: we replace it with 'discard ""':
-      prettybase.replaceComment(result.info)
-
-proc semStmt(c: PContext, n: PNode): PNode =
-  # now: simply an alias:
-  result = semExprNoType(c, n)
+     result[0].kind != nkDefer:
+    result = result[0]
+
+proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode =
+  if efInTypeof notin flags:
+    result = semExprNoType(c, n)
+  else:
+    result = semExpr(c, n, flags)