`propagateToOwner` aborted with
ast.nim(1314, 9) `mask <= o2.flags` IC bug: sealed type missing
propagated flags
whenever a consumer module propagated `tfHasAsgn`/`tfHasOwned` into a
type another module had already sealed. The assert encoded the hope that
a producer's derivation is complete at seal time. It is not: these flags
are DERIVED -- a fixpoint over kind, MM config, elements and the
attached-op table -- so a consumer can legitimately discover one later.
The trigger in the wild is a generic alias, `Channel[TMsg] {.gcsafe.} =
RawChannel`, whose object only acquires `tfHasAsgn` at the first
`Channel[T]` instantiation, in another module and (under IC) another
process. Any program that opens a channel crashed the compiler under
`nim ic` as soon as a `=copy` hook was declared for the alias
(nim-lang/Nim#26182).
Partition the flags instead. `derivedTypeFlags` names the bookkeeping
bits -- tfHasAsgn, tfHasOwned, tfHasGCedMem, tfCheckedForDestructor --
and `ast.inclDerived` / `ast.exclDerived` write exactly those without
the `Sealed` assert. The exemption is sound: none of them is in
`eqTypeFlags`, the `typekeys` content key or the NIF name, so they
cannot rename a type, move it in the cache or change `sameType`; and
`ast2nif.writeType` emits a `SymUse` for any type the current module
does not own, so a consumer's write can never reach a NIF. They stay
serialized, so a reload starts from the producer's derivation and a
consumer only ever adds to it.
`tfGenericHasDestructor` is deliberately NOT in the set: it is an alias
for `tfExplicitCallConv`, and exempting it would exempt a real proc-type
property.
This also gives the four existing `flagsImpl` writes a name. They wrote
the raw field precisely to dodge this assert -- liftdestructors' one
carried `# ^ XXX Breaks IC!` -- and are now `inclDerived`/`exclDerived`
calls, so the exemption is typed, asserted against `derivedTypeFlags`,
and greppable. `-d:icDerivedBarrier` reports every derived-flag write
onto a sealed type; a full `nim ic` build of a channel program produces
25, of which 24 are the `tfCheckedForDestructor` writes that were
already happening silently.