From f98578ea35fdd8b3887778700c07c903fefee512 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Sat, 18 Apr 2026 02:52:31 -0400 Subject: [PATCH] fix 25667; Generic forward type confusion (#25737) ref: #25667 drain deferred reification in a loop until there is no more work to do. Could potentially evaluate the same deferred work more than once. --------- Co-authored-by: Andreas Rumpf --- compiler/semdata.nim | 6 ++--- compiler/semstmts.nim | 35 +++++++++++++++++++++------- compiler/semtypes.nim | 8 +++---- tests/objects/t25627.nim | 17 ++++++++++++++ tests/types/tforwardcycletimeout.nim | 10 ++++++++ 5 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 tests/types/tforwardcycletimeout.nim diff --git a/compiler/semdata.nim b/compiler/semdata.nim index c561f6690e..15d8b14fe7 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -180,9 +180,9 @@ type sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index inUncheckedAssignSection*: int importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id]) - forwardTypeUpdates*: seq[(PType, PNode)] - # types that need to be updated in a type section - # due to containing forward types, and their corresponding nodes + forwardTypeUpdates*: seq[(PSym, PType, PNode)] + # top-level owner, type, and type node for delayed retries inside a + # type section due to containing forward types forwardFieldUpdates*: seq[(PType, PNode, PType)] # object/tuple field definitions whose default values mention forward # types and need delayed const checking diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index d31bb5a9af..465276ffc2 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1808,15 +1808,32 @@ proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) = internalAssert c.config, false proc typeSectionFinalPass(c: PContext, n: PNode) = - for (typ, typeNode) in c.forwardTypeUpdates: - # types that need to be updated due to containing forward types - # and their corresponding type nodes - # for example generic invocations of forward types end up here - var reified = semTypeNode(c, typeNode, nil) - assert reified != nil - assignType(typ, reified) - typ.itemId = reified.itemId # same id - c.forwardTypeUpdates = @[] + # each top level type needs to be processed, each epoch should reify at least one + var remainingOwners = initIntSet() + for (owner, _, _) in c.forwardTypeUpdates: + remainingOwners.incl owner.id + + while c.forwardTypeUpdates.len > 0: + let pending = move c.forwardTypeUpdates + var madeProgress = false + + for (owner, typ, typeNode) in pending: + # types that need to be updated due to containing forward types + # and their corresponding type nodes + # for example generic invocations of forward types end up here + var reified = semTypeNode(c, typeNode, nil) + assert reified != nil + assignType(typ, reified) + typ.itemId = reified.itemId # same id + if containsForwardType(typ): + c.forwardTypeUpdates.add (owner, typ, typeNode) + elif not remainingOwners.missingOrExcl(owner.id): + madeProgress = true + + if not madeProgress: + # can't error here unfortunately + break + for (owner, field, expectedType) in c.forwardFieldUpdates: semDelayedFieldDefault(c, owner, expectedType, field) c.forwardFieldUpdates = @[] diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 0b26cb8b8f..e904579283 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -223,7 +223,7 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType = if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base) if base.kind notin {tyGenericParam, tyGenericInvocation}: if base.kind == tyForward: - c.forwardTypeUpdates.add (base, n[1]) + c.forwardTypeUpdates.add (getCurrOwner(c), base, n[1]) elif not isOrdinalType(base, allowEnumWithHoles = true): localError(c.config, n.info, errOrdinalTypeExpected % typeToString(base, preferDesc)) elif lengthOrd(c.config, base) > MaxSetElements: @@ -1114,7 +1114,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType if needsForwardUpdate: # if the inherited object is a forward type, # the entire object needs to be checked again - c.forwardTypeUpdates.add (result, n) # we retry in the final pass + c.forwardTypeUpdates.add (getCurrOwner(c), result, n) # we retry in the final pass rawAddSon(result, realBase) if realBase == nil and tfInheritable in flags: result.incl tfInheritable @@ -1762,7 +1762,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = for i in 1..