Skip to content

Commit

Permalink
Fix #5691 (#15158)
Browse files Browse the repository at this point in the history
* Fix #5691
* Cleanup and thoughts
* Use scope approach
* Seperate defined/declared/declaredInScope magics
* Fix declaredInScope
* Update spec accordingly
  • Loading branch information
Clyybber authored Aug 27, 2020
1 parent d11933a commit fb58066
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 85 deletions.
2 changes: 1 addition & 1 deletion compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ var
type
TMagic* = enum # symbols that require compiler magic:
mNone,
mDefined, mDefinedInScope, mCompiles, mArrGet, mArrPut, mAsgn,
mDefined, mDeclared, mDeclaredInScope, mCompiles, mArrGet, mArrPut, mAsgn,
mLow, mHigh, mSizeOf, mAlignOf, mOffsetOf, mTypeTrait,
mIs, mOf, mAddr, mType, mTypeOf,
mPlugin, mEcho, mShallowCopy, mSlurp, mStaticExec, mStatic,
Expand Down
1 change: 1 addition & 0 deletions compiler/condsyms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasStacktraceMsgs")
defineSymbol("nimDoesntTrackDefects")
defineSymbol("nimHasLentIterators")
defineSymbol("nimHasDeclaredMagic")
22 changes: 22 additions & 0 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ proc skipAlias*(s: PSym; n: PNode; conf: ConfigRef): PSym =

proc localSearchInScope*(c: PContext, s: PIdent): PSym =
result = strTableGet(c.currentScope.symbols, s)
var shadow = c.currentScope
while result == nil and shadow.parent != nil and shadow.depthLevel == shadow.parent.depthLevel:
# We are in a shadow scope, check in the parent too
result = strTableGet(shadow.parent.symbols, s)
shadow = shadow.parent

proc searchInScopes*(c: PContext, s: PIdent): PSym =
for scope in walkScopes(c.currentScope):
Expand Down Expand Up @@ -231,6 +236,23 @@ proc addInterfaceOverloadableSymAt*(c: PContext, scope: PScope, sym: PSym) =
addOverloadableSymAt(c, scope, sym)
addInterfaceDeclAux(c, sym)

proc openShadowScope*(c: PContext) =
c.currentScope = PScope(parent: c.currentScope,
symbols: newStrTable(),
depthLevel: c.scopeDepth)

proc closeShadowScope*(c: PContext) =
c.closeScope

proc mergeShadowScope*(c: PContext) =
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)

when defined(nimfix):
# when we cannot find the identifier, retry with a changed identifier:
proc altSpelling(x: PIdent): PIdent =
Expand Down
13 changes: 5 additions & 8 deletions compiler/sem.nim
Original file line number Diff line number Diff line change
Expand Up @@ -383,12 +383,10 @@ when not defined(nimHasSinkInference):

include hlo, seminst, semcall

when false:
# hopefully not required:
proc resetSemFlag(n: PNode) =
excl n.flags, nfSem
for i in 0..<n.safeLen:
resetSemFlag(n[i])
proc resetSemFlag(n: PNode) =
excl n.flags, nfSem
for i in 0..<n.safeLen:
resetSemFlag(n[i])

proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
s: PSym, flags: TExprFlags): PNode =
Expand All @@ -403,8 +401,7 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
c.friendModules.add(s.owner.getModule)
idSynchronizationPoint(5000)
result = macroResult
excl(result.flags, nfSem)
#resetSemFlag n
resetSemFlag result
if s.typ[0] == nil:
result = semStmt(c, result, flags)
else:
Expand Down
4 changes: 0 additions & 4 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,6 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
if c.currentScope.symbols.counter == counterInitial or syms.len != 0:
matches(c, n, orig, z)
if z.state == csMatch:
#if sym.name.s == "==" and (n.info ?? "temp3"):
# echo typeToString(sym.typ)
# writeMatches(z)

# little hack so that iterators are preferred over everything else:
if sym.kind == skIterator: inc(z.exactMatches, 200)
case best.state
Expand Down
42 changes: 22 additions & 20 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1854,29 +1854,32 @@ proc semYield(c: PContext, n: PNode): PNode =
elif c.p.owner.typ[0] != nil:
localError(c.config, n.info, errGenerated, "yield statement must yield a value")

proc lookUpForDefined(c: PContext, i: PIdent, onlyCurrentScope: bool): PSym =
if onlyCurrentScope:
result = localSearchInScope(c, i)
else:
result = searchInScopes(c, i) # no need for stub loading
proc semDefined(c: PContext, n: PNode): PNode =
checkSonsLen(n, 2, c.config)
# we replace this node by a 'true' or 'false' node:
result = newIntNode(nkIntLit, 0)
result.intVal = ord isDefined(c.config, considerQuotedIdent(c, n[1], n).s)
result.info = n.info
result.typ = getSysType(c.graph, n.info, tyBool)

proc lookUpForDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PSym =
proc lookUpForDeclared(c: PContext, n: PNode, onlyCurrentScope: bool): PSym =
case n.kind
of nkIdent:
result = lookUpForDefined(c, n.ident, onlyCurrentScope)
of nkIdent, nkAccQuoted:
result = if onlyCurrentScope:
localSearchInScope(c, considerQuotedIdent(c, n))
else:
searchInScopes(c, considerQuotedIdent(c, n))
of nkDotExpr:
result = nil
if onlyCurrentScope: return
checkSonsLen(n, 2, c.config)
var m = lookUpForDefined(c, n[0], onlyCurrentScope)
var m = lookUpForDeclared(c, n[0], onlyCurrentScope)
if m != nil and m.kind == skModule:
let ident = considerQuotedIdent(c, n[1], n)
if m == c.module:
result = strTableGet(c.topLevelScope.symbols, ident)
else:
result = strTableGet(m.tab, ident)
of nkAccQuoted:
result = lookUpForDefined(c, considerQuotedIdent(c, n), onlyCurrentScope)
of nkSym:
result = n.sym
of nkOpenSymChoice, nkClosedSymChoice:
Expand All @@ -1885,15 +1888,11 @@ proc lookUpForDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PSym =
localError(c.config, n.info, "identifier expected, but got: " & renderTree(n))
result = nil

proc semDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PNode =
proc semDeclared(c: PContext, n: PNode, onlyCurrentScope: bool): PNode =
checkSonsLen(n, 2, c.config)
# we replace this node by a 'true' or 'false' node:
result = newIntNode(nkIntLit, 0)
if not onlyCurrentScope and considerQuotedIdent(c, n[0], n).s == "defined":
let d = considerQuotedIdent(c, n[1], n)
result.intVal = ord isDefined(c.config, d.s)
elif lookUpForDefined(c, n[1], onlyCurrentScope) != nil:
result.intVal = 1
result.intVal = ord lookUpForDeclared(c, n[1], onlyCurrentScope) != nil
result.info = n.info
result.typ = getSysType(c.graph, n.info, tyBool)

Expand Down Expand Up @@ -2187,10 +2186,13 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
result = semTypeOf(c, n)
of mDefined:
markUsed(c, n.info, s)
result = semDefined(c, setMs(n, s), false)
of mDefinedInScope:
result = semDefined(c, setMs(n, s))
of mDeclared:
markUsed(c, n.info, s)
result = semDeclared(c, setMs(n, s), false)
of mDeclaredInScope:
markUsed(c, n.info, s)
result = semDefined(c, setMs(n, s), true)
result = semDeclared(c, setMs(n, s), true)
of mCompiles:
markUsed(c, n.info, s)
result = semCompiles(c, setMs(n, s), flags)
Expand Down
2 changes: 1 addition & 1 deletion compiler/semgnrc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ proc semGenericStmt(c: PContext, n: PNode,
var mixinContext = false
if s != nil:
incl(s.flags, sfUsed)
mixinContext = s.magic in {mDefined, mDefinedInScope, mCompiles, mAstToStr}
mixinContext = s.magic in {mDefined, mDeclared, mDeclaredInScope, mCompiles, mAstToStr}
let whichChoice = if s.id in ctx.toBind: scClosed
elif s.isMixedIn: scForceOpen
else: scOpen
Expand Down
2 changes: 1 addition & 1 deletion compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1834,7 +1834,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
result = semProcAnnotation(c, n, validPragmas)
if result != nil: return result
result = n
checkSonsLen(n, bodyPos + 1, c.config)
checkMinSonsLen(n, bodyPos + 1, c.config)
var s: PSym
var typeIsDetermined = false
var isAnon = false
Expand Down
26 changes: 12 additions & 14 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2324,9 +2324,11 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
formal: PSym # current routine parameter

template noMatch() =
c.mergeShadowScope #merge so that we don't have to resem for later overloads
m.state = csNoMatch
m.firstMismatch.arg = a
m.firstMismatch.formal = formal
return

template checkConstraint(n: untyped) {.dirty.} =
if not formal.constraint.isNil:
Expand All @@ -2335,19 +2337,16 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
inc(m.genericMatches, 100)
else:
noMatch()
return

if formal.typ.kind in {tyVar}:
let argConverter = if arg.kind == nkHiddenDeref: arg[0] else: arg
if argConverter.kind == nkHiddenCallConv:
if argConverter.typ.kind notin {tyVar}:
m.firstMismatch.kind = kVarNeeded
noMatch()
return
elif not n.isLValue:
m.firstMismatch.kind = kVarNeeded
noMatch()
return

m.state = csMatch # until proven otherwise
m.firstMismatch = MismatchInfo()
Expand All @@ -2359,6 +2358,9 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
formal = if formalLen > 1: m.callee.n[1].sym else: nil

while a < n.len:

c.openShadowScope

if a >= formalLen-1 and f < formalLen and m.callee.n[f].typ.isVarargsUntyped:
formal = m.callee.n[f].sym
incl(marker, formal.position)
Expand All @@ -2383,12 +2385,10 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
if n[a][0].kind != nkIdent:
localError(c.config, n[a].info, "named parameter has to be an identifier")
noMatch()
return
formal = getNamedParamFromList(m.callee.n, n[a][0].ident)
if formal == nil:
# no error message!
noMatch()
return
if containsOrIncl(marker, formal.position):
m.firstMismatch.kind = kAlreadyGiven
# already in namedParams, so no match
Expand All @@ -2397,7 +2397,6 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
# different parameter names could match later on):
when false: localError(n[a].info, errCannotBindXTwice, formal.name.s)
noMatch()
return
m.baseTypeMatch = false
m.typedescMatched = false
n[a][1] = prepareOperand(c, formal.typ, n[a][1])
Expand All @@ -2407,7 +2406,6 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
m.firstMismatch.kind = kTypeMismatch
if arg == nil:
noMatch()
return
checkConstraint(n[a][1])
if m.baseTypeMatch:
#assert(container == nil)
Expand Down Expand Up @@ -2448,24 +2446,20 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
checkConstraint(n[a])
else:
noMatch()
return
else:
m.firstMismatch.kind = kExtraArg
noMatch()
return
else:
if m.callee.n[f].kind != nkSym:
internalError(c.config, n[a].info, "matches")
noMatch()
return
formal = m.callee.n[f].sym
m.firstMismatch.kind = kTypeMismatch
if containsOrIncl(marker, formal.position) and container.isNil:
m.firstMismatch.kind = kPositionalAlreadyGiven
# positional param already in namedParams: (see above remark)
when false: localError(n[a].info, errCannotBindXTwice, formal.name.s)
noMatch()
return

if formal.typ.isVarargsUntyped:
if container.isNil:
Expand All @@ -2482,7 +2476,6 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
n[a], nOrig[a])
if arg == nil:
noMatch()
return
if m.baseTypeMatch:
assert formal.typ.kind == tyVarargs
#assert(container == nil)
Expand Down Expand Up @@ -2511,8 +2504,13 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
localError(c.config, n[a].info, "cannot convert $1 to $2" % [
typeToString(n[a].typ), typeToString(formal.typ) ])
noMatch()
return
checkConstraint(n[a])

if m.state == csMatch and not(m.calleeSym != nil and m.calleeSym.kind in {skTemplate, skMacro}):
c.mergeShadowScope
else:
c.closeShadowScope

inc(a)
# for some edge cases (see tdont_return_unowned_from_owned test case)
m.firstMismatch.arg = a
Expand Down Expand Up @@ -2544,7 +2542,7 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
if m.state == csNoMatch: return
# check that every formal parameter got a value:
for f in 1..<m.callee.n.len:
var formal = m.callee.n[f].sym
let formal = m.callee.n[f].sym
if not containsOrIncl(marker, formal.position):
if formal.ast == nil:
if formal.typ.kind == tyVarargs:
Expand Down
25 changes: 7 additions & 18 deletions doc/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5019,40 +5019,29 @@ delayed until template instantiation time:
:status: 1
template t(body: typed) =
proc p = echo "hey"
block:
body
t:
var i = 1
echo i
p() # fails with 'undeclared identifier: p'
t:
var i = 2 # fails with 'attempt to redeclare i'
echo i
The above code fails with the mysterious error message that ``i`` has already
been declared. The reason for this is that the ``var i = ...`` bodies need to
be type-checked before they are passed to the ``body`` parameter and type
checking in Nim implies symbol lookups. For the symbol lookups to succeed
``i`` needs to be added to the current (i.e. outer) scope. After type checking
these additions to the symbol table are not rolled back (for better or worse).
The above code fails with the error message that ``p`` is not declared.
The reason for this is that the ``p()`` body is type-checked before getting
passed to the ``body`` parameter and type checking in Nim implies symbol lookups.
The same code works with ``untyped`` as the passed body is not required to be
type-checked:

.. code-block:: nim
:test: "nim c $1"
template t(body: untyped) =
proc p = echo "hey"
block:
body
t:
var i = 1
echo i
t:
var i = 2 # compiles
echo i
p() # compiles
Varargs of untyped
Expand Down
Loading

0 comments on commit fb58066

Please sign in to comment.