Skip to content

Commit

Permalink
Merge pull request nim-lang#4701 from mbaulch/fix4675
Browse files Browse the repository at this point in the history
pickBestCandidate: pre-calculate candidates when symbol table modified
  • Loading branch information
Araq authored Sep 3, 2016
2 parents c2df609 + 9a2b98c commit ea8a11d
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 42 deletions.
114 changes: 72 additions & 42 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -34,55 +34,85 @@ proc sameMethodDispatcher(a, b: PSym): bool =

proc determineType(c: PContext, s: PSym)

proc initCandidateSymbols(c: PContext, headSymbol: PNode,
initialBinding: PNode,
filter: TSymKinds,
best, alt: var TCandidate,
o: var TOverloadIter): seq[tuple[s: PSym, scope: int]] =
result = @[]
var symx = initOverloadIter(o, c, headSymbol)
while symx != nil:
if symx.kind in filter:
result.add((symx, o.lastOverloadScope))
symx = nextOverloadIter(o, c, headSymbol)
if result.len > 0:
initCandidate(c, best, result[0].s, initialBinding, result[0].scope)
initCandidate(c, alt, result[0].s, initialBinding, result[0].scope)
best.state = csNoMatch

proc pickBestCandidate(c: PContext, headSymbol: PNode,
n, orig: PNode,
initialBinding: PNode,
filter: TSymKinds,
best, alt: var TCandidate,
errors: var CandidateErrors) =
while true:
block pickAttempt:
var o: TOverloadIter
var sym = initOverloadIter(o, c, headSymbol)
# Thanks to the lazy semchecking for operands, we need to check whether
# 'initCandidate' modifies the symbol table (via semExpr).
# This can occur in cases like 'init(a, 1, (var b = new(Type2); b))'
let counterInitial = c.currentScope.symbols.counter
var o: TOverloadIter
var sym = initOverloadIter(o, c, headSymbol)
var scope = o.lastOverloadScope
# Thanks to the lazy semchecking for operands, we need to check whether
# 'initCandidate' modifies the symbol table (via semExpr).
# This can occur in cases like 'init(a, 1, (var b = new(Type2); b))'
let counterInitial = c.currentScope.symbols.counter
var syms: seq[tuple[s: PSym, scope: int]]
var nextSymIndex = 0
while sym != nil:
if sym.kind in filter:
# Initialise 'best' and 'alt' with the first available symbol
while sym != nil:
if sym.kind in filter:
initCandidate(c, best, sym, initialBinding, o.lastOverloadScope)
initCandidate(c, alt, sym, initialBinding, o.lastOverloadScope)
best.state = csNoMatch
break
else:
sym = nextOverloadIter(o, c, headSymbol)
var z: TCandidate
while sym != nil:
if sym.kind notin filter:
sym = nextOverloadIter(o, c, headSymbol)
continue
determineType(c, sym)
initCandidate(c, z, sym, initialBinding, o.lastOverloadScope)
if c.currentScope.symbols.counter != counterInitial: break pickAttempt
matches(c, n, orig, z)
if errors != nil:
errors.safeAdd((sym, int z.mutabilityProblem))
if z.errors != nil:
for err in z.errors:
errors.add(err)
if z.state == csMatch:
# little hack so that iterators are preferred over everything else:
if sym.kind == skIterator: inc(z.exactMatches, 200)
case best.state
of csEmpty, csNoMatch: best = z
of csMatch:
var cmp = cmpCandidates(best, z)
if cmp < 0: best = z # x is better than the best so far
elif cmp == 0: alt = z # x is as good as the best so far
else: discard
sym = nextOverloadIter(o, c, headSymbol)
break # pick attempt was successful
initCandidate(c, best, sym, initialBinding, scope)
initCandidate(c, alt, sym, initialBinding, scope)
best.state = csNoMatch
break
else:
sym = nextOverloadIter(o, c, headSymbol)
scope = o.lastOverloadScope
var z: TCandidate
while sym != nil:
if sym.kind notin filter:
sym = nextOverloadIter(o, c, headSymbol)
scope = o.lastOverloadScope
continue
determineType(c, sym)
initCandidate(c, z, sym, initialBinding, scope)
if c.currentScope.symbols.counter == counterInitial or syms != nil:
matches(c, n, orig, z)
if errors != nil:
errors.safeAdd((sym, int z.mutabilityProblem))
if z.errors != nil:
for err in z.errors:
errors.add(err)
if z.state == csMatch:
# little hack so that iterators are preferred over everything else:
if sym.kind == skIterator: inc(z.exactMatches, 200)
case best.state
of csEmpty, csNoMatch: best = z
of csMatch:
var cmp = cmpCandidates(best, z)
if cmp < 0: best = z # x is better than the best so far
elif cmp == 0: alt = z # x is as good as the best so far
else:
# Symbol table has been modified. Restart and pre-calculate all syms
# before any further candidate init and compare. SLOW, but rare case.
syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o)
if syms == nil:
sym = nextOverloadIter(o, c, headSymbol)
scope = o.lastOverloadScope
elif nextSymIndex < syms.len:
# rare case: retrieve the next pre-calculated symbol
sym = syms[nextSymIndex].s
scope = syms[nextSymIndex].scope
nextSymIndex += 1
else:
break

proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
# Gives a detailed error message; this is separated from semOverloadedCall,
Expand Down
5 changes: 5 additions & 0 deletions tests/overload/importA.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type
Field* = object
elemSize*: int

template `+`*(x: untyped, y: Field): untyped = x
15 changes: 15 additions & 0 deletions tests/overload/importB.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
type
Foo*[T] = object
v*: T

template `+`*(x: Foo, y: Foo): untyped = x

template newvar*(r: untyped): untyped {.dirty.} =
var r: float

template t1*(x: Foo): untyped =
newvar(y1)
x
template t2*(x: Foo): untyped =
newvar(y2)
x
7 changes: 7 additions & 0 deletions tests/overload/timport.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# issue 4675
import importA # comment this out to make it work
import importB

var x: Foo[float]
var y: Foo[float]
let r = t1(x) + t2(y)

0 comments on commit ea8a11d

Please sign in to comment.