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.nim2901
1 files changed, 2901 insertions, 0 deletions
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
new file mode 100644
index 000000000..f5f8fea0c
--- /dev/null
+++ b/compiler/semstmts.nim
@@ -0,0 +1,2901 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2013 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## this module does the semantic checking of statements
+#  included from sem.nim
+
+const
+  errNoSymbolToBorrowFromFound = "no symbol to borrow from found"
+  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"
+  errExprCannotBeRaised = "only a 'ref object' can be raised"
+  errBreakOnlyInLoop = "'break' only allowed in loop construct"
+  errExceptionAlreadyHandled = "exception already handled"
+  errYieldNotAllowedHere = "'yield' only allowed in an iterator"
+  errYieldNotAllowedInTryStmt = "'yield' cannot be used within 'try' in a non-inlined iterator"
+  errInvalidNumberOfYieldExpr = "invalid number of 'yield' expressions"
+  errCannotReturnExpr = "current routine cannot return an expression"
+  errGenericLambdaNotAllowed = "A nested proc can have generic parameters only when " &
+    "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 '$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[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[0].kind != nkEmpty:
+    if n.kind != nkContinueStmt:
+      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[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.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[0])
+  if marker == '\0': marker = '`' # default marker
+  result = semAsmOrEmit(c, n, marker)
+
+proc semWhile(c: PContext, n: PNode; flags: TExprFlags): PNode =
+  result = n
+  checkSonsLen(n, 2, c.config)
+  openScope(c)
+  n[0] = forceBool(c, semExprWithType(c, n[0], expectedType = getSysType(c.graph, n.info, tyBool)))
+  inc(c.p.nestedLoopCounter)
+  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[1].typ == c.enforceVoidContext:
+    result.typ = c.enforceVoidContext
+  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; 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; expectedType: PType = nil): PNode =
+  openScope(c)
+  result = semExprBranch(c, n, expectedType = expectedType)
+  closeScope(c)
+
+const
+  skipForDiscardable = {nkStmtList, nkStmtListExpr,
+    nkOfBranch, nkElse, nkFinally, nkExceptBranch,
+    nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr,
+    nkHiddenStdConv, nkHiddenSubConv, nkHiddenDeref}
+
+proc implicitlyDiscardable(n: PNode): bool =
+  # 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.transitionSonsKind(nkStmtList)
+    for it in n: fixNilType(c, it)
+  n.typ = nil
+
+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 = 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:
+      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 0..<n.len:
+    var it = n[i]
+    if it.len == 2:
+      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[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, 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[j]):
+        it[j] = fitNode(c, typ, it[j], it[j].info)
+    result.transitionSonsKind(nkIfExpr)
+    result.typ = typ
+
+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 isImported = false
+    if isImportedException(typ, c.config):
+      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)
+    isImported
+
+  result = n
+  checkMinSonsLen(n, 2, c.config)
+
+  var typ = commonTypeBegin
+  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 = 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] = move a[0].sons
+
+      if a.len == 2 and a[0].isInfixAs():
+        # support ``except Exception as ex: body``
+        let isImported = semExceptBranchType(a[0][1])
+        let symbol = newSymG(skLet, a[0][2], c)
+        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``
+        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: isImported = true
+          else: isNative = true
+
+        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:
+      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:
+    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)
+
+  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, flags)
+    if not endsInNoReturn(n[0]):
+      n[0] = fitNode(c, typ, n[0], n[0].info)
+    for i in 1..last:
+      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 fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode =
+  result = fitNode(c, typ, n, n.info)
+  if result.kind in {nkHiddenStdConv, nkHiddenSubConv}:
+    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
+  elif not sameType(result.typ, typ):
+    changeType(c, result, typ, check=false)
+
+proc findShadowedVar(c: PContext, v: PSym): PSym =
+  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[i], s): return true
+  result = n.kind == nkSym and n.sym.name.id == s.id
+
+proc semIdentDef(c: PContext, n: PNode, kind: TSymKind, reportToNimsuggest = true): PSym =
+  if isTopLevel(c):
+    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
+  else:
+    result = semIdentWithPragma(c, kind, n, {})
+    if result.owner.kind == skModule:
+      incl(result.flags, sfGlobal)
+  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 v.typ.requiresInit:
+    if v.astdef.isNil:
+      message(c.config, v.info, warnProveInit, v.name.s)
+    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 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: var PNode; orig, identDefs: PNode) =
+  if result.kind == nkStmtList:
+    let o = copyNode(orig)
+    o.add identDefs
+    result.add o
+  else:
+    result.add identDefs
+
+proc isDiscardUnderscore(v: PSym): bool =
+  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 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)
+    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[^1].kind != nkEmpty:
+      localError(c.config, a.info, "'using' sections cannot contain assignments")
+
+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.last
+  t = skipTypes(t, {tyGenericInst, tyAlias, tySink, tyOwned})
+  result = n
+  if t.kind in {tyVar, tyLent}:
+    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.elementType
+    result = newNodeIT(nkHiddenDeref, n.info, baseTyp)
+    result.add a
+    t = skipTypes(baseTyp, {tyGenericInst, tyAlias, tySink, tyOwned})
+
+proc fillPartialObject(c: PContext; n: PNode; typ: PType) =
+  if n.len == 2:
+    let x = semExprWithType(c, n[0])
+    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), 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 " &
+        "requires a .partial object, but got " & typeToString(obj))
+  else:
+    localError(c.config, n.info, "nkDotNode requires 2 children")
+
+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, 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)
+
+  # 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}: illFormedAst(a, c.config)
+    checkMinSonsLen(a, 3, c.config)
+
+    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 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)
+          typ = def.typ
+        else:
+          # BUGFIX: ``fitNode`` is needed here!
+          # check type compatibility between def.typ and typ
+          def = fitNodeConsiderViewType(c, typ, def, def.info)
+          #changeType(def.skipConv, typ, check=true)
+      else:
+        typ = def.typ.skipTypes({tyStatic, tySink}).skipIntLit(c.idgen)
+        if typ.kind in tyUserTypeClasses and typ.isResolvedUserTypeClass:
+          typ = typ.last
+        if hasEmpty(typ):
+          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
+
+    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:
+      # 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:
+          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:
+          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
+        # 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)
+        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)
+  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 notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config)
+    checkMinSonsLen(a, 3, c.config)
+
+    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:
+      if typ.isMetaType:
+        def = inferWithMetatype(c, typ, def)
+        typ = def.typ
+      else:
+        def = fitRemoveHiddenConv(c, typ, def)
+    else:
+      typ = def.typ
+
+    # evaluate the node
+    def = semConstExpr(c, def)
+    if def == nil:
+      localError(c.config, a[^1].info, errConstExprExpected)
+      continue
+    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 symForVar(c: PContext, n: PNode): PSym =
+  let m = if n.kind == nkPragmaExpr: n[0] else: n
+  result = newSymG(skForVar, m, c)
+  styleCheckDef(c, result)
+  onDef(n.info, result)
+  if n.kind == nkPragmaExpr:
+    pragma(c, result, n[1], forVarPragmas)
+
+proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
+  result = n
+  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 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 n.len-2 != iterAfterVarLent.len:
+    localError(c.config, n.info, errWrongNumberOfVariables)
+  else:
+    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[^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 =
+  result = newNodeI(nkCall, arg.info)
+  result.add(newIdentNode(getIdent(c.cache, it), arg.info))
+  if arg.typ != nil and arg.typ.kind in {tyVar, tyLent}:
+    result.add newDeref(arg)
+  else:
+    result.add arg
+  result = semExprNoDeref(c, result, {efWantIterator})
+
+proc isTrivalStmtExpr(n: PNode): bool =
+  for i in 0..<n.len-1:
+    if n[i].kind notin {nkEmpty, nkCommentStmt}:
+      return false
+  result = true
+
+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 maType = magicsys.getCompilerProc(c.graph, magicType)
+    if maType == nil: return
+
+    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.firstParamType == maType.typ:
+          if match == nil:
+            match = symx
+          else:
+            localError(c.config, n.info, errAmbiguousCallXYZ % [
+              getProcHeader(c.config, match),
+              getProcHeader(c.config, symx), $selector])
+      symx = nextOverloadIter(o, c, headSymbol)
+
+    if match == nil: return
+    var callExpr = newNodeI(nkCall, n.info)
+    callExpr.add newSymNode(match)
+    callExpr.add n
+    case match.kind
+    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; flags: TExprFlags): PNode =
+  checkMinSonsLen(n, 3, c.config)
+  result = handleForLoopMacro(c, n, flags)
+  if result != nil: return result
+  openScope(c)
+  result = n
+  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[^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[0].sym.magic == mOmpParFor:
+      result = semForVars(c, n, flags)
+      result.transitionSonsKind(nkParForStmt)
+    else:
+      result = semForFields(c, n, call[0].sym.magic)
+  elif isCallExpr and isClosureIterator(call[0].typ.skipTypes(abstractInst)):
+    # first class iterator:
+    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[^2].info, "iterator within for loop context expected")
+    result = semForVars(c, n, flags)
+  else:
+    result = semForVars(c, n, flags)
+  # propagate any enforced VoidContext:
+  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])
+    var typ = n[0].typ
+    if not isImportedException(typ, c.config):
+      typ = typ.skipTypes({tyAlias, tyGenericInst, tyOwned})
+      if typ.kind != tyRef:
+        localError(c.config, n.info, errExprCannotBeRaised)
+      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 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[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
+  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
+    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.typeBodyImpl
+
+  proc traverseSubTypes(c: PContext; t: PType): bool =
+    template error(msg) = localError(c.config, genericType.sym.info, msg)
+    result = false
+    template subresult(r) =
+      let sub = r
+      result = result or sub
+
+    case t.kind
+    of tyGenericParam:
+      t.flags.incl tfWeakCovariant
+      return true
+    of tyObject:
+      for field in t.n:
+        subresult traverseSubTypes(c, field.typ)
+    of tyArray:
+      return traverseSubTypes(c, t.elementType)
+    of tyProc:
+      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.elementType)
+    of tyGenericInvocation:
+      let targetBody = t.genericHead
+      for i in 1..<t.len:
+        let param = t[i]
+        if param.kind == tyGenericParam:
+          if tfCovariant in param.flags:
+            let formalFlags = targetBody[i-1].flags
+            if tfCovariant notin formalFlags:
+              error("covariant param '" & param.sym.name.s &
+                    "' used in a non-covariant position")
+            elif tfWeakCovariant in formalFlags:
+              param.flags.incl tfWeakCovariant
+            result = true
+          elif tfContravariant in param.flags:
+            let formalParam = targetBody[i-1].sym
+            if tfContravariant notin formalParam.typ.flags:
+              error("contravariant param '" & param.sym.name.s &
+                    "' used in a non-contravariant position")
+            result = true
+        else:
+          subresult traverseSubTypes(c, param)
+    of tyAnd, tyOr, tyNot, tyStatic, tyBuiltInTypeClass, tyCompositeTypeClass:
+      error("non-invariant type parameters cannot be used with types such '" & $t & "'")
+    of tyUserTypeClass, tyUserTypeClassInst:
+      error("non-invariant type parameters are not supported in concepts")
+    of tyTuple:
+      for fieldType in t.kids:
+        subresult traverseSubTypes(c, fieldType)
+    of tyPtr, tyRef, tyVar, tyLent:
+      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:
+      discard
+  discard traverseSubTypes(c, body)
+
+proc typeSectionRightSidePass(c: PContext, n: PNode) =
+  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[0])
+    var s = name.sym
+    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)
+    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)
+      pushOwner(c, s)
+      if s.magic == mNone: s.typ.kind = tyGenericBody
+      # XXX for generic type aliases this is not correct! We need the
+      # underlying Id really:
+      #
+      # type
+      #   TGObj[T] = object
+      #   TAlias[T] = TGObj[T]
+      #
+      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
+      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[2], s.typ)
+      dec c.inGenericContext
+      if body != nil:
+        body.sym = s
+        body.size = -1 # could not be computed properly
+        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:
+          # The codegen currently produces various failures with
+          # generic imported types that have fields, but we need
+          # the fields specified in order to detect weak covariance.
+          # The proper solution is to teach the codegen how to handle
+          # such types, because this would offer various interesting
+          # possibilities such as instantiating C++ generic types with
+          # garbage collected Nim types.
+          if sfImportc in s.flags:
+            var body = s.typ.last
+            if body.kind == tyObject:
+              # erases all declared fields
+              body.n.sons = @[]
+
+      popOwner(c)
+      closeScope(c)
+    elif a[2].kind != nkEmpty:
+      # process the type's body:
+      pushOwner(c, s)
+      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):
+        # this can happen for e.g. tcan_alias_specialised_generic:
+        assignType(s.typ, t)
+        #debug s.typ
+      s.ast = a
+      popOwner(c)
+      # 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[0].kind == nkObjectTy:
+      # give anonymous object a dummy symbol:
+      var st = s.typ
+      if st.kind == tyGenericBody: st = st.typeBodyImpl
+      internalAssert c.config, st.kind in {tyPtr, tyRef}
+      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"),
+                       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, hasError)
+  of nkOfBranch, nkElse:
+    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, tyOwned:
+      let start = ord(t.kind in {tyGenericInvocation, tyGenericInst})
+      for i in start..<t.len:
+        checkMeta(c, n, t[i], hasError, t)
+    else:
+      checkMeta(c, n, t, hasError, nil)
+  else:
+    internalAssert c.config, false
+
+proc typeSectionFinalPass(c: PContext, n: PNode) =
+  for i in 0..<n.len:
+    var a = n[i]
+    if a.kind == nkCommentStmt: continue
+    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[1].kind == nkEmpty:
+      var x = a[2]
+      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 =
+  proc gatherStmts(c: PContext; n: PNode; result: PNode) {.nimcall.} =
+    case n.kind
+    of nkIncludeStmt:
+      for i in 0..<n.len:
+        var f = checkModuleName(c.config, n[i])
+        if f != InvalidFileIdx:
+          if containsOrIncl(c.includedFiles, f.int):
+            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[i], result)
+    of nkTypeSection:
+      incl n.flags, nfSem
+      typeSectionLeftSidePass(c, n)
+      result.add n
+    else:
+      result.add n
+
+  result = newNodeI(nkStmtList, n.info)
+  gatherStmts(c, n, result)
+
+  template rec(name) =
+    for i in 0..<result.len:
+      if result[i].kind == nkTypeSection:
+        name(c, result[i])
+
+  rec typeSectionRightSidePass
+  rec typeSectionFinalPass
+  when false:
+    # too beautiful to delete:
+    template rec(name; setbit=false) =
+      proc `name rec`(c: PContext; n: PNode) {.nimcall.} =
+        if n.kind == nkTypeSection:
+          when setbit: incl n.flags, nfSem
+          name(c, n)
+        elif n.kind == nkStmtList:
+          for i in 0..<n.len:
+            `name rec`(c, n[i])
+      `name rec`(c, n)
+    rec typeSectionLeftSidePass, true
+    rec typeSectionRightSidePass
+    rec typeSectionFinalPass
+
+proc semTypeSection(c: PContext, n: PNode): PNode =
+  ## Processes a type section. This must be done in separate passes, in order
+  ## to allow the type definitions in the section to reference each other
+  ## without regard for the order of their definitions.
+  if sfNoForward notin c.module.flags or nfSem notin n.flags:
+    inc c.inTypeContext
+    typeSectionLeftSidePass(c, n)
+    typeSectionRightSidePass(c, n)
+    typeSectionFinalPass(c, n)
+    dec c.inTypeContext
+  result = n
+
+proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) =
+  s.typ = semProcTypeNode(c, n, genericParams, nil, s.kind)
+
+proc addParams(c: PContext, n: PNode, kind: TSymKind) =
+  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, state) = searchForBorrowProc(c, c.currentScope.parent, s)
+  case state
+  of bsMatch:
+    # store the alias:
+    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 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)
+
+  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 =
+  # Mirrored with semVarMacroPragma
+  result = nil
+  var n = prc[pragmasPos]
+  if n == nil or n.kind == nkEmpty: return
+  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
+
+      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
+
+      doAssert result != nil
+
+      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
+
+  n = replaceTypesInBody(c, pt, n, original)
+  result = n
+  s.ast = result
+  n[namePos].sym = s
+  n[genericParamsPos] = c.graph.emptyNode
+  # for LL we need to avoid wrong aliasing
+  let params = copyTree n.typ.n
+  s.typ = n.typ
+  for i in 1..<params.len:
+    if params[i].typ.kind in {tyTypeDesc, tyGenericParam,
+                              tyFromExpr}+tyTypeClasses:
+      localError(c.config, params[i].info, "cannot infer type of parameter: " &
+                 params[i].sym.name.s)
+    #params[i].sym.owner = s
+  openScope(c)
+  pushOwner(c, s)
+  addParams(c, params, skProc)
+  pushProcCon(c, 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)
+  # result = inferred.ast
+  # result.kind = arg.kind
+
+proc activate(c: PContext, n: PNode) =
+  # XXX: This proc is part of my plan for getting rid of
+  # forward declarations. stay tuned.
+  when false:
+    # well for now it breaks code ...
+    case n.kind
+    of nkLambdaKinds:
+      discard semLambda(c, n, {})
+    of nkCallKinds:
+      for i in 1..<n.len: activate(c, n[i])
+    else:
+      discard
+
+proc maybeAddResult(c: PContext, s: PSym, n: PNode) =
+  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 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, 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.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.firstParamType.skipTypes(abstractInst).elementType.skipTypes(abstractInst)
+      while true:
+        if t.kind == tyGenericBody: t = t.typeBodyImpl
+        elif t.kind == tyGenericInvocation: t = t.genericHead
+        else: break
+      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)
+    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.returnType == nil and t.firstParamType.kind == tyVar:
+      var obj = t.firstParamType.elementType
+      while true:
+        incl(obj.flags, tfHasAsgn)
+        if obj.kind == tyGenericBody: obj = obj.skipModifier
+        elif obj.kind == tyGenericInvocation: obj = obj.genericHead
+        else: break
+      var objB = t[2]
+      while true:
+        if objB.kind == tyGenericBody: objB = objB.skipModifier
+        elif objB.kind in {tyGenericInvocation, tyGenericInst}:
+          objB = objB.genericHead
+        else: break
+      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,
+            "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 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
+
+proc cursorInProc(conf: ConfigRef; n: PNode): bool =
+  if n.info.fileIndex == conf.m.trackPos.fileIndex:
+    result = cursorInProcAux(conf, n)
+  else:
+    result = false
+
+proc hasObjParam(s: PSym): bool =
+  result = false
+  var t = s.typ
+  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, 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 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 1..<tt.len:
+      let t = tt[col]
+      if t != nil and t.kind == tyGenericInvocation:
+        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
+          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[bodyPos].kind != nkEmpty:
+    if hasObjParam(s):
+      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, flags: TExprFlags = {}): PNode =
+  result = semProcAnnotation(c, n, validPragmas)
+  if result != nil: return result
+  result = n
+  checkMinSonsLen(n, bodyPos + 1, c.config)
+
+  let
+    isAnon = n[namePos].kind == nkEmpty
+    isHighlight = c.config.ideCmd == ideHighlight
+
+  var s: PSym
+
+  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
+         sfSystemModule notin c.module.flags:
+        addInterfaceOverloadableSymAt(c, c.currentScope, s)
+        s.flags.incl sfForward
+        return
+
+  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 params & body, set as current the scope
+  # where the proc was declared
+  let declarationScope = c.currentScope
+  pushOwner(c, s)
+  openScope(c)
+
+  # process parameters:
+  # 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[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, 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
+
+  if not hasProto and sfGenSym notin s.flags: #and not isAnon:
+    if s.kind in OverloadableSyms:
+      addInterfaceOverloadableSymAt(c, declarationScope, s)
+    else:
+      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:
+    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[genericParamsPos].isGenericParams:
+      addGenericParamListToScope(c, proto.ast[genericParamsPos])
+    addParams(c, proto.typ.n, proto.kind)
+    proto.info = s.info       # more accurate line information
+    proto.options = s.options
+    s = proto
+    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 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[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)
+        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.returnType != nil and s.kind != skIterator):
+          addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), c.idgen, s, n.info))
+
+        openScope(c)
+        n[bodyPos] = semGenericStmt(c, n[bodyPos])
+        closeScope(c)
+        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 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[patternPos].kind != nkEmpty:
+    c.patterns.add(s)
+  if isAnon:
+    n.transitionSonsKind(nkLambda)
+    result.typ = s.typ
+    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, {})
+
+proc semIterator(c: PContext, n: PNode): PNode =
+  # gensym'ed iterator?
+  if n[namePos].kind == nkSym:
+    # gensym'ed iterators might need to become closure iterators:
+    n[namePos].sym.owner = getCurrOwner(c)
+    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[namePos].sym
+  var t = s.typ
+  if t.returnType == nil and s.typ.callConv != ccClosure:
+    localError(c.config, n.info, "iterator needs a return type")
+  # 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.
+  if s.typ.callConv == ccClosure:
+    incl(s.typ.flags, tfCapturesEnv)
+  else:
+    s.typ.callConv = ccInline
+  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 =
+  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")
+  result = semProcAux(c, n, skMethod, methodPragmas)
+  # macros can transform converters to nothing:
+  if namePos >= result.safeLen: return result
+  # 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 != nkMethodDef: return
+  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.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[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")
+  result = semProcAux(c, n, skConverter, converterPragmas)
+  # macros can transform converters to nothing:
+  if namePos >= result.safeLen: return result
+  # 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 != nkConverterDef: return
+  var s = result[namePos].sym
+  var t = s.typ
+  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 =
+  result = semProcAux(c, n, skMacro, macroPragmas)
+  # macros can transform macros to nothing:
+  if namePos >= result.safeLen: return result
+  # 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 != nkMacroDef: return
+  var s = result[namePos].sym
+  var t = s.typ
+  var allUntyped = true
+  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 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)
+  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:
+        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
+  openScope(c)
+  let a = semStmt(c, n[0], {})
+  closeScope(c)
+  dec c.inStaticContext
+  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[0] = c.graph.emptyNode
+
+proc usesResult(n: PNode): bool =
+  # nkStmtList(expr) properly propagates the void context,
+  # so we don't need to process that all over again:
+  if n.kind notin {nkStmtList, nkStmtListExpr,
+                   nkMacroDef, nkTemplateDef} + procDefs:
+    if isAtom(n):
+      result = n.kind == nkSym and n.sym.kind == skResult
+    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
+  let res = semConstExpr(c, n)
+  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])
+  typ.n = res
+
+proc semStmtList(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType = nil): PNode =
+  result = n
+  result.transitionSonsKind(nkStmtList)
+  var voidContext = false
+  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[last].kind in {nkPragma, nkCommentStmt,
+  #                                         nkNilLit, nkEmpty}:
+  #  dec last
+  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 x.typ.kind
+      of tyBool:
+        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 x[2].typ.isUnresolvedStatic:
+            inferConceptStaticParam(c, x[2], x[1])
+            continue
+
+        let verdict = semConstExpr(c, n[i])
+        if verdict == nil or verdict.kind != nkIntLit or verdict.intVal == 0:
+          localError(c.config, result.info, "concept predicate failed")
+      of tyFromExpr: continue
+      else: discard
+    if n[i].typ == c.enforceVoidContext: #or usesResult(n[i]):
+      voidContext = true
+      n.typ = c.enforceVoidContext
+    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[i], flags)
+    else:
+      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
+     # concept bodies should be preserved as a stmt list:
+     c.matchedConcept == nil and
+     # also, don't make life complicated for macros.
+     # they will always expect a proper stmtlist:
+     nfBlockArg notin n.flags and
+     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)