IC: don't mutate loaded types in handleGenericInvocation

When a generic's body is computed by a macro (Bar[T, U] = makeBar(T, U)),
`newbody` after replaceTypeVarsT can be a type loaded from a dep module
- even a builtin like `int` - which is Sealed under IC:

- the in-place flag accumulation `newbody.flags = newbody.flags + ...`
  asserted (and under non-IC silently pollutes the shared type's flags,
  e.g. the global `int`); compute the flags into a local, skip the
  in-place store for Sealed types and feed `result.flags` from the
  local - value-identical for the instance.
- `newbody.typeInst = result` likewise; a loaded body keeps whatever
  its defining module serialized (the field was first-wins anyway).

Both changes are no-ops for non-IC (types are never Sealed there).
Fixes tmacrogenerics. Macro sweep 93/95 - the two remaining fails are
tmacro7 (disabled test, fails identically under non-IC) and
tmacrogetimpl (needs a design decision on getImplTransformed sym
sharing). tests/ic 5/5, koch boot -d:release and clean koch bootic
both reach bit-identical fixed points.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Araq
2026-06-10 13:41:10 +02:00
parent 5ce2c8f95d
commit cc2afc616f

View File

@@ -503,8 +503,14 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
let bbody = last body
var newbody = replaceTypeVarsT(cl, bbody, isInstValue = true)
cl.skipTypedesc = oldSkipTypedesc
newbody.flags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
result.flags = result.flags + newbody.flags - tfInstClearedFlags
let newbodyFlags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
if newbody.state != Sealed:
newbody.flags = newbodyFlags
# else: `newbody` is a type loaded from a dep module (it can even be a
# builtin like `int` when the generic's body is computed by a macro) and is
# immutable under IC. Skip the in-place flag accumulation on the shared
# type; the instance `result` still receives the flags below.
result.flags = result.flags + newbodyFlags - tfInstClearedFlags
setToPreviousLayer(cl.typeMap)
@@ -524,8 +530,11 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
# generics *when the type is constructed*:
cl.c.graph.setAttachedOp(cl.c.module.position, newbody, attachedDeepCopy,
cl.c.instTypeBoundOp(cl.c, dc, result, cl.info, attachedDeepCopy, 1))
if newbody.typeInst == nil:
if newbody.typeInst == nil and newbody.state != Sealed:
# doAssert newbody.typeInst == nil
# An IC-loaded (Sealed) `newbody` keeps whatever `typeInst` its defining
# module serialized; recording this process's first instantiation on the
# shared type is not possible (and was always first-wins anyway).
newbody.typeInst = result
if tfRefsAnonObj in newbody.flags and newbody.kind != tyGenericInst:
# can come here for tyGenericInst too, see tests/metatype/ttypeor.nim