summary refs log tree commit diff stats
path: root/compiler/semtempl.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/semtempl.nim')
-rw-r--r--compiler/semtempl.nim917
1 files changed, 917 insertions, 0 deletions
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
new file mode 100644
index 000000000..817cb6249
--- /dev/null
+++ b/compiler/semtempl.nim
@@ -0,0 +1,917 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# included from sem.nim
+
+discard """
+  hygienic templates:
+
+    template `||` (a, b: untyped): untyped =
+      let aa = a
+      if aa: aa else: b
+
+    var
+      a, b: T
+
+    echo a || b || a
+
+  Each evaluation context has to be different and we need to perform
+  some form of preliminary symbol lookup in template definitions. Hygiene is
+  a way to achieve lexical scoping at compile time.
+"""
+
+const
+  errImplOfXNotAllowed = "implementation of '$1' is not allowed"
+
+type
+  TSymBinding = enum
+    spNone, spGenSym, spInject
+
+proc symBinding(n: PNode): TSymBinding =
+  result = spNone
+  for i in 0..<n.len:
+    var it = n[i]
+    var key = if it.kind == nkExprColonExpr: it[0] else: it
+    if key.kind == nkIdent:
+      case whichKeyword(key.ident)
+      of wGensym: return spGenSym
+      of wInject: return spInject
+      else: discard
+
+type
+  TSymChoiceRule = enum
+    scClosed, scOpen, scForceOpen
+
+proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule;
+               isField = false): PNode =
+  var
+    a: PSym
+    o: TOverloadIter = default(TOverloadIter)
+  var i = 0
+  a = initOverloadIter(o, c, n)
+  while a != nil:
+    if a.kind != skModule:
+      inc(i)
+      if i > 1: break
+    a = nextOverloadIter(o, c, n)
+  let info = getCallLineInfo(n)
+  if i <= 1 and r != scForceOpen:
+    # XXX this makes more sense but breaks bootstrapping for now:
+    # (s.kind notin routineKinds or s.magic != mNone):
+    # for instance 'nextTry' is both in tables.nim and astalgo.nim ...
+    if not isField or sfGenSym notin s.flags:
+      result = newSymNode(s, info)
+      markUsed(c, info, s)
+      onUse(info, s)
+    else:
+      result = n
+  elif i == 0:
+    # forced open but symbol not in scope, retain information
+    result = n
+  else:
+    # semantic checking requires a type; ``fitNode`` deals with it
+    # appropriately
+    let kind = if r == scClosed or n.kind == nkDotExpr: nkClosedSymChoice
+               else: nkOpenSymChoice
+    result = newNodeIT(kind, info, newTypeS(tyNone, c))
+    a = initOverloadIter(o, c, n)
+    while a != nil:
+      if a.kind != skModule and (not isField or sfGenSym notin a.flags):
+        incl(a.flags, sfUsed)
+        markOwnerModuleAsUsed(c, a)
+        result.add newSymNode(a, info)
+        onUse(info, a)
+      a = nextOverloadIter(o, c, n)
+
+proc semBindStmt(c: PContext, n: PNode, toBind: var IntSet): PNode =
+  result = copyNode(n)
+  for i in 0..<n.len:
+    var a = n[i]
+    # If 'a' is an overloaded symbol, we used to use the first symbol
+    # as a 'witness' and use the fact that subsequent lookups will yield
+    # the same symbol!
+    # This is however not true anymore for hygienic templates as semantic
+    # processing for them changes the symbol table...
+    let s = qualifiedLookUp(c, a, {checkUndeclared})
+    if s != nil:
+      # we need to mark all symbols:
+      let sc = symChoice(c, n, s, scClosed)
+      if sc.kind == nkSym:
+        toBind.incl(sc.sym.id)
+        result.add sc
+      else:
+        for x in items(sc):
+          toBind.incl(x.sym.id)
+          result.add x
+    else:
+      illFormedAst(a, c.config)
+
+proc semMixinStmt(c: PContext, n: PNode, toMixin: var IntSet): PNode =
+  result = copyNode(n)
+  for i in 0..<n.len:
+    toMixin.incl(considerQuotedIdent(c, n[i]).id)
+    let x = symChoice(c, n[i], nil, scForceOpen)
+    result.add x
+
+proc replaceIdentBySym(c: PContext; n: var PNode, s: PNode) =
+  case n.kind
+  of nkPostfix: replaceIdentBySym(c, n[1], s)
+  of nkPragmaExpr: replaceIdentBySym(c, n[0], s)
+  of nkIdent, nkAccQuoted, nkSym: n = s
+  else: illFormedAst(n, c.config)
+
+type
+  TemplCtx = object
+    c: PContext
+    toBind, toMixin, toInject: IntSet
+    owner: PSym
+    cursorInBody: bool # only for nimsuggest
+    scopeN: int
+    noGenSym: int
+    inTemplateHeader: int
+
+proc isTemplParam(c: TemplCtx, s: PSym): bool {.inline.} =
+  result = s.kind == skParam and
+           s.owner == c.owner and sfTemplateParam in s.flags
+
+proc getIdentReplaceParams(c: var TemplCtx, n: var PNode): tuple[node: PNode, hasParam: bool] =
+  case n.kind
+  of nkPostfix: result = getIdentReplaceParams(c, n[1])
+  of nkPragmaExpr: result = getIdentReplaceParams(c, n[0])
+  of nkIdent:
+    result = (n, false)
+    let s = qualifiedLookUp(c.c, n, {})
+    if s != nil and isTemplParam(c, s):
+      n = newSymNode(s, n.info)
+      result = (n, true)
+  of nkSym:
+    result = (n, isTemplParam(c, n.sym))
+  of nkAccQuoted:
+    result = (n, false)
+    for i in 0..<n.safeLen:
+      let (ident, hasParam) = getIdentReplaceParams(c, n[i])
+      if hasParam:
+        result.node[i] = ident
+        result.hasParam = true
+  else:
+    illFormedAst(n, c.c.config)
+    result = (n, false)
+
+proc semTemplBody(c: var TemplCtx, n: PNode): PNode
+
+proc openScope(c: var TemplCtx) =
+  openScope(c.c)
+
+proc closeScope(c: var TemplCtx) =
+  closeScope(c.c)
+
+proc semTemplBodyScope(c: var TemplCtx, n: PNode): PNode =
+  openScope(c)
+  result = semTemplBody(c, n)
+  closeScope(c)
+
+proc newGenSym(kind: TSymKind, n: PNode, c: var TemplCtx): PSym =
+  result = newSym(kind, considerQuotedIdent(c.c, n), c.c.idgen, c.owner, n.info)
+  incl(result.flags, sfGenSym)
+  incl(result.flags, sfShadowed)
+
+proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) =
+  # locals default to 'gensym', fields default to 'inject':
+  if (n.kind == nkPragmaExpr and symBinding(n[1]) == spInject) or
+      k == skField:
+    # even if injected, don't produce a sym choice here:
+    #n = semTemplBody(c, n)
+    let (ident, hasParam) = getIdentReplaceParams(c, n)
+    if not hasParam:
+      if k != skField:
+        c.toInject.incl(considerQuotedIdent(c.c, ident).id)
+  else:
+    if (n.kind == nkPragmaExpr and n.len >= 2 and n[1].kind == nkPragma):
+      let pragmaNode = n[1]
+      for i in 0..<pragmaNode.len:
+        let ni = pragmaNode[i]
+        # see D20210801T100514
+        var found = false
+        if ni.kind == nkIdent:
+          for a in templatePragmas:
+            if ni.ident.id == ord(a):
+              found = true
+              break
+        if not found:
+          openScope(c)
+          pragmaNode[i] = semTemplBody(c, pragmaNode[i])
+          closeScope(c)
+    let (ident, hasParam) = getIdentReplaceParams(c, n)
+    if not hasParam:
+      if n.kind != nkSym and not (n.kind == nkIdent and n.ident.id == ord(wUnderscore)):
+        let local = newGenSym(k, ident, c)
+        addPrelimDecl(c.c, local)
+        styleCheckDef(c.c, n.info, local)
+        onDef(n.info, local)
+        replaceIdentBySym(c.c, n, newSymNode(local, n.info))
+        if k == skParam and c.inTemplateHeader > 0:
+          local.flags.incl sfTemplateParam
+
+proc semTemplSymbol(c: var TemplCtx, n: PNode, s: PSym; isField, isAmbiguous: bool): PNode =
+  incl(s.flags, sfUsed)
+  # bug #12885; ideally sem'checking is performed again afterwards marking
+  # the symbol as used properly, but the nfSem mechanism currently prevents
+  # that from happening, so we mark the module as used here already:
+  markOwnerModuleAsUsed(c.c, s)
+  # we do not call onUse here, as the identifier is not really
+  # resolved here. We will fixup the used identifiers later.
+  case s.kind
+  of skUnknown:
+    # Introduced in this pass! Leave it as an identifier.
+    result = n
+  of OverloadableSyms:
+    result = symChoice(c.c, n, s, scOpen, isField)
+    if not isField and result.kind in {nkSym, nkOpenSymChoice}:
+      if openSym in c.c.features:
+        if result.kind == nkSym:
+          result = newOpenSym(result)
+        else:
+          result.typ = nil
+      else:
+        result.flags.incl nfDisabledOpenSym
+        result.typ = nil
+  of skGenericParam:
+    if isField and sfGenSym in s.flags: result = n
+    else:
+      result = newSymNodeTypeDesc(s, c.c.idgen, n.info)
+      if not isField and s.owner != c.owner:
+        if openSym in c.c.features:
+          result = newOpenSym(result)
+        else:
+          result.flags.incl nfDisabledOpenSym
+          result.typ = nil
+  of skParam:
+    result = n
+  of skType:
+    if isField and sfGenSym in s.flags: result = n
+    else:
+      if isAmbiguous:
+        # ambiguous types should be symchoices since lookup behaves
+        # differently for them in regular expressions
+        result = symChoice(c.c, n, s, scOpen, isField)
+      else: result = newSymNodeTypeDesc(s, c.c.idgen, n.info)
+      if not isField and not (s.owner == c.owner and
+          s.typ != nil and s.typ.kind == tyGenericParam) and
+          result.kind in {nkSym, nkOpenSymChoice}:
+        if openSym in c.c.features:
+          if result.kind == nkSym:
+            result = newOpenSym(result)
+          else:
+            result.typ = nil
+        else:
+          result.flags.incl nfDisabledOpenSym
+          result.typ = nil
+  else:
+    if isField and sfGenSym in s.flags: result = n
+    else:
+      result = newSymNode(s, n.info)
+      if not isField:
+        if openSym in c.c.features:
+          result = newOpenSym(result)
+        else:
+          result.flags.incl nfDisabledOpenSym
+          result.typ = nil
+    # Issue #12832
+    when defined(nimsuggest):
+      suggestSym(c.c.graph, n.info, s, c.c.graph.usageSym, false)
+    # field access (dot expr) will be handled by builtinFieldAccess
+    if not isField:
+      styleCheckUse(c.c, n.info, s)
+
+proc semRoutineInTemplName(c: var TemplCtx, n: PNode, explicitInject: bool): PNode =
+  result = n
+  if n.kind == nkIdent:
+    let s = qualifiedLookUp(c.c, n, {})
+    if s != nil:
+      if s.owner == c.owner and (s.kind == skParam or
+          (sfGenSym in s.flags and not explicitInject)):
+        incl(s.flags, sfUsed)
+        result = newSymNode(s, n.info)
+        onUse(n.info, s)
+  else:
+    for i in 0..<n.safeLen:
+      result[i] = semRoutineInTemplName(c, n[i], explicitInject)
+
+proc semRoutineInTemplBody(c: var TemplCtx, n: PNode, k: TSymKind): PNode =
+  result = n
+  checkSonsLen(n, bodyPos + 1, c.c.config)
+  if n.kind notin nkLambdaKinds:
+    # routines default to 'inject':
+    let binding = symBinding(n[pragmasPos])
+    if binding == spGenSym:
+      let (ident, hasParam) = getIdentReplaceParams(c, n[namePos])
+      if not hasParam:
+        var s = newGenSym(k, ident, c)
+        s.ast = n
+        addPrelimDecl(c.c, s)
+        styleCheckDef(c.c, n.info, s)
+        onDef(n.info, s)
+        n[namePos] = newSymNode(s, n[namePos].info)
+      else:
+        n[namePos] = ident
+    else:
+      n[namePos] = semRoutineInTemplName(c, n[namePos], binding == spInject)
+  # open scope for parameters
+  openScope(c)
+  for i in patternPos..paramsPos-1:
+    n[i] = semTemplBody(c, n[i])
+
+  if k == skTemplate: inc(c.inTemplateHeader)
+  n[paramsPos] = semTemplBody(c, n[paramsPos])
+  if k == skTemplate: dec(c.inTemplateHeader)
+
+  for i in paramsPos+1..miscPos:
+    n[i] = semTemplBody(c, n[i])
+  # open scope for locals
+  inc c.scopeN
+  openScope(c)
+  n[bodyPos] = semTemplBody(c, n[bodyPos])
+  # close scope for locals
+  closeScope(c)
+  dec c.scopeN
+  # close scope for parameters
+  closeScope(c)
+
+proc semTemplIdentDef(c: var TemplCtx, a: PNode, symKind: TSymKind) =
+  checkMinSonsLen(a, 3, c.c.config)
+  when defined(nimsuggest):
+    inc c.c.inTypeContext
+  a[^2] = semTemplBody(c, a[^2])
+  when defined(nimsuggest):
+    dec c.c.inTypeContext
+  a[^1] = semTemplBody(c, a[^1])
+  for j in 0..<a.len-2:
+    addLocalDecl(c, a[j], symKind)
+
+proc semTemplSomeDecl(c: var TemplCtx, n: PNode, symKind: TSymKind; start = 0) =
+  for i in start..<n.len:
+    var a = n[i]
+    case a.kind:
+    of nkCommentStmt: continue
+    of nkIdentDefs, nkVarTuple, nkConstDef:
+      semTemplIdentDef(c, a, symKind)
+    else:
+      illFormedAst(a, c.c.config)
+
+
+proc semPattern(c: PContext, n: PNode; s: PSym): PNode
+
+proc semTemplBodySons(c: var TemplCtx, n: PNode): PNode =
+  result = n
+  for i in 0..<n.len:
+    result[i] = semTemplBody(c, n[i])
+
+proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
+  result = n
+  semIdeForTemplateOrGenericCheck(c.c.config, n, c.cursorInBody)
+  case n.kind
+  of nkIdent:
+    if n.ident.id in c.toInject: return n
+    c.c.isAmbiguous = false
+    let s = qualifiedLookUp(c.c, n, {})
+    if s != nil:
+      if s.owner == c.owner and s.kind == skParam and sfTemplateParam in s.flags:
+        incl(s.flags, sfUsed)
+        result = newSymNode(s, n.info)
+        onUse(n.info, s)
+      elif contains(c.toBind, s.id):
+        result = symChoice(c.c, n, s, scClosed, c.noGenSym > 0)
+      elif contains(c.toMixin, s.name.id):
+        result = symChoice(c.c, n, s, scForceOpen, c.noGenSym > 0)
+      elif s.owner == c.owner and sfGenSym in s.flags and c.noGenSym == 0:
+        # template tmp[T](x: var seq[T]) =
+        # var yz: T
+        incl(s.flags, sfUsed)
+        result = newSymNode(s, n.info)
+        onUse(n.info, s)
+      else:
+        if s.kind in {skVar, skLet, skConst}:
+          discard qualifiedLookUp(c.c, n, {checkAmbiguity, checkModule})
+        result = semTemplSymbol(c, n, s, c.noGenSym > 0, c.c.isAmbiguous)
+  of nkBind:
+    result = semTemplBody(c, n[0])
+  of nkBindStmt:
+    result = semBindStmt(c.c, n, c.toBind)
+  of nkMixinStmt:
+    if c.scopeN > 0: result = semTemplBodySons(c, n)
+    else: result = semMixinStmt(c.c, n, c.toMixin)
+  of nkEmpty, nkSym..nkNilLit, nkComesFrom:
+    discard
+  of nkIfStmt:
+    for i in 0..<n.len:
+      var it = n[i]
+      if it.len == 2:
+        openScope(c)
+        it[0] = semTemplBody(c, it[0])
+        it[1] = semTemplBody(c, it[1])
+        closeScope(c)
+      else:
+        n[i] = semTemplBodyScope(c, it)
+  of nkWhileStmt:
+    openScope(c)
+    for i in 0..<n.len:
+      n[i] = semTemplBody(c, n[i])
+    closeScope(c)
+  of nkCaseStmt:
+    openScope(c)
+    n[0] = semTemplBody(c, n[0])
+    for i in 1..<n.len:
+      var a = n[i]
+      checkMinSonsLen(a, 1, c.c.config)
+      for j in 0..<a.len-1:
+        a[j] = semTemplBody(c, a[j])
+      a[^1] = semTemplBodyScope(c, a[^1])
+    closeScope(c)
+  of nkForStmt, nkParForStmt:
+    openScope(c)
+    n[^2] = semTemplBody(c, n[^2])
+    for i in 0..<n.len - 2:
+      if n[i].kind == nkVarTuple:
+        for j in 0..<n[i].len-1:
+          addLocalDecl(c, n[i][j], skForVar)
+      else:
+        addLocalDecl(c, n[i], skForVar)
+    openScope(c)
+    n[^1] = semTemplBody(c, n[^1])
+    closeScope(c)
+    closeScope(c)
+  of nkBlockStmt, nkBlockExpr, nkBlockType:
+    checkSonsLen(n, 2, c.c.config)
+    openScope(c)
+    if n[0].kind != nkEmpty:
+      addLocalDecl(c, n[0], skLabel)
+      when false:
+        # labels are always 'gensym'ed:
+        let s = newGenSym(skLabel, n[0], c)
+        addPrelimDecl(c.c, s)
+        styleCheckDef(c.c, s)
+        onDef(n[0].info, s)
+        n[0] = newSymNode(s, n[0].info)
+    n[1] = semTemplBody(c, n[1])
+    closeScope(c)
+  of nkTryStmt, nkHiddenTryStmt:
+    checkMinSonsLen(n, 2, c.c.config)
+    n[0] = semTemplBodyScope(c, n[0])
+    for i in 1..<n.len:
+      var a = n[i]
+      checkMinSonsLen(a, 1, c.c.config)
+      openScope(c)
+      for j in 0..<a.len-1:
+        if a[j].isInfixAs():
+          addLocalDecl(c, a[j][2], skLet)
+          a[j][1] = semTemplBody(c, a[j][1])
+        else:
+          a[j] = semTemplBody(c, a[j])
+      a[^1] = semTemplBodyScope(c, a[^1])
+      closeScope(c)
+  of nkVarSection: semTemplSomeDecl(c, n, skVar)
+  of nkLetSection: semTemplSomeDecl(c, n, skLet)
+  of nkFormalParams:
+    checkMinSonsLen(n, 1, c.c.config)
+    semTemplSomeDecl(c, n, skParam, 1)
+    n[0] = semTemplBody(c, n[0])
+  of nkConstSection: semTemplSomeDecl(c, n, skConst)
+  of nkTypeSection:
+    for i in 0..<n.len:
+      var a = n[i]
+      if a.kind == nkCommentStmt: continue
+      if (a.kind != nkTypeDef): illFormedAst(a, c.c.config)
+      checkSonsLen(a, 3, c.c.config)
+      addLocalDecl(c, a[0], skType)
+    for i in 0..<n.len:
+      var a = n[i]
+      if a.kind == nkCommentStmt: continue
+      if (a.kind != nkTypeDef): illFormedAst(a, c.c.config)
+      checkSonsLen(a, 3, c.c.config)
+      if a[1].kind != nkEmpty:
+        openScope(c)
+        a[1] = semTemplBody(c, a[1])
+        a[2] = semTemplBody(c, a[2])
+        closeScope(c)
+      else:
+        a[2] = semTemplBody(c, a[2])
+  of nkObjectTy:
+    openScope(c)
+    result = semTemplBodySons(c, n)
+    closeScope(c)
+  of nkRecList:
+    for i in 0..<n.len:
+      var a = n[i]
+      case a.kind:
+      of nkCommentStmt, nkNilLit, nkSym, nkEmpty: continue
+      of nkIdentDefs:
+        semTemplIdentDef(c, a, skField)
+      of nkRecCase, nkRecWhen:
+        n[i] = semTemplBody(c, a)
+      else:
+        illFormedAst(a, c.c.config)
+  of nkRecCase:
+    semTemplIdentDef(c, n[0], skField)
+    for i in 1..<n.len:
+      n[i] = semTemplBody(c, n[i])
+  of nkProcDef, nkLambdaKinds:
+    result = semRoutineInTemplBody(c, n, skProc)
+  of nkFuncDef:
+    result = semRoutineInTemplBody(c, n, skFunc)
+  of nkMethodDef:
+    result = semRoutineInTemplBody(c, n, skMethod)
+  of nkIteratorDef:
+    result = semRoutineInTemplBody(c, n, skIterator)
+  of nkTemplateDef:
+    result = semRoutineInTemplBody(c, n, skTemplate)
+  of nkMacroDef:
+    result = semRoutineInTemplBody(c, n, skMacro)
+  of nkConverterDef:
+    result = semRoutineInTemplBody(c, n, skConverter)
+  of nkPragmaExpr:
+    result[0] = semTemplBody(c, n[0])
+  of nkPostfix:
+    result[1] = semTemplBody(c, n[1])
+  of nkPragma:
+    for x in n:
+      if x.kind == nkExprColonExpr:
+        x[1] = semTemplBody(c, x[1])
+  of nkBracketExpr:
+    if n.typ == nil:
+      # if a[b] is nested inside a typed expression, don't convert it
+      # back to `[]`(a, b), prepareOperand will not typecheck it again
+      # and so `[]` will not be resolved
+      # checking if a[b] is typed should be enough to cover this case
+      result = newNodeI(nkCall, n.info)
+      result.add newIdentNode(getIdent(c.c.cache, "[]"), n.info)
+      for i in 0..<n.len: result.add(n[i])
+    result = semTemplBodySons(c, result)
+  of nkCurlyExpr:
+    if n.typ == nil:
+      # see nkBracketExpr case for explanation
+      result = newNodeI(nkCall, n.info)
+      result.add newIdentNode(getIdent(c.c.cache, "{}"), n.info)
+      for i in 0..<n.len: result.add(n[i])
+    result = semTemplBodySons(c, result)
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
+    checkSonsLen(n, 2, c.c.config)
+    let a = n[0]
+    let b = n[1]
+
+    let k = a.kind
+    case k
+    of nkBracketExpr:
+      if a.typ == nil:
+        # see nkBracketExpr case above for explanation
+        result = newNodeI(nkCall, n.info)
+        result.add newIdentNode(getIdent(c.c.cache, "[]="), n.info)
+        for i in 0..<a.len: result.add(a[i])
+        result.add(b)
+      let a0 = semTemplBody(c, a[0])
+      result = semTemplBodySons(c, result)
+    of nkCurlyExpr:
+      if a.typ == nil:
+        # see nkBracketExpr case above for explanation
+        result = newNodeI(nkCall, n.info)
+        result.add newIdentNode(getIdent(c.c.cache, "{}="), n.info)
+        for i in 0..<a.len: result.add(a[i])
+        result.add(b)
+      result = semTemplBodySons(c, result)
+    else:
+      result = semTemplBodySons(c, n)
+  of nkCallKinds-{nkPostfix}:
+    # do not transform runnableExamples (bug #9143)
+    if not isRunnableExamples(n[0]):
+      result = semTemplBodySons(c, n)
+  of nkDotExpr, nkAccQuoted:
+    # dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam',
+    # so we use the generic code for nkDotExpr too
+    c.c.isAmbiguous = false
+    let s = qualifiedLookUp(c.c, n, {})
+    if s != nil:
+      # mirror the nkIdent case
+      # do not symchoice a quoted template parameter (bug #2390):
+      if s.owner == c.owner and s.kind == skParam and
+          n.kind == nkAccQuoted and n.len == 1:
+        incl(s.flags, sfUsed)
+        onUse(n.info, s)
+        return newSymNode(s, n.info)
+      elif contains(c.toBind, s.id):
+        return symChoice(c.c, n, s, scClosed, c.noGenSym > 0)
+      elif contains(c.toMixin, s.name.id):
+        return symChoice(c.c, n, s, scForceOpen, c.noGenSym > 0)
+      else:
+        if s.kind in {skVar, skLet, skConst}:
+          discard qualifiedLookUp(c.c, n, {checkAmbiguity, checkModule})
+        return semTemplSymbol(c, n, s, c.noGenSym > 0, c.c.isAmbiguous)
+    if n.kind == nkDotExpr:
+      result = n
+      result[0] = semTemplBody(c, n[0])
+      inc c.noGenSym
+      result[1] = semTemplBody(c, n[1])
+      dec c.noGenSym
+      if result[1].kind == nkSym and result[1].sym.kind in routineKinds:
+        # prevent `dotTransformation` from rewriting this node to `nkIdent`
+        # by making it a symchoice
+        # in generics this becomes `nkClosedSymChoice` but this breaks code
+        # as the old behavior here was that this became `nkIdent`
+        var choice = newNodeIT(nkOpenSymChoice, n[1].info, newTypeS(tyNone, c.c))
+        choice.add result[1]
+        result[1] = choice
+    else:
+      result = semTemplBodySons(c, n)
+  of nkExprColonExpr, nkExprEqExpr:
+    if n.len == 2:
+      inc c.noGenSym
+      result[0] = semTemplBody(c, n[0])
+      dec c.noGenSym
+      result[1] = semTemplBody(c, n[1])
+    else:
+      result = semTemplBodySons(c, n)
+  of nkTableConstr:
+    # also transform the keys (bug #12595)
+    for i in 0..<n.len:
+      result[i] = semTemplBodySons(c, n[i])
+  else:
+    result = semTemplBodySons(c, n)
+
+proc semTemplBodyDirty(c: var TemplCtx, n: PNode): PNode =
+  result = n
+  semIdeForTemplateOrGenericCheck(c.c.config, n, c.cursorInBody)
+  case n.kind
+  of nkIdent:
+    let s = qualifiedLookUp(c.c, n, {})
+    if s != nil:
+      if s.owner == c.owner and s.kind == skParam:
+        result = newSymNode(s, n.info)
+      elif contains(c.toBind, s.id):
+        result = symChoice(c.c, n, s, scClosed)
+  of nkBind:
+    result = semTemplBodyDirty(c, n[0])
+  of nkBindStmt:
+    result = semBindStmt(c.c, n, c.toBind)
+  of nkEmpty, nkSym..nkNilLit, nkComesFrom:
+    discard
+  else:
+    # dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam',
+    # so we use the generic code for nkDotExpr too
+    if n.kind == nkDotExpr or n.kind == nkAccQuoted:
+      let s = qualifiedLookUp(c.c, n, {})
+      if s != nil and contains(c.toBind, s.id):
+        return symChoice(c.c, n, s, scClosed)
+    result = n
+    for i in 0..<n.len:
+      result[i] = semTemplBodyDirty(c, n[i])
+
+# in semstmts.nim:
+proc semProcAnnotation(c: PContext, prc: PNode; validPragmas: TSpecialWords): PNode
+
+proc semTemplateDef(c: PContext, n: PNode): PNode =
+  result = semProcAnnotation(c, n, templatePragmas)
+  if result != nil: return result
+  result = n
+  var s: PSym
+  if isTopLevel(c):
+    s = semIdentVis(c, skTemplate, n[namePos], {sfExported})
+    incl(s.flags, sfGlobal)
+  else:
+    s = semIdentVis(c, skTemplate, n[namePos], {})
+  assert s.kind == skTemplate
+
+  styleCheckDef(c, s)
+  onDef(n[namePos].info, s)
+  # check parameter list:
+  #s.scope = c.currentScope
+  # push noalias flag at first to prevent unwanted recursive calls:
+  incl(s.flags, sfNoalias)
+  pushOwner(c, s)
+  openScope(c)
+  n[namePos] = newSymNode(s)
+  s.ast = n # for implicitPragmas to use
+  pragmaCallable(c, s, n, templatePragmas)
+  implicitPragmas(c, s, n.info, templatePragmas)
+
+  setGenericParamsMisc(c, n)
+  # process parameters:
+  var allUntyped = true
+  var nullary = true
+  if n[paramsPos].kind != nkEmpty:
+    semParamList(c, n[paramsPos], n[genericParamsPos], s)
+    # a template's parameters are not gensym'ed even if that was originally the
+    # case as we determine whether it's a template parameter in the template
+    # body by the absence of the sfGenSym flag:
+    let retType = s.typ.returnType
+    if retType != nil and retType.kind != tyUntyped:
+      allUntyped = false
+    for i in 1..<s.typ.n.len:
+      let param = s.typ.n[i].sym
+      if param.name.id != ord(wUnderscore):
+        param.flags.incl sfTemplateParam
+        param.flags.excl sfGenSym
+      if param.typ.kind != tyUntyped: allUntyped = false
+      # no default value, parameters required in call
+      if param.ast == nil: nullary = false
+  else:
+    s.typ = newTypeS(tyProc, c)
+    # XXX why do we need tyTyped as a return type again?
+    s.typ.n = newNodeI(nkFormalParams, n.info)
+    rawAddSon(s.typ, newTypeS(tyTyped, c))
+    s.typ.n.add newNodeIT(nkType, n.info, s.typ.returnType)
+  if n[genericParamsPos].safeLen == 0:
+    # restore original generic type params as no explicit or implicit were found
+    n[genericParamsPos] = n[miscPos][1]
+    n[miscPos] = c.graph.emptyNode
+  if allUntyped: incl(s.flags, sfAllUntyped)
+  if nullary and
+      n[genericParamsPos].kind == nkEmpty and
+      n[bodyPos].kind != nkEmpty:
+    # template can be called with alias syntax, remove pushed noalias flag
+    excl(s.flags, sfNoalias)
+
+  if n[patternPos].kind != nkEmpty:
+    n[patternPos] = semPattern(c, n[patternPos], s)
+
+  var ctx = TemplCtx(
+    toBind: initIntSet(),
+    toMixin: initIntSet(),
+    toInject: initIntSet(),
+    c: c,
+    owner: s
+  )
+  # handle default params:
+  for i in 1..<s.typ.n.len:
+    let param = s.typ.n[i].sym
+    if param.ast != nil:
+      # param default values need to be treated like template body:
+      if sfDirty in s.flags:
+        param.ast = semTemplBodyDirty(ctx, param.ast)
+      else:
+        param.ast = semTemplBody(ctx, param.ast)
+      if param.ast.referencesAnotherParam(s):
+        param.ast.flags.incl nfDefaultRefsParam
+  if sfDirty in s.flags:
+    n[bodyPos] = semTemplBodyDirty(ctx, n[bodyPos])
+  else:
+    n[bodyPos] = semTemplBody(ctx, n[bodyPos])
+  # only parameters are resolved, no type checking is performed
+  semIdeForTemplateOrGeneric(c, n[bodyPos], ctx.cursorInBody)
+  closeScope(c)
+  popOwner(c)
+
+  if sfCustomPragma in s.flags:
+    if n[bodyPos].kind != nkEmpty:
+      localError(c.config, n[bodyPos].info, errImplOfXNotAllowed % s.name.s)
+  elif n[bodyPos].kind == nkEmpty:
+    localError(c.config, n.info, "implementation of '$1' expected" % s.name.s)
+  var (proto, comesFromShadowscope) = searchForProc(c, c.currentScope, s)
+  if proto == nil:
+    addInterfaceOverloadableSymAt(c, c.currentScope, s)
+  elif not comesFromShadowscope:
+    if {sfTemplateRedefinition, sfGenSym} * s.flags == {}:
+      #wrongRedefinition(c, n.info, proto.name.s, proto.info)
+      message(c.config, n.info, warnImplicitTemplateRedefinition, s.name.s)
+    symTabReplace(c.currentScope.symbols, proto, s)
+  if n[patternPos].kind != nkEmpty:
+    c.patterns.add(s)
+
+proc semPatternBody(c: var TemplCtx, n: PNode): PNode =
+  template templToExpand(s: untyped): untyped =
+    s.kind == skTemplate and (s.typ.len == 1 or sfAllUntyped in s.flags)
+
+  proc newParam(c: var TemplCtx, n: PNode, s: PSym): PNode =
+    # the param added in the current scope is actually wrong here for
+    # macros because they have a shadowed param of type 'PNimNode' (see
+    # semtypes.addParamOrResult). Within the pattern we have to ensure
+    # to use the param with the proper type though:
+    incl(s.flags, sfUsed)
+    onUse(n.info, s)
+    let x = c.owner.typ.n[s.position+1].sym
+    assert x.name == s.name
+    result = newSymNode(x, n.info)
+
+  proc handleSym(c: var TemplCtx, n: PNode, s: PSym): PNode =
+    result = n
+    if s != nil:
+      if s.owner == c.owner and s.kind == skParam:
+        result = newParam(c, n, s)
+      elif contains(c.toBind, s.id):
+        result = symChoice(c.c, n, s, scClosed)
+      elif templToExpand(s):
+        result = semPatternBody(c, semTemplateExpr(c.c, n, s, {efNoSemCheck}))
+      else:
+        discard
+        # we keep the ident unbound for matching instantiated symbols and
+        # more flexibility
+
+  proc expectParam(c: var TemplCtx, n: PNode): PNode =
+    let s = qualifiedLookUp(c.c, n, {})
+    if s != nil and s.owner == c.owner and s.kind == skParam:
+      result = newParam(c, n, s)
+    else:
+      localError(c.c.config, n.info, "invalid expression")
+      result = n
+
+  result = n
+  case n.kind
+  of nkIdent:
+    let s = qualifiedLookUp(c.c, n, {})
+    result = handleSym(c, n, s)
+  of nkBindStmt:
+    result = semBindStmt(c.c, n, c.toBind)
+  of nkEmpty, nkSym..nkNilLit: discard
+  of nkCurlyExpr:
+    # we support '(pattern){x}' to bind a subpattern to a parameter 'x';
+    # '(pattern){|x}' does the same but the matches will be gathered in 'x'
+    if n.len != 2:
+      localError(c.c.config, n.info, "invalid expression")
+    elif n[1].kind == nkIdent:
+      n[0] = semPatternBody(c, n[0])
+      n[1] = expectParam(c, n[1])
+    elif n[1].kind == nkPrefix and n[1][0].kind == nkIdent:
+      let opr = n[1][0]
+      if opr.ident.s == "|":
+        n[0] = semPatternBody(c, n[0])
+        n[1][1] = expectParam(c, n[1][1])
+      else:
+        localError(c.c.config, n.info, "invalid expression")
+    else:
+      localError(c.c.config, n.info, "invalid expression")
+  of nkStmtList, nkStmtListExpr:
+    if stupidStmtListExpr(n):
+      result = semPatternBody(c, n.lastSon)
+    else:
+      for i in 0..<n.len:
+        result[i] = semPatternBody(c, n[i])
+  of nkCallKinds:
+    let s = qualifiedLookUp(c.c, n[0], {})
+    if s != nil:
+      if s.owner == c.owner and s.kind == skParam: discard
+      elif contains(c.toBind, s.id): discard
+      elif templToExpand(s):
+        return semPatternBody(c, semTemplateExpr(c.c, n, s, {efNoSemCheck}))
+
+    if n.kind == nkInfix and (let id = considerQuotedIdent(c.c, n[0]); id != nil):
+      # we interpret `*` and `|` only as pattern operators if they occur in
+      # infix notation, so that '`*`(a, b)' can be used for verbatim matching:
+      if id.s == "*" or id.s == "**":
+        result = newNodeI(nkPattern, n.info, n.len)
+        result[0] = newIdentNode(id, n.info)
+        result[1] = semPatternBody(c, n[1])
+        result[2] = expectParam(c, n[2])
+        return
+      elif id.s == "|":
+        result = newNodeI(nkPattern, n.info, n.len)
+        result[0] = newIdentNode(id, n.info)
+        result[1] = semPatternBody(c, n[1])
+        result[2] = semPatternBody(c, n[2])
+        return
+
+    if n.kind == nkPrefix and (let id = considerQuotedIdent(c.c, n[0]); id != nil):
+      if id.s == "~":
+        result = newNodeI(nkPattern, n.info, n.len)
+        result[0] = newIdentNode(id, n.info)
+        result[1] = semPatternBody(c, n[1])
+        return
+
+    for i in 0..<n.len:
+      result[i] = semPatternBody(c, n[i])
+  else:
+    # dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam',
+    # so we use the generic code for nkDotExpr too
+    case n.kind
+    of nkDotExpr, nkAccQuoted:
+      let s = qualifiedLookUp(c.c, n, {})
+      if s != nil:
+        if contains(c.toBind, s.id):
+          return symChoice(c.c, n, s, scClosed)
+        else:
+          return newIdentNode(s.name, n.info)
+    of nkPar:
+      if n.len == 1: return semPatternBody(c, n[0])
+    else: discard
+    for i in 0..<n.len:
+      result[i] = semPatternBody(c, n[i])
+
+proc semPattern(c: PContext, n: PNode; s: PSym): PNode =
+  openScope(c)
+  var ctx = TemplCtx(
+    toBind: initIntSet(),
+    toMixin: initIntSet(),
+    toInject: initIntSet(),
+    c: c,
+    owner: getCurrOwner(c)
+  )
+  result = flattenStmts(semPatternBody(ctx, n))
+  if result.kind in {nkStmtList, nkStmtListExpr}:
+    if result.len == 1:
+      result = result[0]
+    elif result.len == 0:
+      localError(c.config, n.info, "a pattern cannot be empty")
+  closeScope(c)
+  addPattern(c, LazySym(sym: s))