Files
Nim/compiler/lookups.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

861 lines
30 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module implements lookup helpers.
import std/[algorithm, strutils, tables]
when defined(nimPreviewSlimSystem):
import std/assertions
import
intsets, ast, astalgo, idents, semdata, types, msgs, options,
renderer, lineinfos, modulegraphs, astmsgs, sets, wordrecg
proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope)
proc noidentError(conf: ConfigRef; n, origin: PNode) =
var m = ""
if origin != nil:
m.add "in expression '" & origin.renderTree & "': "
m.add "identifier expected, but found '" & n.renderTree & "'"
localError(conf, n.info, m)
proc considerQuotedIdent*(c: PContext; n: PNode, origin: PNode = nil): PIdent =
## Retrieve a PIdent from a PNode, taking into account accent nodes.
## ``origin`` can be nil. If it is not nil, it is used for a better
## error message.
template handleError(n, origin: PNode) =
noidentError(c.config, n, origin)
result = getIdent(c.cache, "<Error>")
case n.kind
of nkIdent: result = n.ident
of nkSym: result = n.sym.name
of nkAccQuoted:
case n.len
of 0: handleError(n, origin)
of 1: result = considerQuotedIdent(c, n[0], origin)
else:
var id = ""
for i in 0..<n.len:
let x = n[i]
case x.kind
of nkIdent: id.add(x.ident.s)
of nkSym: id.add(x.sym.name.s)
of nkSymChoices:
if x[0].kind == nkSym:
id.add(x[0].sym.name.s)
else:
handleError(n, origin)
of nkLiterals - nkFloatLiterals: id.add(x.renderTree)
else: handleError(n, origin)
result = getIdent(c.cache, id)
of nkOpenSymChoice, nkClosedSymChoice:
if n[0].kind == nkSym:
result = n[0].sym.name
else:
handleError(n, origin)
else:
handleError(n, origin)
template addSym*(scope: PScope, s: PSym) =
strTableAdd(scope.symbols, s)
proc addUniqueSym*(scope: PScope, s: PSym): PSym =
result = strTableInclReportConflict(scope.symbols, s)
proc openScope*(c: PContext): PScope {.discardable.} =
result = PScope(parent: c.currentScope,
symbols: newStrTable(),
depthLevel: c.scopeDepth + 1)
c.currentScope = result
proc rawCloseScope*(c: PContext) =
c.currentScope = c.currentScope.parent
proc closeScope*(c: PContext) =
ensureNoMissingOrUnusedSymbols(c, c.currentScope)
rawCloseScope(c)
iterator allScopes*(scope: PScope): PScope =
var current = scope
while current != nil:
yield current
current = current.parent
iterator localScopesFrom*(c: PContext; scope: PScope): PScope =
for s in allScopes(scope):
if s == c.topLevelScope: break
yield s
proc isShadowScope*(s: PScope): bool {.inline.} =
s.parent != nil and s.parent.depthLevel == s.depthLevel
proc localSearchInScope*(c: PContext, s: PIdent): PSym =
var scope = c.currentScope
result = strTableGet(scope.symbols, s)
while result == nil and scope.isShadowScope:
# We are in a shadow scope, check in the parent too
scope = scope.parent
result = strTableGet(scope.symbols, s)
proc initIdentIter(ti: var ModuleIter; marked: var IntSet; im: ImportedModule; name: PIdent;
g: ModuleGraph): PSym =
result = initModuleIter(ti, g, im.m, name)
while result != nil:
let b =
case im.mode
of importAll: true
of importSet: result.id in im.imported
of importExcept: name.id notin im.exceptSet
if b and not containsOrIncl(marked, result.id):
return result
result = nextModuleIter(ti, g)
proc nextIdentIter(ti: var ModuleIter; marked: var IntSet; im: ImportedModule;
g: ModuleGraph): PSym =
while true:
result = nextModuleIter(ti, g)
if result == nil: return nil
case im.mode
of importAll:
if not containsOrIncl(marked, result.id):
return result
of importSet:
if result.id in im.imported and not containsOrIncl(marked, result.id):
return result
of importExcept:
if result.name.id notin im.exceptSet and not containsOrIncl(marked, result.id):
return result
iterator symbols(im: ImportedModule; marked: var IntSet; name: PIdent; g: ModuleGraph): PSym =
var ti: ModuleIter
var candidate = initIdentIter(ti, marked, im, name, g)
while candidate != nil:
yield candidate
candidate = nextIdentIter(ti, marked, im, g)
iterator importedItems*(c: PContext; name: PIdent): PSym =
var marked = initIntSet()
for im in c.imports.mitems:
for s in symbols(im, marked, name, c.graph):
yield s
proc allPureEnumFields(c: PContext; name: PIdent): seq[PSym] =
var ti: TIdentIter
result = @[]
var res = initIdentIter(ti, c.pureEnumFields, name)
while res != nil:
result.add res
res = nextIdentIter(ti, c.pureEnumFields)
iterator allSyms*(c: PContext): (PSym, int, bool) =
# really iterate over all symbols in all the scopes. This is expensive
# and only used by suggest.nim.
var isLocal = true
var scopeN = 0
for scope in allScopes(c.currentScope):
if scope == c.topLevelScope: isLocal = false
dec scopeN
for item in scope.symbols:
yield (item, scopeN, isLocal)
dec scopeN
isLocal = false
for im in c.imports.mitems:
for s in modulegraphs.allSyms(c.graph, im.m):
assert s != nil
yield (s, scopeN, isLocal)
iterator uniqueSyms*(c: PContext): (PSym, int, bool) =
## Like [allSyms] except only returns unique symbols (Uniqueness determined by line + name)
# Track seen symbols so we don't duplicate them.
# The int is for the symbols name, and line info is
# to be able to tell apart symbols with same name but on different lines
var seen = initHashSet[(TLineInfo, int)]()
for res in allSyms(c):
if not seen.containsOrIncl((res[0].info, res[0].name.id)):
yield res
proc someSymFromImportTable*(c: PContext; name: PIdent; ambiguous: var bool): PSym =
var marked = initIntSet()
var symSet = OverloadableSyms
result = nil
block outer:
for im in c.imports.mitems:
for s in symbols(im, marked, name, c.graph):
if result == nil:
result = s
elif s.kind notin symSet or result.kind notin symSet:
ambiguous = true
break outer
proc searchInScopes*(c: PContext, s: PIdent; ambiguous: var bool): PSym =
for scope in allScopes(c.currentScope):
result = strTableGet(scope.symbols, s)
if result != nil: return result
result = someSymFromImportTable(c, s, ambiguous)
proc debugScopes*(c: PContext; limit=0, max = int.high) {.deprecated.} =
var i = 0
var count = 0
for scope in allScopes(c.currentScope):
echo "scope ", i
for h in 0..high(scope.symbols.data):
if scope.symbols.data[h] != nil:
if count >= max: return
echo count, ": ", scope.symbols.data[h].name.s
count.inc
if i == limit: return
inc i
proc searchInScopesAllCandidatesFilterBy*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSym] =
result = @[]
for scope in allScopes(c.currentScope):
var ti: TIdentIter
var candidate = initIdentIter(ti, scope.symbols, s)
while candidate != nil:
if candidate.kind in filter:
result.add candidate
candidate = nextIdentIter(ti, scope.symbols)
if result.len == 0:
var marked = initIntSet()
for im in c.imports.mitems:
for s in symbols(im, marked, s, c.graph):
if s.kind in filter:
result.add s
proc searchInScopesFilterBy*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSym] =
result = @[]
block outer:
for scope in allScopes(c.currentScope):
var ti: TIdentIter
var candidate = initIdentIter(ti, scope.symbols, s)
while candidate != nil:
if candidate.kind in filter:
result.add candidate
# Break here, because further symbols encountered would be shadowed
break outer
candidate = nextIdentIter(ti, scope.symbols)
if result.len == 0:
var marked = initIntSet()
for im in c.imports.mitems:
for s in symbols(im, marked, s, c.graph):
if s.kind in filter:
result.add s
proc isAmbiguous*(c: PContext, s: PIdent, filter: TSymKinds, sym: var PSym): bool =
result = false
block outer:
for scope in allScopes(c.currentScope):
var ti: TIdentIter
var candidate = initIdentIter(ti, scope.symbols, s)
var scopeHasCandidate = false
while candidate != nil:
if candidate.kind in filter:
if scopeHasCandidate:
# 2 candidates in same scope, ambiguous
return true
else:
scopeHasCandidate = true
sym = candidate
candidate = nextIdentIter(ti, scope.symbols)
if scopeHasCandidate:
# scope had a candidate but wasn't ambiguous
return false
var importsHaveCandidate = false
var marked = initIntSet()
for im in c.imports.mitems:
for s in symbols(im, marked, s, c.graph):
if s.kind in filter:
if importsHaveCandidate:
# 2 candidates among imports, ambiguous
return true
else:
importsHaveCandidate = true
sym = s
if importsHaveCandidate:
# imports had a candidate but wasn't ambiguous
return false
proc errorSym*(c: PContext, n: PNode): PSym =
## creates an error symbol to avoid cascading errors (for IDE support)
var m = n
# ensure that 'considerQuotedIdent' can't fail:
if m.kind == nkDotExpr: m = m[1]
let ident = if m.kind in {nkIdent, nkSym, nkAccQuoted}:
considerQuotedIdent(c, m)
else:
getIdent(c.cache, "err:" & renderTree(m))
result = newSym(skError, ident, c.idgen, getCurrOwner(c), n.info, {})
result.typ = errorType(c)
incl(result.flags, sfDiscardable)
# pretend it's from the top level scope to prevent cascading errors:
if c.config.cmd != cmdInteractive and c.compilesContextId == 0:
c.moduleScope.addSym(result)
type
TOverloadIterMode* = enum
oimDone, oimNoQualifier, oimSelfModule, oimOtherModule, oimSymChoice,
oimSymChoiceLocalLookup
TOverloadIter* = object
it*: TIdentIter
mit*: ModuleIter
m*: PSym
mode*: TOverloadIterMode
symChoiceIndex*: int
currentScope: PScope
importIdx: int
marked: IntSet
proc getSymRepr*(conf: ConfigRef; s: PSym, getDeclarationPath = true): string =
case s.kind
of routineKinds, skType:
result = getProcHeader(conf, s, getDeclarationPath = getDeclarationPath)
else:
result = "'$1'" % s.name.s
if getDeclarationPath:
result.addDeclaredLoc(conf, s)
proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) =
# check if all symbols have been used and defined:
var it: TTabIter
var s = initTabIter(it, scope.symbols)
var missingImpls = 0
var unusedSyms: seq[tuple[sym: PSym, key: string]]
while s != nil:
if sfForward in s.flags and s.kind notin {skType, skModule}:
# too many 'implementation of X' errors are annoying
# and slow 'suggest' down:
if missingImpls == 0:
localError(c.config, s.info, "implementation of '$1' expected" %
getSymRepr(c.config, s, getDeclarationPath=false))
inc missingImpls
elif {sfUsed, sfExported} * s.flags == {}:
if s.kind notin {skForVar, skParam, skMethod, skUnknown, skGenericParam, skEnumField}:
# XXX: implicit type params are currently skTypes
# maybe they can be made skGenericParam as well.
if s.typ != nil and tfImplicitTypeParam notin s.typ.flags and
s.typ.kind != tyGenericParam:
unusedSyms.add (s, toFileLineCol(c.config, s.info))
s = nextIter(it, scope.symbols)
for (s, _) in sortedByIt(unusedSyms, it.key):
message(c.config, s.info, hintXDeclaredButNotUsed, s.name.s)
proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string;
conflictsWith: TLineInfo, note = errGenerated) =
## Emit a redefinition error if in non-interactive mode
if c.config.cmd != cmdInteractive:
localError(c.config, info, note,
"redefinition of '$1'; previous declaration here: $2" %
[s, c.config $ conflictsWith])
# xxx pending bootstrap >= 1.4, replace all those overloads with a single one:
# proc addDecl*(c: PContext, sym: PSym, info = sym.info, scope = c.currentScope) {.inline.} =
proc addDeclAt*(c: PContext; scope: PScope, sym: PSym, info: TLineInfo) =
if sym.name.id == ord(wUnderscore): return
let conflict = scope.addUniqueSym(sym)
if conflict != nil:
if sym.kind == skModule and conflict.kind == skModule:
# e.g.: import foo; import foo
# xxx we could refine this by issuing a different hint for the case
# where a duplicate import happens inside an include.
if c.importModuleMap[sym.id] == c.importModuleMap[conflict.id]:
#only hints if the conflict is the actual module not just a shared name
localError(c.config, info, hintDuplicateModuleImport,
"duplicate import of '$1'; previous import here: $2" %
[sym.name.s, c.config $ conflict.info])
else:
wrongRedefinition(c, info, sym.name.s, conflict.info, errGenerated)
proc addDeclAt*(c: PContext; scope: PScope, sym: PSym) {.inline.} =
addDeclAt(c, scope, sym, sym.info)
proc addDecl*(c: PContext, sym: PSym, info: TLineInfo) {.inline.} =
addDeclAt(c, c.currentScope, sym, info)
proc addDecl*(c: PContext, sym: PSym) {.inline.} =
addDeclAt(c, c.currentScope, sym)
proc addPrelimDecl*(c: PContext, sym: PSym) =
discard c.currentScope.addUniqueSym(sym)
from ic / ic import addHidden
proc addInterfaceDeclAux(c: PContext, sym: PSym) =
## adds symbol to the module for either private or public access.
if sfExported in sym.flags:
# add to interface:
if c.module != nil: exportSym(c, sym)
else: internalError(c.config, sym.info, "addInterfaceDeclAux")
elif sym.kind in ExportableSymKinds and c.module != nil and isTopLevelInsideDeclaration(c, sym):
strTableAdd(semtabAll(c.graph, c.module), sym)
if c.config.symbolFiles != disabledSf:
addHidden(c.encoder, c.packedRepr, sym)
proc addInterfaceDeclAt*(c: PContext, scope: PScope, sym: PSym) =
## adds a symbol on the scope and the interface if appropriate
addDeclAt(c, scope, sym)
if not scope.isShadowScope:
# adding into a non-shadow scope, we need to handle exports, etc
addInterfaceDeclAux(c, sym)
proc addInterfaceDecl*(c: PContext, sym: PSym) {.inline.} =
## adds a decl and the interface if appropriate
addInterfaceDeclAt(c, c.currentScope, sym)
proc addOverloadableSymAt*(c: PContext; scope: PScope, fn: PSym) =
## adds an symbol to the given scope, will check for and raise errors if it's
## a redefinition as opposed to an overload.
if fn.kind notin OverloadableSyms:
internalError(c.config, fn.info, "addOverloadableSymAt")
return
if fn.name.id != ord(wUnderscore):
let check = strTableGet(scope.symbols, fn.name)
if check != nil and check.kind notin OverloadableSyms:
wrongRedefinition(c, fn.info, fn.name.s, check.info)
else:
scope.addSym(fn)
proc addInterfaceOverloadableSymAt*(c: PContext, scope: PScope, sym: PSym) =
## adds an overloadable symbol on the scope and the interface if appropriate
addOverloadableSymAt(c, scope, sym)
if not scope.isShadowScope:
# adding into a non-shadow scope, we need to handle exports, etc
addInterfaceDeclAux(c, sym)
proc openShadowScope*(c: PContext) =
## opens a shadow scope, just like any other scope except the depth is the
## same as the parent -- see `isShadowScope`.
c.currentScope = PScope(parent: c.currentScope,
symbols: newStrTable(),
depthLevel: c.scopeDepth)
proc closeShadowScope*(c: PContext) =
## closes the shadow scope, but doesn't merge any of the symbols
## Does not check for unused symbols or missing forward decls since a macro
## or template consumes this AST
rawCloseScope(c)
proc mergeShadowScope*(c: PContext) =
## close the existing scope and merge in all defined symbols, this will also
## trigger any export related code if this is into a non-shadow scope.
##
## Merges:
## shadow -> shadow: add symbols to the parent but check for redefinitions etc
## shadow -> non-shadow: the above, but also handle exports and all that
let shadowScope = c.currentScope
c.rawCloseScope
for sym in shadowScope.symbols:
if sym.kind in OverloadableSyms:
c.addInterfaceOverloadableSymAt(c.currentScope, sym)
else:
c.addInterfaceDecl(sym)
import std/editdistance, heapqueue
type SpellCandidate = object
dist: int
depth: int
msg: string
sym: PSym
template toOrderTup(a: SpellCandidate): (int, int, string) =
# `dist` is first, to favor nearby matches
# `depth` is next, to favor nearby enclosing scopes among ties
# `sym.name.s` is last, to make the list ordered and deterministic among ties
(a.dist, a.depth, a.msg)
proc `<`(a, b: SpellCandidate): bool =
a.toOrderTup < b.toOrderTup
proc mustFixSpelling(c: PContext): bool {.inline.} =
result = c.config.spellSuggestMax != 0 and c.compilesContextId == 0
# don't slowdown inside compiles()
proc fixSpelling(c: PContext, n: PNode, ident: PIdent, result: var string) =
## when we cannot find the identifier, suggest nearby spellings
var list = initHeapQueue[SpellCandidate]()
let name0 = ident.s.nimIdentNormalize
for (sym, depth, isLocal) in allSyms(c):
let depth = -depth - 1
let dist = editDistance(name0, sym.name.s.nimIdentNormalize)
var msg: string
msg.add "\n ($1, $2): '$3'" % [$dist, $depth, sym.name.s]
list.push SpellCandidate(dist: dist, depth: depth, msg: msg, sym: sym)
if list.len == 0: return
let e0 = list[0]
var
count = 0
last: PIdent = nil
while true:
# pending https://github.com/timotheecour/Nim/issues/373 use more efficient `itemsSorted`.
if list.len == 0: break
let e = list.pop()
if c.config.spellSuggestMax == spellSuggestSecretSauce:
const
minLengthForSuggestion = 4
maxCount = 3 # avoids ton of matches; three counts for equal distances
if e.dist > e0.dist or count >= maxCount or name0.len < minLengthForSuggestion: break
elif count >= c.config.spellSuggestMax: break
if count == 0:
result.add "\ncandidates (edit distance, scope distance); see '--spellSuggest': "
if e.sym.name != last:
result.add e.msg
count.inc
last = e.sym.name
proc errorUseQualifier(c: PContext; info: TLineInfo; s: PSym; amb: var bool): PSym =
var err = "ambiguous identifier: '" & s.name.s & "'"
var i = 0
var ignoredModules = 0
for candidate in importedItems(c, s.name):
if i == 0: err.add " -- use one of the following:\n"
else: err.add "\n"
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ)
if candidate.kind == skModule:
inc ignoredModules
else:
result = candidate
inc i
if ignoredModules != i-1:
localError(c.config, info, errGenerated, err)
result = nil
else:
amb = false
proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) =
var amb: bool
discard errorUseQualifier(c, info, s, amb)
proc errorUseQualifier(c: PContext; info: TLineInfo; candidates: seq[PSym]; prefix = "use one of") =
var err = "ambiguous identifier: '" & candidates[0].name.s & "'"
var i = 0
for candidate in candidates:
if i == 0: err.add " -- $1 the following:\n" % prefix
else: err.add "\n"
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ)
inc i
localError(c.config, info, errGenerated, err)
proc errorUseQualifier*(c: PContext; info:TLineInfo; choices: PNode) =
var candidates = newSeq[PSym](choices.len)
let prefix = if choices[0].typ.kind != tyProc: "use one of" else: "you need a helper proc to disambiguate"
for i, n in choices:
candidates[i] = n.sym
errorUseQualifier(c, info, candidates, prefix)
proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string, extra = "") =
var err: string
if name == "_":
err = "the special identifier '_' is ignored in declarations and cannot be used"
else:
err = "undeclared identifier: '" & name & "'"
if "`gensym" in name:
err.add "; if declared in a template, this identifier may be inconsistently marked inject or gensym"
if extra.len != 0:
err.add extra
if c.recursiveDep.len > 0:
err.add "\nThis might be caused by a recursive module dependency:\n"
err.add c.recursiveDep
# prevent excessive errors for 'nim check'
c.recursiveDep = ""
localError(c.config, info, errGenerated, err)
proc errorUndeclaredIdentifierHint*(c: PContext; n: PNode, ident: PIdent): PSym =
var extra = ""
if c.mustFixSpelling: fixSpelling(c, n, ident, extra)
errorUndeclaredIdentifier(c, n.info, ident.s, extra)
result = errorSym(c, n)
proc lookUp*(c: PContext, n: PNode): PSym =
# Looks up a symbol. Generates an error in case of nil.
var amb = false
case n.kind
of nkIdent:
result = searchInScopes(c, n.ident, amb)
if result == nil: result = errorUndeclaredIdentifierHint(c, n, n.ident)
of nkSym:
result = n.sym
of nkAccQuoted:
var ident = considerQuotedIdent(c, n)
result = searchInScopes(c, ident, amb)
if result == nil: result = errorUndeclaredIdentifierHint(c, n, ident)
else:
internalError(c.config, n.info, "lookUp")
return
if amb:
#contains(c.ambiguousSymbols, result.id):
result = errorUseQualifier(c, n.info, result, amb)
when false:
if result.kind == skStub: loadStub(result)
type
TLookupFlag* = enum
checkAmbiguity, checkUndeclared, checkModule, checkPureEnumFields
proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
const allExceptModule = {low(TSymKind)..high(TSymKind)} - {skModule, skPackage}
case n.kind
of nkIdent, nkAccQuoted:
var amb = false
var ident = considerQuotedIdent(c, n)
if checkModule in flags:
result = searchInScopes(c, ident, amb)
else:
let candidates = searchInScopesFilterBy(c, ident, allExceptModule)
if candidates.len > 0:
result = candidates[0]
amb = candidates.len > 1
if amb and checkAmbiguity in flags:
errorUseQualifier(c, n.info, candidates)
if result == nil:
let candidates = allPureEnumFields(c, ident)
if candidates.len > 0:
result = candidates[0]
amb = candidates.len > 1
if amb and checkAmbiguity in flags:
errorUseQualifier(c, n.info, candidates)
if result == nil and checkUndeclared in flags:
result = errorUndeclaredIdentifierHint(c, n, ident)
elif checkAmbiguity in flags and result != nil and amb:
result = errorUseQualifier(c, n.info, result, amb)
c.isAmbiguous = amb
of nkSym:
result = n.sym
of nkDotExpr:
result = nil
var m = qualifiedLookUp(c, n[0], (flags * {checkUndeclared}) + {checkModule})
if m != nil and m.kind == skModule:
var ident: PIdent = nil
if n[1].kind == nkIdent:
ident = n[1].ident
elif n[1].kind == nkAccQuoted:
ident = considerQuotedIdent(c, n[1])
if ident != nil:
if m == c.module:
result = strTableGet(c.topLevelScope.symbols, ident)
else:
if c.importModuleLookup.getOrDefault(m.name.id).len > 1:
var amb: bool
result = errorUseQualifier(c, n.info, m, amb)
else:
result = someSym(c.graph, m, ident)
if result == nil and checkUndeclared in flags:
result = errorUndeclaredIdentifierHint(c, n[1], ident)
elif n[1].kind == nkSym:
result = n[1].sym
if result.owner != nil and result.owner != m and checkUndeclared in flags:
# dotExpr in templates can end up here
result = errorUndeclaredIdentifierHint(c, n[1], considerQuotedIdent(c, n[1]))
elif checkUndeclared in flags and
n[1].kind notin {nkOpenSymChoice, nkClosedSymChoice}:
localError(c.config, n[1].info, "identifier expected, but got: " &
renderTree(n[1]))
result = errorSym(c, n[1])
else:
result = nil
when false:
if result != nil and result.kind == skStub: loadStub(result)
proc initOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym =
o.importIdx = -1
o.marked = initIntSet()
case n.kind
of nkIdent, nkAccQuoted:
var ident = considerQuotedIdent(c, n)
var scope = c.currentScope
o.mode = oimNoQualifier
while true:
result = initIdentIter(o.it, scope.symbols, ident)
if result != nil:
o.currentScope = scope
break
else:
scope = scope.parent
if scope == nil:
for i in 0..c.imports.high:
result = initIdentIter(o.mit, o.marked, c.imports[i], ident, c.graph)
if result != nil:
o.currentScope = nil
o.importIdx = i
return result
return nil
of nkSym:
result = n.sym
o.mode = oimDone
of nkDotExpr:
o.mode = oimOtherModule
o.m = qualifiedLookUp(c, n[0], {checkUndeclared, checkModule})
if o.m != nil and o.m.kind == skModule:
var ident: PIdent = nil
if n[1].kind == nkIdent:
ident = n[1].ident
elif n[1].kind == nkAccQuoted:
ident = considerQuotedIdent(c, n[1], n)
if ident != nil:
if o.m == c.module:
# a module may access its private members:
result = initIdentIter(o.it, c.topLevelScope.symbols,
ident)
o.mode = oimSelfModule
else:
result = initModuleIter(o.mit, c.graph, o.m, ident)
else:
noidentError(c.config, n[1], n)
result = errorSym(c, n[1])
of nkClosedSymChoice, nkOpenSymChoice:
o.mode = oimSymChoice
if n[0].kind == nkSym:
result = n[0].sym
else:
o.mode = oimDone
return nil
o.symChoiceIndex = 1
o.marked = initIntSet()
incl(o.marked, result.id)
else: discard
when false:
if result != nil and result.kind == skStub: loadStub(result)
proc lastOverloadScope*(o: TOverloadIter): int =
case o.mode
of oimNoQualifier:
result = if o.importIdx >= 0: 0
elif o.currentScope.isNil: -1
else: o.currentScope.depthLevel
of oimSelfModule: result = 1
of oimOtherModule: result = 0
else: result = -1
proc nextOverloadIterImports(o: var TOverloadIter, c: PContext, n: PNode): PSym =
assert o.currentScope == nil
var idx = o.importIdx+1
o.importIdx = c.imports.len # assume the other imported modules lack this symbol too
while idx < c.imports.len:
result = initIdentIter(o.mit, o.marked, c.imports[idx], o.it.name, c.graph)
if result != nil:
# oh, we were wrong, some other module had the symbol, so remember that:
o.importIdx = idx
break
inc idx
proc symChoiceExtension(o: var TOverloadIter; c: PContext; n: PNode): PSym =
assert o.currentScope == nil
while o.importIdx < c.imports.len:
result = initIdentIter(o.mit, o.marked, c.imports[o.importIdx], o.it.name, c.graph)
#while result != nil and result.id in o.marked:
# result = nextIdentIter(o.it, o.marked, c.imports[o.importIdx])
if result != nil:
#assert result.id notin o.marked
return result
inc o.importIdx
proc nextOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym =
case o.mode
of oimDone:
result = nil
of oimNoQualifier:
if o.currentScope != nil:
assert o.importIdx < 0
result = nextIdentIter(o.it, o.currentScope.symbols)
while result == nil:
o.currentScope = o.currentScope.parent
if o.currentScope != nil:
result = initIdentIter(o.it, o.currentScope.symbols, o.it.name)
# BUGFIX: o.it.name <-> n.ident
else:
o.importIdx = 0
if c.imports.len > 0:
result = initIdentIter(o.mit, o.marked, c.imports[o.importIdx], o.it.name, c.graph)
if result == nil:
result = nextOverloadIterImports(o, c, n)
break
elif o.importIdx < c.imports.len:
result = nextIdentIter(o.mit, o.marked, c.imports[o.importIdx], c.graph)
if result == nil:
result = nextOverloadIterImports(o, c, n)
else:
result = nil
of oimSelfModule:
result = nextIdentIter(o.it, c.topLevelScope.symbols)
of oimOtherModule:
result = nextModuleIter(o.mit, c.graph)
of oimSymChoice:
if o.symChoiceIndex < n.len:
result = n[o.symChoiceIndex].sym
incl(o.marked, result.id)
inc o.symChoiceIndex
elif n.kind == nkOpenSymChoice:
# try 'local' symbols too for Koenig's lookup:
o.mode = oimSymChoiceLocalLookup
o.currentScope = c.currentScope
result = firstIdentExcluding(o.it, o.currentScope.symbols,
n[0].sym.name, o.marked)
while result == nil:
o.currentScope = o.currentScope.parent
if o.currentScope != nil:
result = firstIdentExcluding(o.it, o.currentScope.symbols,
n[0].sym.name, o.marked)
else:
o.importIdx = 0
result = symChoiceExtension(o, c, n)
break
if result != nil:
incl o.marked, result.id
of oimSymChoiceLocalLookup:
if o.currentScope != nil:
result = nextIdentExcluding(o.it, o.currentScope.symbols, o.marked)
while result == nil:
o.currentScope = o.currentScope.parent
if o.currentScope != nil:
result = firstIdentExcluding(o.it, o.currentScope.symbols,
n[0].sym.name, o.marked)
else:
o.importIdx = 0
result = symChoiceExtension(o, c, n)
break
if result != nil:
incl o.marked, result.id
elif o.importIdx < c.imports.len:
result = nextIdentIter(o.mit, o.marked, c.imports[o.importIdx], c.graph)
#assert result.id notin o.marked
#while result != nil and result.id in o.marked:
# result = nextIdentIter(o.it, c.imports[o.importIdx])
if result == nil:
inc o.importIdx
result = symChoiceExtension(o, c, n)
when false:
if result != nil and result.kind == skStub: loadStub(result)
proc pickSym*(c: PContext, n: PNode; kinds: set[TSymKind];
flags: TSymFlags = {}): PSym =
var o: TOverloadIter
var a = initOverloadIter(o, c, n)
while a != nil:
if a.kind in kinds and flags <= a.flags:
if result == nil: result = a
else: return nil # ambiguous
a = nextOverloadIter(o, c, n)