# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # This implements the first pass over the generic body; it resolves some # symbols. Thus for generics there is a two-phase symbol lookup just like # in C++. # A problem is that it cannot be detected if the symbol is introduced # as in ``var x = ...`` or used because macros/templates can hide this! # So we have to eval templates/macros right here so that symbol # lookup can be accurate. # included from sem.nim proc getIdentNode(c: PContext; n: PNode): PNode = case n.kind of nkPostfix: result = getIdentNode(c, n[1]) of nkPragmaExpr: result = getIdentNode(c, n[0]) of nkIdent, nkAccQuoted, nkSym: result = n else: illFormedAst(n, c.config) result = n type GenericCtx = object toMixin, toBind: IntSet cursorInBody: bool # only for nimsuggest bracketExpr: PNode TSemGenericFlag = enum withinBind, withinTypeDesc, withinMixin, withinConcept TSemGenericFlags = set[TSemGenericFlag] proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx): PNode proc semGenericStmtScope(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx): PNode = openScope(c) result = semGenericStmt(c, n, flags, ctx) closeScope(c) template macroToExpand(s): untyped = s.kind in {skMacro, skTemplate} and (s.typ.len == 1 or sfAllUntyped in s.flags) template macroToExpandSym(s): untyped = sfCustomPragma notin s.flags and s.kind in {skMacro, skTemplate} and (s.typ.len == 1) and not fromDotExpr template isMixedIn(sym): bool = let s = sym s.name.id in ctx.toMixin or (withinConcept in flags and s.magic == mNone and s.kind in OverloadableSyms) proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, ctx: var GenericCtx; flags: TSemGenericFlags, fromDotExpr=false): PNode = semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody) incl(s.flags, sfUsed) case s.kind of skUnknown: # Introduced in this pass! Leave it as an identifier. result = n of skProc, skFunc, skMethod, skIterator, skConverter, skModule: result = symChoice(c, n, s, scOpen) of skTemplate: if macroToExpandSym(s): onUse(n.info, s) result = semTemplateExpr(c, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, {}, ctx) else: result = symChoice(c, n, s, scOpen) of skMacro: if macroToExpandSym(s): onUse(n.info, s) result = semMacroExpr(c, n, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, {}, ctx) else: result = symChoice(c, n, s, scOpen) of skGenericParam: if s.typ != nil and s.typ.kind == tyStatic: if s.typ.n != nil: result = s.typ.n else: result = n else: result = newSymNodeTypeDesc(s, c.idgen, n.info) onUse(n.info, s) of skParam: result = n onUse(n.info, s) of skType: if (s.typ != nil) and (s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} == {}): result = newSymNodeTypeDesc(s, c.idgen, n.info) else: result = n onUse(n.info, s) of skEnumField: if overloadableEnums in c.features: result = symChoice(c, n, s, scOpen) else: result = newSymNode(s, n.info) onUse(n.info, s) else: result = newSymNode(s, n.info) onUse(n.info, s) proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx): PNode = result = n let ident = considerQuotedIdent(c, n) var amb = false var s = searchInScopes(c, ident, amb).skipAlias(n, c.config) if s == nil: s = strTableGet(c.pureEnumFields, ident) #if s != nil and contains(c.ambiguousSymbols, s.id): # s = nil if s == nil: if ident.id notin ctx.toMixin and withinMixin notin flags: errorUndeclaredIdentifier(c, n.info, ident.s) else: if withinBind in flags or s.id in ctx.toBind: result = symChoice(c, n, s, scClosed) elif s.isMixedIn: result = symChoice(c, n, s, scForceOpen) else: result = semGenericStmtSymbol(c, n, s, ctx, flags) # else: leave as nkIdent proc newDot(n, b: PNode): PNode = result = newNodeI(nkDotExpr, n.info) result.add(n[0]) result.add(b) proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx; isMacro: var bool): PNode = assert n.kind == nkDotExpr semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody) let luf = if withinMixin notin flags: {checkUndeclared, checkModule} else: {checkModule} var s = qualifiedLookUp(c, n, luf) if s != nil: result = semGenericStmtSymbol(c, n, s, ctx, flags) else: n[0] = semGenericStmt(c, n[0], flags, ctx) result = n let n = n[1] let ident = considerQuotedIdent(c, n) var candidates = searchInScopesFilterBy(c, ident, routineKinds) # .skipAlias(n, c.config) if candidates.len > 0: let s = candidates[0] # XXX take into account the other candidates! isMacro = s.kind in {skTemplate, skMacro} if withinBind in flags or s.id in ctx.toBind: result = newDot(result, symChoice(c, n, s, scClosed)) elif s.isMixedIn: result = newDot(result, symChoice(c, n, s, scForceOpen)) else: let syms = semGenericStmtSymbol(c, n, s, ctx, flags, fromDotExpr=true) if syms.kind == nkSym: let choice = symChoice(c, n, s, scForceOpen) choice.transitionSonsKind(nkClosedSymChoice) result = newDot(result, choice) else: result = newDot(result, syms) proc addTempDecl(c: PContext; n: PNode; kind: TSymKind) = let s = newSymS(skUnknown, getIdentNode(c, n), c) addPrelimDecl(c, s) styleCheckDef(c.config, n.info, s, kind) onDef(n.info, s) proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx): PNode = result = n when defined(nimsuggest): if withinTypeDesc in flags: inc c.inTypeContext #if conf.cmd == cmdIdeTools: suggestStmt(c, n) semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody) case n.kind of nkIdent, nkAccQuoted: result = lookup(c, n, flags, ctx) if result != nil and result.kind == nkSym: assert result.sym != nil markUsed(c, n.info, result.sym) of nkDotExpr: #let luf = if withinMixin notin flags: {checkUndeclared} else: {} #var s = qualifiedLookUp(c, n, luf) #if s != nil: result = semGenericStmtSymbol(c, n, s) # XXX for example: ``result.add`` -- ``add`` needs to be looked up here... var dummy: bool result = fuzzyLookup(c, n, flags, ctx, dummy) of nkSym: let a = n.sym let b = getGenSym(c, a) if b != a: n.sym = b of nkEmpty, succ(nkSym)..nkNilLit, nkComesFrom: # see tests/compile/tgensymgeneric.nim: # We need to open the gensym'ed symbol again so that the instantiation # creates a fresh copy; but this is wrong the very first reason for gensym # is that scope rules cannot be used! So simply removing 'sfGenSym' does # not work. Copying the symbol does not work either because we're already # the owner of the symbol! What we need to do is to copy the symbol # in the generic instantiation process... discard of nkBind: result = semGenericStmt(c, n[0], flags+{withinBind}, ctx) of nkMixinStmt: result = semMixinStmt(c, n, ctx.toMixin) of nkBindStmt: result = semBindStmt(c, n, ctx.toBind) of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkCommand, nkCallStrLit: # check if it is an expression macro: checkMinSonsLen(n, 1, c.config) let fn = n[0] var s = qualifiedLookUp(c, fn, {}) if s == nil and {withinMixin, withinConcept}*flags == {} and fn.kind in {nkIdent, nkAccQuoted} and considerQuotedIdent(c, fn).id notin ctx.toMixin: errorUndeclaredIdentifier(c, n.info, fn.renderTree) var first = int ord(withinConcept in flags) var mixinContext = false if s != nil: incl(s.flags, sfUsed) mixinContext = s.magic in {mDefined, mDeclared, mDeclaredInScope, mCompiles, mAstToStr} let whichChoice = if s.id in ctx.toBind: scClosed elif s.isMixedIn: scForceOpen else: scOpen let sc = symChoice(c, fn, s, whichChoice) case s.kind of skMacro: if macroToExpand(s) and sc.safeLen <= 1: onUse(fn.info, s) result = semMacroExpr(c, n, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, flags, ctx) else: n[0] = sc result = n mixinContext = true of skTemplate: if macroToExpand(s) and sc.safeLen <= 1: onUse(fn.info, s) result = semTemplateExpr(c, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, flags, ctx) else: n[0] = sc result = n # BUGFIX: we must not return here, we need to do first phase of # symbol lookup. Also since templates and macros can do scope injections # we need to put the ``c`` in ``t(c)`` in a mixin context to prevent # the famous "undeclared identifier: it" bug: mixinContext = true of skUnknown, skParam: # Leave it as an identifier. discard of skProc, skFunc, skMethod, skIterator, skConverter, skModule: result[0] = sc first = 1 # We're not interested in the example code during this pass so let's # skip it if s.magic == mRunnableExamples: first = result.safeLen # see trunnableexamples.fun3 of skGenericParam: result[0] = newSymNodeTypeDesc(s, c.idgen, fn.info) onUse(fn.info, s) first = 1 of skType: # bad hack for generics: if (s.typ != nil) and (s.typ.kind != tyGenericParam): result[0] = newSymNodeTypeDesc(s, c.idgen, fn.info) onUse(fn.info, s) first = 1 else: result[0] = newSymNode(s, fn.info) onUse(fn.info, s) first = 1 elif fn.kind == nkDotExpr: result[0] = fuzzyLookup(c, fn, flags, ctx, mixinContext) first = 1 # Consider 'when declared(globalsSlot): ThreadVarSetValue(globalsSlot, ...)' # in threads.nim: the subtle preprocessing here binds 'globalsSlot' which # is not exported and yet the generic 'threadProcWrapper' works correctly. let flags = if mixinContext: flags+{withinMixin} else: flags for i in first..