refactorings: progress

This commit is contained in:
Araq
2026-07-08 20:28:49 +02:00
parent bd0de5f9aa
commit cd7feabc97
12 changed files with 361 additions and 122 deletions

View File

@@ -626,7 +626,14 @@ proc `[]`*(n: PType, i: int): PType {.inline.} =
else:
n.sonsImpl[i]
proc `[]=`*(n: PType, i: int; x: PType) {.inline.} =
proc replaceSon*(n: PType, i: int; x: PType) {.inline.} =
## The single low-level "replace son `i` in place" primitive. All in-place son
## mutation funnels through here -- call sites go via `TypeBuilder.setSon`
## (`typebuilders.nim`, the reopen/mutable-staging seam); this is its backing.
## The `PType.[]=` operators used to do this inline; they were removed so that
## son replacement is named and greppable, and the PType->NifCursor swap (where
## this becomes a token rewrite over a thawed cursor) touches one proc, not
## every caller.
if n.state == Partial: loadType(n)
if n.kind == tyProc and i > 0:
assert n.nImpl[i] != nil and n.nImpl[i].sym != nil
@@ -638,9 +645,9 @@ proc `[]`*(n: PType, i: BackwardsIndex): PType {.inline.} =
if n.state == Partial: loadType(n)
n[n.sonsImpl.len - i.int]
proc `[]=`*(n: PType, i: BackwardsIndex; x: PType) {.inline.} =
proc replaceSon*(n: PType, i: BackwardsIndex; x: PType) {.inline.} =
if n.state == Partial: loadType(n)
n[n.sonsImpl.len - i.int] = x
replaceSon(n, n.sonsImpl.len - i.int, x)
proc getDeclPragma*(n: PNode): PNode =
## return the `nkPragma` node for declaration `n`, or `nil` if no pragma was found.
@@ -1165,7 +1172,7 @@ proc assignType*(dest, src: PType) =
dest.sonsImpl[0] = src.sonsImpl[0]
else:
newSons(dest, src.len)
for i in 0..<src.len: dest[i] = src[i]
for i in 0..<src.len: replaceSon(dest, i, src[i])
proc copyType*(t: PType, idgen: IdGenerator, owner: PSym): PType =
result = newType(t.kind, idgen, owner)

View File

@@ -164,13 +164,6 @@ template fieldCheck {.dirty.} =
echo "missed field ", field.name.s
writeStackTrace()
proc rawAddField*(obj: PType; field: PSym) =
assert field.kind == skField
field.position = obj.n.len
obj.n.add newSymNode(field)
propagateToOwner(obj, field.typ)
fieldCheck()
proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
# returns a[].field as a node
assert field.kind == skField
@@ -239,40 +232,70 @@ proc lookupCapturedField(n: PNode, s: PSym): PSym =
result = n.sym
else: discard
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
# Idempotent w.r.t. the captured symbol (mirrors `addUniqueField`): re-lifting
# a LOADED routine re-derives its transformed body (never serialized under IC)
# and re-captures the same locals, but the env object loaded from the NIF
# already carries their fields. Re-adding would duplicate the field and, worse,
# mutate a Sealed loaded type via `propagateToOwner` (the `t.state != Sealed`
# crash). Return the existing field instead.
let existing = lookupInRecord(obj.n, s.itemId)
if existing != nil:
return existing
# Re-lifting a LOADED routine during a VM transform (its transformed body is
# re-derived per process, never serialized) re-captures the same locals, but
# for a macro-generated gensym (e.g. libp2p `p2pProtocolBackendImpl`'s
# `msgVar`) its process-local id diverges from the one baked into the loaded
# env field, so the id match above misses. Reuse the existing same-named field
# rather than appending a divergent duplicate, which keeps the re-derived
# closure consistent (else a stale `:env` access reaches `cannotEval`).
# Confined to a loaded (Sealed) env: in a freshly built env ids are consistent,
# and two distinct same-named captures legitimately get distinct fields there.
if obj.state == Sealed:
let byName = lookupCapturedField(obj.n, s)
if byName != nil:
return byName
# Genuinely new field. Under IC the env may be a loaded Sealed type whose
# transform-time mutation is process-local (the body is discarded after the
# macro runs), so downgrade it to mutable instead of crashing on
# `t.state != Sealed` (mirrors `markAsClosure`).
type
ObjectBuilder* = object
## Extends an existing object type with record fields -- the deferred
## object-BODY counterpart to `typebuilders.TypeBuilder`. The object's
## identity is fixed (a shell from `createObj` or a type loaded from NIF);
## only its `nkRecList` body grows, possibly after thawing a loaded Sealed
## type. Lives here rather than in `typebuilders.nim` because it needs the
## record-walk reuse lookups above; hoist it once those move.
## See `doc/ic_type_body_builder.md` for the NIF-cursor migration story.
obj {.cursor.}: PType
cache {.cursor.}: IdentCache
idgen {.cursor.}: IdGenerator
proc reopenObject*(obj: PType; cache: IdentCache; idgen: IdGenerator): ObjectBuilder {.inline.} =
## Positions a builder to append fields to `obj`, keeping its identity. Does
## not thaw yet: the idempotency lookups must observe the pre-thaw `Sealed`
## state first (see `findField`).
ObjectBuilder(obj: obj, cache: cache, idgen: idgen)
proc findField*(b: ObjectBuilder; s: PSym; byName: bool): PSym =
## The idempotency lookup, load-bearing for correctness (not a fast path):
## re-lifting a LOADED routine re-derives its transformed body per process and
## re-captures the same locals, but the loaded env already carries their
## fields -- re-adding would duplicate and mutate Sealed memory.
##
## By derived item id first. Then, for a loaded (`Sealed`) body and when
## `byName`, by the stable name+position key: a macro-generated gensym (e.g.
## libp2p `p2pProtocolBackendImpl`'s `msgVar`) has a process-local id that
## diverges from the one baked into the loaded env field, so the id match
## misses; the same-named field is reused instead of appending a divergent
## duplicate (else a stale `:env` access reaches `cannotEval`). A freshly
## built env keeps consistent ids, so two same-named captures there
## legitimately get distinct fields -- hence the `Sealed`-only gate.
result = lookupInRecord(b.obj.n, s.itemId)
if result != nil: return
if byName and b.obj.state == Sealed:
result = lookupCapturedField(b.obj.n, s)
proc appendField*(b: var ObjectBuilder; field: PSym) =
## Low-level append of a prebuilt `skField` (replaces `rawAddField`): set its
## position, append it, fold its type into the object.
assert field.kind == skField
let obj = b.obj
field.position = obj.n.len
obj.n.add newSymNode(field)
propagateToOwner(obj, field.typ)
fieldCheck()
proc captureField*(b: var ObjectBuilder; s: PSym): PSym {.discardable.} =
## Idempotent capture of local `s` (= `addField`). On a `findField` miss,
## thaws the env if needed then mints the field. Under IC the env may be a
## loaded Sealed type whose transform-time mutation is process-local (the body
## is discarded after the macro runs), so `unsealForTransform` downgrades it to
## mutable instead of crashing on `t.state != Sealed` (mirrors `markAsClosure`).
result = b.findField(s, byName = true)
if result != nil: return
let obj = b.obj
unsealForTransform(obj)
# because of 'gensym' support, we have to mangle the name with its ID.
# This is hacky but the clean solution is much more complex than it looks.
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len),
idgen, s.owner, s.info, s.options)
var field = newSym(skField, getIdent(b.cache, s.name.s & $obj.n.len),
b.idgen, s.owner, s.info, s.options)
field.itemId = derivedFieldId(s.itemId)
let t = skipIntLit(s.typ, idgen)
let t = skipIntLit(s.typ, b.idgen)
field.typ = t
if s.kind in {skLet, skVar, skField, skForVar}:
#field.bitsize = s.bitsize
@@ -286,19 +309,44 @@ proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym
fieldCheck()
result = field
proc captureUniqueField*(b: var ObjectBuilder; s: PSym): PSym {.discardable.} =
## `addUniqueField`: idempotent by item id ONLY (no name fallback, no thaw,
## no alignment/flag copy).
result = b.findField(s, byName = false)
if result != nil: return
let obj = b.obj
var field = newSym(skField, getIdent(b.cache, s.name.s & $obj.n.len),
b.idgen, s.owner, s.info, s.options)
field.itemId = derivedFieldId(s.itemId)
let t = skipIntLit(s.typ, b.idgen)
field.typ = t
assert t.kind != tyTyped
propagateToOwner(obj, t)
field.position = obj.n.len
obj.n.add newSymNode(field)
result = field
proc finishObject*(b: sink ObjectBuilder) {.inline.} =
## Publish the completed body. A no-op today (the thawed env stays `Complete`,
## process-local, never re-serialized); the seam where the NIF backend will
## `beginRead` the record buffer into a read-only cursor and republish it
## under the object's SymId.
discard
proc rawAddField*(obj: PType; field: PSym) =
var b = reopenObject(obj, nil, nil) # prebuilt field: cache/idgen unused
b.appendField(field)
finishObject b
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
var b = reopenObject(obj, cache, idgen)
result = b.captureField(s)
finishObject b
proc addUniqueField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym {.discardable.} =
result = lookupInRecord(obj.n, s.itemId)
if result == nil:
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), idgen,
s.owner, s.info, s.options)
field.itemId = derivedFieldId(s.itemId)
let t = skipIntLit(s.typ, idgen)
field.typ = t
assert t.kind != tyTyped
propagateToOwner(obj, t)
field.position = obj.n.len
obj.n.add newSymNode(field)
result = field
var b = reopenObject(obj, cache, idgen)
result = b.captureUniqueField(s)
finishObject b
proc newDotExpr*(obj, b: PSym): PNode =
result = newNodeI(nkDotExpr, obj.info)

View File

@@ -183,7 +183,8 @@ proc commonType*(c: PContext; x, y: PType): PType =
nt = copyType(a, c.idgen, a.owner)
copyTypeProps(c.graph, c.idgen.module, nt, a)
nt[i] = if aEmpty: bb else: aa
var ntb = reopen(nt)
ntb.setSon(i, if aEmpty: bb else: aa)
if not nt.isNil: result = nt
#elif b[idx].kind == tyEmpty: return x
elif a.kind == tyRange and b.kind == tyRange:

View File

@@ -2250,7 +2250,8 @@ proc semYield(c: PContext, n: PNode): PNode =
if resultTypeIsInferrable(restype):
let inferred = n[0].typ
iterType[0] = inferred
var b = reopen(iterType)
b.setSon(0, inferred)
if c.p.resultSym != nil:
c.p.resultSym.typ = inferred
else:

View File

@@ -24,7 +24,7 @@ when defined(nimPreviewSlimSystem):
proc errorType*(g: ModuleGraph): PType =
## creates a type representing an error state
result = newType(tyError, g.idgen, g.owners[^1])
result.flagsImpl.incl tfCheckedForDestructor
result.incl tfCheckedForDestructor
proc getIntLitTypeG(g: ModuleGraph; literal: PNode; idgen: IdGenerator): PType =
# we cache some common integer literal types for performance:

View File

@@ -436,7 +436,8 @@ proc semUnown(c: PContext; n: PNode): PNode =
result = copyType(t, c.idgen, t.owner)
copyTypeProps(c.graph, c.idgen.module, result, t)
result[^1] = b
var rb = reopen(result)
rb.setSon(^1, b)
result.excl tfHasOwned
else:
result = t

View File

@@ -1152,10 +1152,12 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
case iter[i].kind
of tyVar:
mutable = true
iter[i] = iter[i].skipTypes({tyVar})
var b = reopen(iter)
b.setSon(i, iter[i].skipTypes({tyVar}))
of tyLent:
isLent = true
iter[i] = iter[i].skipTypes({tyLent})
var b = reopen(iter)
b.setSon(i, iter[i].skipTypes({tyLent}))
else: discard
if n[i].len-1 != iter[i].len:
@@ -1680,7 +1682,8 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
# object might have been assumed to be final
if tfInheritable in oldFlags and tfFinal in body.flags:
excl(body, tfFinal)
s.typ[^1] = body
var b = reopen(s.typ)
b.setSon(^1, body)
if tfCovariant in s.typ.flags:
checkCovariantParamsUsages(c, s.typ)
# XXX: This is a temporary limitation:

View File

@@ -56,6 +56,17 @@ proc openType(c: PContext; kind: TTypeKind; prev: PType): TypeBuilder =
else:
result = openType(c, kind)
proc openPair(c: PContext; kind: TTypeKind; prev: PType): TypePairBuilder =
## Prev-aware deferred (`TypePair`) open -- the deferred analogue of the
## prev-aware `openType` above, for types whose identity is published before
## their body is finished. Keeps a forward/partial `prev`'s reserved name,
## else mints a fresh identity at the same sequence point as `newTypeS`.
if reusePrev(prev):
if prev.kind == tyForward: prev.kind = kind
result = reopenPair(prev, c.idgen)
else:
result = openPair(kind, c.idgen, getCurrOwner(c))
proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext, son: sink PType): PType =
if reusePrev(prev):
result = prev
@@ -420,11 +431,17 @@ proc semDistinct(c: PContext, n: PNode, prev: PType): PType =
proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
assert isRange(n)
checkSonsLen(n, 3, c.config)
result = newOrPrevType(tyRange, prev, c)
result.n = newNodeI(nkRange, n.info)
# Deferred build: a *valid* tyRange must exist before the throwing
# `semExprWithType` below (bug #6895), so its base type is minted as an
# `errorType` placeholder son up front and back-patched via `setSon(0, …)`
# once the real bounds are known. The `.n` (nkRange bound exprs) and flags
# stay direct pokes on the live shell, as in `semProcTypeNode`.
var rb = openPair(c, tyRange, prev)
rb.setN newNodeI(nkRange, n.info)
# always create a 'valid' range type, but overwrite it later
# because 'semExprWithType' can raise an exception. See bug #6895.
addSonSkipIntLit(result, errorType(c), c.idgen)
rb.add errorType(c)
result = rb.pair.decl
if (n[1].kind == nkEmpty) or (n[2].kind == nkEmpty):
localError(c.config, n.info, "range is empty")
@@ -465,7 +482,9 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
if weakLeValue(result.n[0], result.n[1]) == impNo:
localError(c.config, n.info, "range is empty")
result[0] = rangeT[0]
# overwrite the placeholder son minted above with the real base type, then seal
rb.setSon(0, rangeT[0])
result = finishPair(rb).decl
proc semRange(c: PContext, n: PNode, prev: PType): PType =
result = nil
@@ -627,8 +646,12 @@ proc firstRange(config: ConfigRef, t: PType): PNode =
proc semTuple(c: PContext, n: PNode, prev: PType): PType =
var typ: PType
result = newOrPrevType(tyTuple, prev, c)
result.n = newNodeI(nkRecList, n.info)
# Deferred build: the tuple's identity is handed to `semFieldDefault` (which
# propagates each default field's type into the owner) while its fields/sons
# are still being appended -- so it goes through `TypePairBuilder`, publishing
# `rb.pair` mid-build rather than `openType ... finish`.
var rb = openPair(c, tyTuple, prev)
rb.setN newNodeI(nkRecList, n.info)
var check = initIntSet()
var counter = 0
for i in ord(n.kind == nkBracketExpr)..<n.len:
@@ -638,7 +661,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
var hasDefaultField = a[^1].kind != nkEmpty
if hasDefaultField:
typ = if a[^2].kind != nkEmpty: semTypeNode(c, a[^2], nil) else: nil
typ = semFieldDefault(c, result, typ, a)
typ = semFieldDefault(c, rb.pair.decl, typ, a)
elif a[^2].kind != nkEmpty:
typ = semTypeNode(c, a[^2], nil)
if c.graph.config.isDefined("nimPreviewRangeDefault") and typ.skipTypes(abstractInst).kind == tyRange:
@@ -659,11 +682,12 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
if hasDefaultField:
fSym.sym.ast = a[^1]
fSym.sym.ast.flags.incl nfSkipFieldChecking
result.n.add fSym
addSonSkipIntLit(result, typ, c.idgen)
rb.addRecField fSym
rb.add typ
styleCheckDef(c, a[j].info, field)
onDef(field.info, field)
if result.n.len == 0: result.n = nil
if rb.pair.decl.n.len == 0: rb.setN nil
result = finishPair(rb).decl
if isRecursiveStructuralType(result):
localError(c.config, n.info, errIllegalRecursionInTypeX % typeToString(result))
@@ -1143,17 +1167,24 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
base = nil
realBase = nil
if n.kind != nkObjectTy: internalError(c.config, n.info, "semObjectNode")
result = newOrPrevType(tyObject, prev, c)
# Deferred build: the object's identity is published to `forwardTypeUpdates`
# (a retry pass), to `semRecordNodeAux` (field sem may reference the object
# itself), and to the pragma dummy sym -- all before its body is complete. The
# son-tree (base son) + initial `.n` (nkRecList) allocation + seal go through
# the builder; field growth (via `semRecordNodeAux` into `result.n`) and flags
# stay direct pokes on the live shell, as in `semProcTypeNode`/`semRangeAux`.
var rb = openPair(c, tyObject, prev)
result = rb.pair.decl
if needsForwardUpdate:
# if the inherited object is a forward type,
# the entire object needs to be checked again
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) # we retry in the final pass
rawAddSon(result, realBase)
rb.addRaw realBase
if realBase == nil and tfInheritable in flags:
result.incl tfInheritable
if tfAcyclic in flags: result.incl tfAcyclic
if result.n.isNil:
result.n = newNodeI(nkRecList, n.info)
rb.setN newNodeI(nkRecList, n.info)
else:
# partial object so add things to the check
if not tryAddInheritedFields(c, check, pos, result, n, isPartial = true):
@@ -1169,6 +1200,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
incl(result, tfFinal)
if c.inGenericContext == 0 and computeRequiresInit(c, result):
result.incl tfRequiresInit
result = finishPair(rb).decl # seal the deferred object build
proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
if n.len < 1:
@@ -1359,7 +1391,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
for i in 0..<paramType.len:
let t = recurse(paramType[i])
if t != nil:
paramType[i] = t
var b = reopen(paramType)
b.setSon(i, t)
result = paramType
of tyAlias, tyOwned:
@@ -1387,32 +1420,38 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
globalError(c.config, info, errIllegalRecursionInTypeX % typeToString(paramType))
var lifted = recurse(paramType[i])
if lifted != nil:
paramType[i] = lifted
var b = reopen(paramType)
b.setSon(i, lifted)
result = paramType
of tyGenericBody:
result = newTypeS(tyGenericInvocation, c)
result.rawAddSon(paramType)
# A user-type-class body instantiates to a tyUserTypeClassInst, everything
# else to a tyGenericInvocation. The kind is decided up front (from the
# already-complete `paramType`), so the builder opens with the final tag
# rather than the old mint-as-invocation-then-mutate-kind dance.
let isUserTypeClass = paramType.typeBodyImpl.kind == tyUserTypeClass
var b = openType(c, if isUserTypeClass: tyUserTypeClassInst else: tyGenericInvocation)
b.addRaw paramType
for i in 0..<paramType.len - 1:
if paramType[i].kind == tyStatic:
var staticCopy = paramType[i].exactReplica(c.idgen)
staticCopy.incl tfInferrableStatic
result.rawAddSon staticCopy
b.addRaw staticCopy
else:
result.rawAddSon newTypeS(tyAnything, c)
b.addRaw newTypeS(tyAnything, c)
if paramType.typeBodyImpl.kind == tyUserTypeClass:
result.kind = tyUserTypeClassInst
result.rawAddSon paramType.typeBodyImpl
return addImplicitGeneric(c, result, paramTypId, info, genericParams, paramName)
if isUserTypeClass:
b.addRaw paramType.typeBodyImpl
return addImplicitGeneric(c, finish b, paramTypId, info, genericParams, paramName)
result = finish b
let x = instGenericContainer(c, paramType.sym.info, result,
allowMetaTypes = true)
result = newTypeS(tyCompositeTypeClass, c)
result.rawAddSon paramType
result.rawAddSon x
result = addImplicitGeneric(c, result, paramTypId, info, genericParams, paramName)
var cb = openType(c, tyCompositeTypeClass)
cb.addRaw paramType
cb.addRaw x
result = addImplicitGeneric(c, finish cb, paramTypId, info, genericParams, paramName)
of tyGenericInst:
result = nil
@@ -1426,7 +1465,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
for i in 1..<paramType.len-1:
var lifted = recurse(paramType[i])
if lifted != nil:
paramType[i] = lifted
var b = reopen(paramType)
b.setSon(i, lifted)
result = paramType
result.last.shouldHaveMeta
if paramType.isConcept:
@@ -1443,7 +1483,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
for i in 1..<paramType.len:
#if paramType[i].kind != tyTypeDesc:
let lifted = recurse(paramType[i])
if lifted != nil: paramType[i] = lifted
if lifted != nil:
var b = reopen(paramType)
b.setSon(i, lifted)
let body = paramType.base
if body.kind in {tyForward, tyError}:
@@ -1504,7 +1546,14 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
# for historical reasons (code grows) this is invoked for parameter
# lists too and then 'isType' is false.
checkMinSonsLen(n, 1, c.config)
# Deferred build: `newProcType` opens the shell with a nil return-type slot
# (son 0) and an effect-list `.n`; params are appended as interleaved son +
# `.n` entries below, and son 0 is back-patched once the return type is known.
# `openType ... finish` cannot model the placeholder-then-backpatch, so the
# son tree grows through a `TypePairBuilder` reopened on the shell. Flags and
# `.n.typ` remain direct pokes on the live shell (`result` == `rb.pair.decl`).
result = newProcType(c, n.info, prev)
var rb = reopenPair(result, c.idgen)
var check = initIntSet()
var counter = 0
template isCurrentlyGeneric: bool =
@@ -1642,8 +1691,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
inc(counter)
if def != nil and def.kind != nkEmpty:
arg.ast = copyTree(def)
result.n.add newSymNode(arg)
rawAddSon(result, finalType)
rb.addRecField newSymNode(arg)
rb.addRaw finalType
addParamOrResult(c, arg, kind)
styleCheckDef(c, a[j].info, arg)
onDef(a[j].info, arg)
@@ -1703,7 +1752,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
# we don't need to change the return type to iter[T]
result.incl tfIterator
# XXX Would be nice if we could get rid of this
result[0] = r
rb.setSon(0, r)
let oldFlags = result.flags
propagateToOwner(result, r)
if oldFlags != result.flags:
@@ -1721,6 +1770,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
n.sym.transitionGenericParamToType()
n.sym.typ.excl tfWildcard
result = finishPair(rb).decl # seal the deferred proc-type build
proc semStmtListType(c: PContext, n: PNode, prev: PType): PType =
checkMinSonsLen(n, 1, c.config)
for i in 0..<n.len - 1:
@@ -1789,24 +1840,32 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
var t = s.typ.skipTypes({tyAlias})
if t.kind == tyCompositeTypeClass and t.base.kind == tyGenericBody:
t = t.base
result = newOrPrevType(tyGenericInvocation, prev, c)
addSonSkipIntLit(result, t, c.idgen)
# Deferred build: the tyGenericInvocation's identity is published to
# `forwardTypeUpdates` (a retry pass) and consumed by `instGenericContainer`,
# both only after its arg sons are appended. The son-tree goes through one
# deferred `rb`; `result` stays the live shell (later branches may replace it
# with an error/forward type or the instantiated container). Sealed once the
# args are in, before any consumer reads it.
var rb = openPair(c, tyGenericInvocation, prev)
result = rb.pair.decl
rb.add t
template addToResult(typ, skip) =
if typ.isNil:
internalAssert c.config, false
rawAddSon(result, typ)
rb.addRaw typ
else:
if skip:
addSonSkipIntLit(result, typ, c.idgen)
rb.add typ
else:
rawAddSon(result, makeRangeWithStaticExpr(c, typ.n))
rb.addRaw makeRangeWithStaticExpr(c, typ.n)
if t.kind == tyForward:
for i in 1..<n.len:
var elem = semGenericParamInInvocation(c, n[i])
addToResult(elem, true)
result = finishPair(rb).decl # seal the deferred invocation build
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
return
elif t.kind != tyGenericBody:
@@ -1854,6 +1913,8 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
if typ.kind == tyForward:
hasForwardTypeParam = true
result = finishPair(rb).decl # seal the deferred invocation build (args complete)
if isConcrete:
if s.ast == nil and s.typ.kind != tyCompositeTypeClass:
# XXX: What kind of error is this? is it still relevant?
@@ -2440,8 +2501,9 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
let old = result
result = copyType(result, c.idgen, getCurrOwner(c))
copyTypeProps(c.graph, c.idgen.module, result, old)
var b = reopen(result, c.idgen)
for i in 1..<n.len:
result.rawAddSon(semTypeNode(c, n[i], nil))
b.addRaw(semTypeNode(c, n[i], nil))
of mDistinct:
checkSonsLen(n, 2, c.config)
var b = openType(c, tyDistinct, prev)

View File

@@ -57,12 +57,17 @@ proc searchInstTypes*(g: ModuleGraph; key: PType): PType =
return inst
proc cacheTypeInst(c: PContext; inst: PType) =
let gt = inst[0]
proc cacheTypeInst(c: PContext; inst: TypePair) =
# Publishes an in-progress instance under its name, for recursive
# instantiations. Takes the (identity, tree) pair rather than a bare `PType`:
# the cache key is derived from the generic head's identity, and only the
# instance's identity is registered -- today via `inst.decl`, under NIF via
# `inst.id`.
let gt = inst.decl[0]
let t = if gt.kind == tyGenericBody: gt.typeBodyImpl else: gt
if t.kind in {tyStatic, tyError, tyGenericParam} + tyTypeClasses:
return
addToGenericCache(c, gt.sym, inst)
addToGenericCache(c, gt.sym, inst.decl)
type
TReplTypeVars* = object
@@ -466,7 +471,8 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
x = lookupTypeVar(cl, x)
if x != nil:
if header == t: header = instCopyType(cl, t)
header[i] = x
var hb = reopen(header)
hb.setSon(i, x)
propagateToOwner(header, x)
else:
# Under IC `t` may be a loaded dep type (Sealed/immutable); mutating it
@@ -493,16 +499,17 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
# the generic body's module (`t.genericHead.owner`) has no business owning a
# type that references instantiation-site types — that is the IC parent->child
# heap leak the write-barrier surfaces.
result = newType(tyGenericInst, cl.c.idgen, cl.c.module, son = header.genericHead)
result.flags = header.flags
var rb = openPair(tyGenericInst, cl.c.idgen, cl.c.module, son = header.genericHead)
rb.setFlags header.flags
# be careful not to propagate unnecessary flags here (don't use rawAddSon)
# ugh need another pass for deeply recursive generic types (e.g. PActor)
# we need to add the candidate here, before it's fully instantiated for
# recursive instantions:
# recursive instantions: publish the instance's *identity* (`rb.pair`) while
# its body is still open, so recursive instantiations find it under its name.
if not cl.allowMetaTypes:
cacheTypeInst(cl.c, result)
cacheTypeInst(cl.c, rb.pair)
else:
cl.localCache[t.itemId] = result
cl.localCache[t.itemId] = rb.pair.decl
let oldSkipTypedesc = cl.skipTypedesc
cl.skipTypedesc = true
@@ -516,17 +523,18 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
else:
header[i]
assert x.kind != tyGenericInvocation
header[i] = x
var hb = reopen(header)
hb.setSon(i, x)
propagateToOwner(header, x)
cl.typeMap.put(body[i-1], x)
for i in FirstGenericParamAt..<t.kidsLen:
# if one of the params is not concrete, we cannot do anything
# but we already raised an error!
rawAddSon(result, header[i], propagateHasAsgn = false)
rb.addRaw(header[i], propagateHasAsgn = false)
if body.kind == tyError:
return
return finishPair(rb).decl
let bbody = last body
var newbody = replaceTypeVarsT(cl, bbody, isInstValue = true)
@@ -538,7 +546,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
# 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
rb.setFlags(rb.flags + newbodyFlags - tfInstClearedFlags)
setToPreviousLayer(cl.typeMap)
@@ -549,7 +557,8 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
# handleGenericInvocation will handle the alias-to-alias-to-alias case
if newbody.isGenericAlias: newbody = newbody.skipGenericAlias
rawAddSon(result, newbody)
rb.addRaw newbody
result = finishPair(rb).decl
checkPartialConstructedType(cl.c.config, cl.info, newbody)
if not cl.allowMetaTypes:
let dc = cl.c.graph.getAttachedOp(newbody, attachedDeepCopy)
@@ -614,10 +623,11 @@ proc eraseTupleVoidFields*(t: PType) =
if t.n[i].kind == nkRecList or t[i].kind == tyVoid:
# found first void field, compact from here
var pos = i
var b = reopen(t)
for j in i+1..<t.kidsLen:
if t[j].kind != tyVoid and j < t.n.len and t.n[j].kind != nkRecList:
t.n[pos] = t.n[j]
t[pos] = t[j]
b.setSon(pos, t[j])
if t.n[pos].kind == nkSym:
t.n[pos].sym.position = pos
inc pos
@@ -627,11 +637,12 @@ proc eraseTupleVoidFields*(t: PType) =
break
proc skipIntLiteralParams*(t: PType; idgen: IdGenerator) =
var b = reopen(t)
for i, p in t.ikids:
if p == nil: continue
let skipped = p.skipIntLit(idgen)
if skipped != p:
t[i] = skipped
b.setSon(i, skipped)
if i > 0: t.n[i].sym.typ = skipped
# when the typeof operator is used on a static input
@@ -769,11 +780,12 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
bailout()
result = instCopyType(cl, t)
cl.localCache[t.itemId] = result
var b = reopen(result)
for i in FirstGenericParamAt..<result.kidsLen:
var r = result[i]
if r != nil:
r = replaceTypeVarsT(cl, r)
result[i] = r
b.setSon(i, r)
propagateToOwner(result, r)
result.n = replaceTypeVarsN(cl, result.n)
if not cl.allowMetaTypes and result.n != nil and
@@ -785,8 +797,9 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
bailout()
result = instCopyType(cl, t)
cl.localCache[t.itemId] = result
var b = reopen(result)
for i in FirstGenericParamAt..<result.kidsLen:
result[i] = replaceTypeVarsT(cl, result[i])
b.setSon(i, replaceTypeVarsT(cl, result[i]))
propagateToOwner(result, result.last)
else:
@@ -802,6 +815,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
cl.localCache[t.itemId] = result
let propagateInstValue = isInstValue and isRefPtrObject(t)
var b = reopen(result)
for i, resulti in result.ikids:
if resulti != nil:
if resulti.kind == tyGenericBody and not cl.allowMetaTypes:
@@ -817,7 +831,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
if r2.kind in {tyPtr, tyRef}:
r = skipTypes(r2, {tyPtr, tyRef})
if result.kind != tyProc or i == 0:
result[i] = r
b.setSon(i, r)
if result.kind != tyArray or i != 0:
propagateToOwner(result, r)
# bug #4677: Do not instantiate effect lists

View File

@@ -884,11 +884,13 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
openScope(c)
matchedConceptContext.candidateType = a
typeClass[0][0] = a
var tcb = reopen(typeClass[0])
tcb.setSon(0, a)
c.matchedConcept = addr(matchedConceptContext)
defer:
c.matchedConcept = prevMatchedConcept
typeClass[0][0] = prevCandidateType
var tcb2 = reopen(typeClass[0])
tcb2.setSon(0, prevCandidateType)
closeScope(c)
var typeParams: seq[(PSym, PType)] = @[]

View File

@@ -102,6 +102,26 @@ proc reopen*(t: PType; idgen: IdGenerator): TypeBuilder {.inline.} =
## case: the name (`t`) stays, only its tree structure is (re)built.
TypeBuilder(t: t, idgen: idgen)
proc reopen*(t: PType): TypeBuilder {.inline.} =
## Reopens an existing type purely to *transform* its sons in place (see
## `setSon`), without adding fresh ones -- so no `idgen` is needed. This is the
## "mutable staging buffer" seam for son-replacement: today it is in-place
## mutation of `t`; under NIF `reopen` thaws `t`'s sealed cursor into a mutable
## buffer, `setSon` rewrites a token, and the buffer is re-sealed. Distinct from
## the id-minting `reopen(t, idgen)` used to (re)build a forward type's body.
TypeBuilder(t: t, idgen: nil)
proc setSon*(b: var TypeBuilder; i: int; son: PType) {.inline.} =
## Replaces son `i` of a reopened type -- the in-place transform seam. Mirrors
## the old `PType.[]=` (via `ast.replaceSon`), including the `tyProc` return/
## param slot handling. Distinct from `add` (append a new son) and from the
## whole-list `ast.setSon(dest, son)`. Under NIF this is a token rewrite in the
## buffer thawed by `reopen`.
replaceSon(b.t, i, son)
proc setSon*(b: var TypeBuilder; i: BackwardsIndex; son: PType) {.inline.} =
replaceSon(b.t, i, son)
proc setN*(b: var TypeBuilder; n: PNode) {.inline.} =
b.t.n = n
@@ -131,3 +151,81 @@ template finish*(b: TypeBuilder): PType =
## read with no call/move/destroy overhead over the old direct construction.
## Later this becomes `beginRead`, yielding a read-only cursor.
b.t
type
TypePairBuilder* = object
## The *deferred* construction seam: like `TypeBuilder`, but its identity is
## published -- cached, stashed for a later pass, or handed to a recursive
## sem call -- *before* its body is finished. Recursive generic
## instantiation needs the in-progress instance to be findable under its
## name while its sons are still being appended; `TypeBuilder` cannot model
## that because `finish` is the seal point and nothing may be appended after
## it. `TypePairBuilder` can, because the thing it hands out early is a
## `TypePair` -- an (identity, tree) pair -- and early consumers take only
## its `id`.
##
## Contract: whatever observes `pair` before `finishPair` must rely on
## `pair.id` (the name) alone -- never the son count or son contents of the
## still-open `decl`. Today `decl` is the growing `PType` and `decl.itemId
## == id`, so this holds trivially; under NIF `id` is a `SymId` valid the
## instant the shell exists and `decl` is the open `TokenBuf`, sealed into a
## read-only cursor by `finishPair`.
t: PType
idgen {.cursor.}: IdGenerator
proc openPair*(kind: TTypeKind; idgen: IdGenerator; owner: PSym;
son: sink PType = nil): TypePairBuilder {.inline.} =
## Mints the shell (optionally with `son0` already set -- e.g. the generic
## head for `tyGenericInst`). Mirrors `newType(kind, idgen, owner, son)`. The
## `pair` is publishable the moment this returns.
TypePairBuilder(t: newType(kind, idgen, owner, son), idgen: idgen)
proc reopenPair*(t: PType; idgen: IdGenerator): TypePairBuilder {.inline.} =
## Continues building an *existing* (forward-declared / partial) type as a
## deferred pair, preserving its identity. The deferred analogue of
## `reopen(t, idgen)`; its `pair` is publishable immediately.
TypePairBuilder(t: t, idgen: idgen)
proc pair*(b: TypePairBuilder): TypePair {.inline.} =
## The publishable (identity, tree) handle -- cache it / stash it / thread it
## through recursive sem *before* the body is complete. Only `pair.id` may be
## relied upon by those early consumers.
typePair(b.t)
proc add*(b: var TypePairBuilder; son: PType) {.inline.} =
addSonSkipIntLit(b.t, son, b.idgen)
proc addRaw*(b: var TypePairBuilder; son: PType; propagateHasAsgn = true) {.inline.} =
## Appends a son verbatim while the body is open. Mirrors `rawAddSon` -- the
## incremental-append step of the deferred build.
rawAddSon(b.t, son, propagateHasAsgn)
proc setSon*(b: var TypePairBuilder; i: int; son: PType) {.inline.} =
replaceSon(b.t, i, son)
proc setN*(b: var TypePairBuilder; n: PNode) {.inline.} =
b.t.n = n
proc addRecField*(b: var TypePairBuilder; fieldNode: PNode) {.inline.} =
## Appends a field entry to the type's record list (`.n`), the way tuple /
## object / proc bodies grow their `nkRecList` / `nkFormalParams`. Mirrors
## `t.n.add fieldNode`, and pairs with `add`/`addRaw` for the parallel son.
b.t.n.add fieldNode
proc flags*(b: TypePairBuilder): TTypeFlags {.inline.} =
## Reads the shell's current flags (they may have accumulated via `addRaw`'s
## propagation since the last `setFlags`).
b.t.flags
proc setFlags*(b: var TypePairBuilder; flags: TTypeFlags) {.inline.} =
b.t.flags = flags
proc incl*(b: var TypePairBuilder; flag: TTypeFlag) {.inline.} =
b.t.incl flag
proc finishPair*(b: sink TypePairBuilder): TypePair {.inline.} =
## Seals the deferred build. Today returns the pair unchanged; under NIF this
## is `beginRead` -- the open `TokenBuf` becomes a read-only cursor, still
## reachable through `pair.id`, so recursive references bound to the name now
## resolve to the sealed tree.
typePair(b.t)

View File

@@ -11,7 +11,7 @@
import
ast, astalgo, trees, msgs, platform, renderer, options,
lineinfos, int128, modulegraphs, astmsgs, wordrecg
lineinfos, int128, modulegraphs, astmsgs, wordrecg, typebuilders
import std/[intsets, strutils]
@@ -1267,7 +1267,8 @@ proc baseOfDistinct*(t: PType; g: ModuleGraph; idgen: IdGenerator): PType =
parent = it
it = it.elementType
if it.kind == tyDistinct and parent != nil:
parent[0] = it[0]
var b = reopen(parent)
b.setSon(0, it[0])
proc safeInheritanceDiff*(a, b: PType): int =
# same as inheritanceDiff but checks for tyError:
@@ -1444,7 +1445,8 @@ proc takeType*(formal, arg: PType; g: ModuleGraph; idgen: IdGenerator): PType =
arg.isEmptyContainer:
let a = copyType(arg.skipTypes({tyGenericInst, tyAlias}), idgen, arg.owner)
copyTypeProps(g, idgen.module, a, arg)
a[ord(arg.kind == tyArray)] = formal[0]
var b = reopen(a)
b.setSon(ord(arg.kind == tyArray), formal[0])
result = a
elif formal.kind in {tyTuple, tySet} and arg.kind == formal.kind:
result = formal