mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-10 05:38:10 +00:00
Encountered in realistic scenario. Didn't really look at this one. AI one shot it lol When a module defines method-bearing types and a when isMainModule block imports additional modules that also define methods on the same type hierarchy, sortVTableDispatchers crashes with: Error: unhandled exception: key not found: (module: N, item: M) [KeyError] Root cause: the itemTable built during vtable sorting is populated from g.objectTree[baseType], which only contains types from the current compilation pass. When when isMainModule triggers re-import of method-bearing modules, the method bucket contains types from both passes. Types from the first pass have ItemIds not present in the second pass's object tree, so itemTable[obj.itemId] raises KeyError at line 155. Fix: if obj.itemId is missing from itemTable, create an empty slot array of the correct length. The entry is a local temporary — the second loop in sortVTableDispatchers only calls setMethodsPerType for types in the current object tree, so types from the prior pass retain their already-established dispatch. The entry exists solely to prevent the KeyError during the assignment loop. The methodIndexLen used for the new entry is the bucket's slot count, which is correct for any type in the hierarchy. Added test tests/method/tvtable_reentry.nim that defines methods across three types in two compilation passes and verifies dispatch correctness for all three.
169 lines
6.6 KiB
Nim
169 lines
6.6 KiB
Nim
import ast, modulegraphs, magicsys, lineinfos, options, cgmeth, types
|
|
import std/[algorithm, tables, intsets, assertions]
|
|
|
|
|
|
|
|
proc genVTableDispatcher(g: ModuleGraph; methods: seq[PSym]; index: int): PSym =
|
|
#[
|
|
proc dispatch(x: Base, params: ...) =
|
|
cast[proc bar(x: Base, params: ...)](x.vTable[index])(x, params)
|
|
]#
|
|
var base = methods[0].ast[dispatcherPos].sym
|
|
result = base
|
|
var paramLen = base.typ.signatureLen
|
|
var body = newNodeI(nkStmtList, base.info)
|
|
|
|
var disp = newNodeI(nkIfStmt, base.info)
|
|
|
|
let nimGetVTableSym = getCompilerProc(g, "nimGetVTable")
|
|
let ptrPNimType = nimGetVTableSym.typ.n[1].sym.typ
|
|
|
|
var nTyp = base.typ.n[1].sym.typ
|
|
var dispatchObject = newSymNode(base.typ.n[1].sym)
|
|
if nTyp.kind == tyObject:
|
|
dispatchObject = newTree(nkAddr, dispatchObject)
|
|
else:
|
|
if g.config.backend != backendCpp: # TODO: maybe handle ptr?
|
|
if nTyp.kind == tyVar and nTyp.skipTypes({tyVar}).kind != tyObject:
|
|
dispatchObject = newTree(nkDerefExpr, dispatchObject)
|
|
|
|
var getVTableCall = newTree(nkCall,
|
|
newSymNode(nimGetVTableSym),
|
|
dispatchObject,
|
|
newIntNode(nkIntLit, index)
|
|
)
|
|
getVTableCall.typ = getSysType(g, unknownLineInfo, tyPointer)
|
|
var vTableCall = newNodeIT(nkCall, base.info, base.typ.returnType)
|
|
var castNode = newTree(nkCast,
|
|
newNodeIT(nkType, base.info, base.typ),
|
|
getVTableCall)
|
|
|
|
castNode.typ = base.typ
|
|
vTableCall.add castNode
|
|
for col in 1..<paramLen:
|
|
let param = base.typ.n[col].sym
|
|
vTableCall.add newSymNode(param)
|
|
|
|
var ret: PNode
|
|
if base.typ.returnType != nil:
|
|
var a = newNodeI(nkFastAsgn, base.info)
|
|
a.add newSymNode(base.ast[resultPos].sym)
|
|
a.add vTableCall
|
|
ret = newNodeI(nkReturnStmt, base.info)
|
|
ret.add a
|
|
else:
|
|
ret = vTableCall
|
|
|
|
if base.typ.n[1].sym.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
|
|
let ifBranch = newNodeI(nkElifBranch, base.info)
|
|
let boolType = getSysType(g, unknownLineInfo, tyBool)
|
|
var isNil = getSysMagic(g, unknownLineInfo, "isNil", mIsNil)
|
|
let checkSelf = newNodeIT(nkCall, base.info, boolType)
|
|
checkSelf.add newSymNode(isNil)
|
|
checkSelf.add newSymNode(base.typ.n[1].sym)
|
|
ifBranch.add checkSelf
|
|
ifBranch.add newTree(nkCall,
|
|
newSymNode(getCompilerProc(g, "chckNilDisp")), newSymNode(base.typ.n[1].sym))
|
|
let elseBranch = newTree(nkElifBranch, ret)
|
|
disp.add ifBranch
|
|
disp.add elseBranch
|
|
else:
|
|
disp = ret
|
|
|
|
body.add disp
|
|
body.flags.incl nfTransf # should not be further transformed
|
|
result.ast[bodyPos] = body
|
|
|
|
proc containGenerics(base: PType, s: seq[tuple[depth: int, value: PType]]): bool =
|
|
result = tfHasMeta in base.flags
|
|
for i in s:
|
|
if tfHasMeta in i.value.flags:
|
|
result = true
|
|
break
|
|
|
|
proc collectVTableDispatchers*(g: ModuleGraph) =
|
|
var itemTable = initTable[ItemId, seq[PSym]]()
|
|
var rootTypeSeq = newSeq[PType]()
|
|
var rootItemIdCount = initCountTable[ItemId]()
|
|
for bucket in 0..<g.methods.len:
|
|
var relevantCols = initIntSet()
|
|
if relevantCol(g.methods[bucket].methods, 1): incl(relevantCols, 1)
|
|
sortBucket(g.methods[bucket].methods, relevantCols)
|
|
let base = g.methods[bucket].methods[^1]
|
|
let baseType = base.typ.firstParamType.skipTypes(skipPtrs-{tyTypeDesc})
|
|
if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):
|
|
let methodIndexLen = g.bucketTable[baseType.itemId]
|
|
if baseType.itemId notin itemTable: # once is enough
|
|
rootTypeSeq.add baseType
|
|
itemTable[baseType.itemId] = newSeq[PSym](methodIndexLen)
|
|
|
|
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
|
|
if x.depth >= y.depth: 1
|
|
else: -1
|
|
)
|
|
|
|
for item in g.objectTree[baseType.itemId]:
|
|
if item.value.itemId notin itemTable:
|
|
itemTable[item.value.itemId] = newSeq[PSym](methodIndexLen)
|
|
|
|
var mIndex = 0 # here is the correpsonding index
|
|
if baseType.itemId notin rootItemIdCount:
|
|
rootItemIdCount[baseType.itemId] = 1
|
|
else:
|
|
mIndex = rootItemIdCount[baseType.itemId]
|
|
rootItemIdCount.inc(baseType.itemId)
|
|
for idx in 0..<g.methods[bucket].methods.len:
|
|
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
|
|
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
|
|
g.addDispatchers genVTableDispatcher(g, g.methods[bucket].methods, mIndex)
|
|
else: # if the base object doesn't have this method
|
|
g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, g.idgen)
|
|
|
|
proc sortVTableDispatchers*(g: ModuleGraph) =
|
|
var itemTable = initTable[ItemId, seq[PSym]]()
|
|
var rootTypeSeq = newSeq[ItemId]()
|
|
var rootItemIdCount = initCountTable[ItemId]()
|
|
for bucket in 0..<g.methods.len:
|
|
var relevantCols = initIntSet()
|
|
if relevantCol(g.methods[bucket].methods, 1): incl(relevantCols, 1)
|
|
sortBucket(g.methods[bucket].methods, relevantCols)
|
|
let base = g.methods[bucket].methods[^1]
|
|
let baseType = base.typ.firstParamType.skipTypes(skipPtrs-{tyTypeDesc})
|
|
if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):
|
|
let methodIndexLen = g.bucketTable[baseType.itemId]
|
|
if baseType.itemId notin itemTable: # once is enough
|
|
rootTypeSeq.add baseType.itemId
|
|
itemTable[baseType.itemId] = newSeq[PSym](methodIndexLen)
|
|
|
|
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
|
|
if x.depth >= y.depth: 1
|
|
else: -1
|
|
)
|
|
|
|
for item in g.objectTree[baseType.itemId]:
|
|
if item.value.itemId notin itemTable:
|
|
itemTable[item.value.itemId] = newSeq[PSym](methodIndexLen)
|
|
|
|
var mIndex = 0 # here is the correpsonding index
|
|
if baseType.itemId notin rootItemIdCount:
|
|
rootItemIdCount[baseType.itemId] = 1
|
|
else:
|
|
mIndex = rootItemIdCount[baseType.itemId]
|
|
rootItemIdCount.inc(baseType.itemId)
|
|
for idx in 0..<g.methods[bucket].methods.len:
|
|
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
|
|
if obj.itemId notin itemTable:
|
|
itemTable[obj.itemId] = newSeq[PSym](methodIndexLen)
|
|
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
|
|
|
|
for baseType in rootTypeSeq:
|
|
g.setMethodsPerType(baseType, itemTable[baseType])
|
|
for item in g.objectTree[baseType]:
|
|
let typ = item.value.skipTypes(skipPtrs)
|
|
let idx = typ.itemId
|
|
for mIndex in 0..<itemTable[idx].len:
|
|
if itemTable[idx][mIndex] == nil:
|
|
let parentIndex = typ.baseClass.skipTypes(skipPtrs).itemId
|
|
itemTable[idx][mIndex] = itemTable[parentIndex][mIndex]
|
|
g.setMethodsPerType(idx, itemTable[idx])
|