Fix #21272: Rewrite parts of pickBestCandidate (#21465)

* Make pickBestCandidate store syms

* Remove useless cursor

* Try making pickBestCandidate more readable

* Fix advance order

* Revert back to seq with lots of comments

---------

Co-authored-by: SirOlaf <>
This commit is contained in:
SirOlaf 2023-03-05 11:56:51 +01:00 • committed by GitHub
commit 7bde421e4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -43,6 +43,7 @@ proc initCandidateSymbols(c: PContext, headSymbol: PNode,
best, alt: var TCandidate, best, alt: var TCandidate,
o: var TOverloadIter, o: var TOverloadIter,
diagnostics: bool): seq[tuple[s: PSym, scope: int]] = diagnostics: bool): seq[tuple[s: PSym, scope: int]] =
## puts all overloads into a seq and prepares best+alt
result = @[] result = @[]
var symx = initOverloadIter(o, c, headSymbol) var symx = initOverloadIter(o, c, headSymbol)
while symx != nil: while symx != nil:
@ -64,36 +65,35 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
errors: var CandidateErrors, errors: var CandidateErrors,
diagnosticsFlag: bool, diagnosticsFlag: bool,
errorsEnabled: bool, flags: TExprFlags) = errorsEnabled: bool, flags: TExprFlags) =
# `matches` may find new symbols, so keep track of count
var symCount = c.currentScope.symbols.counter
var o: TOverloadIter var o: TOverloadIter
var sym = initOverloadIter(o, c, headSymbol) # https://github.com/nim-lang/Nim/issues/21272
var scope = o.lastOverloadScope # prevent mutation during iteration by storing them in a seq
# Thanks to the lazy semchecking for operands, we need to check whether # luckily `initCandidateSymbols` does just that
# 'initCandidate' modifies the symbol table (via semExpr). var syms = initCandidateSymbols(c, headSymbol, initialBinding, filter,
# This can occur in cases like 'init(a, 1, (var b = new(Type2); b))' best, alt, o, diagnosticsFlag)
let counterInitial = c.currentScope.symbols.counter if len(syms) == 0:
var syms: seq[tuple[s: PSym, scope: int]] return
var noSyms = true # current overload being considered
var nextSymIndex = 0 var sym = syms[0].s
while sym != nil: var scope = syms[0].scope
if sym.kind in filter:
# Initialise 'best' and 'alt' with the first available symbol # starts at 1 because 0 is already done with setup, only needs checking
initCandidate(c, best, sym, initialBinding, scope, diagnosticsFlag) var nextSymIndex = 1
initCandidate(c, alt, sym, initialBinding, scope, diagnosticsFlag) var z: TCandidate # current candidate
best.state = csNoMatch while true:
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) determineType(c, sym)
initCandidate(c, z, sym, initialBinding, scope, diagnosticsFlag) initCandidate(c, z, sym, initialBinding, scope, diagnosticsFlag)
if c.currentScope.symbols.counter == counterInitial or syms.len != 0:
# this is kinda backwards as without a check here the described
# problems in recalc would not happen, but instead it 100%
# does check forever in some cases
if c.currentScope.symbols.counter == symCount:
# may introduce new symbols with caveats described in recalc branch
matches(c, n, orig, z) matches(c, n, orig, z)
if z.state == csMatch: if z.state == csMatch:
# little hack so that iterators are preferred over everything else: # little hack so that iterators are preferred over everything else:
if sym.kind == skIterator: if sym.kind == skIterator:
@ -113,22 +113,36 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
firstMismatch: z.firstMismatch, firstMismatch: z.firstMismatch,
diagnostics: z.diagnostics)) diagnostics: z.diagnostics))
else: else:
# this branch feels like a ticking timebomb
# one of two bad things could happen
# 1) new symbols are discovered but the loop ends before we recalc
# 2) new symbols are discovered and resemmed forever
# not 100% sure if these are possible though as they would rely
# on somehow introducing a new overload during overload resolution
# Symbol table has been modified. Restart and pre-calculate all syms # Symbol table has been modified. Restart and pre-calculate all syms
# before any further candidate init and compare. SLOW, but rare case. # before any further candidate init and compare. SLOW, but rare case.
syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, syms = initCandidateSymbols(c, headSymbol, initialBinding, filter,
best, alt, o, diagnosticsFlag) best, alt, o, diagnosticsFlag)
noSyms = false
if noSyms: # reset counter because syms may be in a new order
sym = nextOverloadIter(o, c, headSymbol) symCount = c.currentScope.symbols.counter
scope = o.lastOverloadScope nextSymIndex = 0
elif nextSymIndex < syms.len:
# rare case: retrieve the next pre-calculated symbol # just in case, should be impossible though
sym = syms[nextSymIndex].s if syms.len == 0:
scope = syms[nextSymIndex].scope break
nextSymIndex += 1
else: if nextSymIndex > high(syms):
# we have reached the end
break break
# advance to next sym
sym = syms[nextSymIndex].s
scope = syms[nextSymIndex].scope
inc(nextSymIndex)
proc effectProblem(f, a: PType; result: var string; c: PContext) = proc effectProblem(f, a: PType; result: var string; c: PContext) =
if f.kind == tyProc and a.kind == tyProc: if f.kind == tyProc and a.kind == tyProc:
if tfThread in f.flags and tfThread notin a.flags: if tfThread in f.flags and tfThread notin a.flags: