# # # 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.. 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 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 s.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 = for i in 0..= 2 and n[1].kind == nkPragma): let pragmaNode = n[1] for i in 0.. 0: local.flags.incl sfTemplateParam else: replaceIdentBySym(c.c, n, ident) 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: result = symChoice(c, n, s, scOpen, isField) of skGenericParam: if isField and sfGenSym in s.flags: result = n else: result = newSymNodeTypeDesc(s, n.info) of skParam: result = n of skType: if isField and sfGenSym in s.flags: result = n else: result = newSymNodeTypeDesc(s, 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.config, n.info, s, c.graph.usageSym, false) if {optStyleHint, optStyleError} * c.config.globalOptions != {}: styleCheckUse(c.config, 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 or sfGenSym in s.flags): incl(s.flags, sfUsed) result = newSymNode(s, n.info) onUse(n.info, s) else: for i in 0.. 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: 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..=", ">", "incl", "excl", "in", "notin", "isnot"] if sfSystemModule in s.owner.flags and s.name.s in names or s.owner.name.s == "vm" and s.name.s == "stackTrace": incl(s.flags, sfCallsite) styleCheckDef(c.config, s) onDef(n[0].info, s) # check parameter list: #s.scope = c.currentScope pushOwner(c, s) openScope(c) n[namePos] = newSymNode(s, n[namePos].info) if n[pragmasPos].kind != nkEmpty: pragma(c, s, n[pragmasPos], templatePragmas) var gp: PNode if n[genericParamsPos].kind != nkEmpty: n[genericParamsPos] = semGenericParamList(c, n[genericParamsPos]) gp = n[genericParamsPos] else: gp = newNodeI(nkGenericParams, n.info) # process parameters: var allUntyped = true if n[paramsPos].kind != nkEmpty: semParamList(c, n[paramsPos], gp, 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: for i in 1.. 0: if n[genericParamsPos].kind == nkEmpty: # we have a list of implicit type parameters: n[genericParamsPos] = gp 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 allUntyped: incl(s.flags, sfAllUntyped) if n[patternPos].kind != nkEmpty: n[patternPos] = semPattern(c, n[patternPos]) 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) s.ast = n result = 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 = searchForProc(c, c.currentScope, s) if proto == nil: addInterfaceOverloadableSymAt(c, c.currentScope, s) else: 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 proc stupidStmtListExpr(n: PNode): bool = for i in 0..