Files
Nim/compiler/semtempl.nim
metagn b1bfba9a31 stop gensym identifiers hijacking routine decl names in templates (#23392)
fixes #23326

In a routine declaration node in a template, if the routine is marked as
`gensym`, the compiler adds it as a new symbol to a preliminary scope of
the template. If it's not marked as gensym, then it searches the
preliminary scope of the template for the name of the routine, then when
it matches a template parameter or a gensym identifier, the compiler
replaces the name node with a symbol node of the found symbol.

This makes sense for the template parameter since it has to be replaced
later, but not really for the gensym identifier, as it doesn't allow us
to inject a routine with the same name as an identifier previously
declared as gensym (the problem in #23326 is when this is in another
`when` branch).

However this is the only channel to reuse a gensym symbol in a
declaration, so maybe removing it has side effects. For example if we
have:

```nim
proc foo(x: int) {.gensym.} = discard
proc foo(x: float) {.gensym.} = discard
```

it will not behave the same as

```nim
proc foo(x: int) {.gensym.} = discard
proc foo(x: float) = discard
```

behaved previously, which maybe allowed overloading over the gensym'd
symbols.

A note to the "undeclared identifier" error message has also been added
for a potential error code that implicitly depended on the old behavior
might give, namely ``undeclared identifier: 'abc`gensym123'``, which
happens when in a template an identifier is first declared gensym in
code that doesn't compile, then as a routine which injects by default,
then the identifier is used.

(cherry picked from commit 73b0b0d31c)
2024-05-21 18:51:03 +02:00

871 lines
29 KiB
Nim

#
#
# 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 =
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
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: PContext, n: PNode, s: PSym; isField: 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, 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-{skTemplate,skMacro}:
result = symChoice(c, n, s, scOpen, isField)
of skTemplate, skMacro:
result = symChoice(c, n, s, scOpen, isField)
if result.kind == nkSym:
# template/macro symbols might need to be semchecked again
# prepareOperand etc don't do this without setting the type to nil
result.typ = nil
of skGenericParam:
if isField and sfGenSym in s.flags: result = n
else: result = newSymNodeTypeDesc(s, c.idgen, n.info)
of skParam:
result = n
of skType:
if isField and sfGenSym in s.flags: result = n
else: result = newSymNodeTypeDesc(s, c.idgen, n.info)
else:
if isField and sfGenSym in s.flags: result = n
else: result = newSymNode(s, n.info)
# Issue #12832
when defined(nimsuggest):
suggestSym(c.graph, n.info, s, c.graph.usageSym, false)
# field access (dot expr) will be handled by builtinFieldAccess
if not isField:
styleCheckUse(c, n.info, s)
proc semRoutineInTemplName(c: var TemplCtx, n: PNode): 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:
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])
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':
if symBinding(n[pragmasPos]) == 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])
# 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
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 {skType, skVar, skLet, skConst}:
discard qualifiedLookUp(c.c, n, {checkAmbiguity, checkModule})
result = semTemplSymbol(c.c, n, s, c.noGenSym > 0)
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
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 {skType, skVar, skLet, skConst}:
discard qualifiedLookUp(c.c, n, {checkAmbiguity, checkModule})
return semTemplSymbol(c.c, n, s, c.noGenSym > 0)
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)
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[0]
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[0])
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
ctx.toBind = initIntSet()
ctx.toMixin = initIntSet()
ctx.toInject = initIntSet()
ctx.c = c
ctx.owner = s
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)
# set the symbol AST after pragmas, at least. This stops pragma that have
# been pushed (implicit) to be explicitly added to the template definition
# and misapplied to the body. see #18113
s.ast = n
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
ctx.toBind = initIntSet()
ctx.toMixin = initIntSet()
ctx.toInject = initIntSet()
ctx.c = c
ctx.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))