cgen: name the PType child instead of subscripting it

The same treatment the `PNode` side just got, for the reason that applies to
types: `t[0]` is the return type, the base class, the index type or the generic
head depending on the kind, and the subscript says none of that. Every child
access in the cgen files that has a named accessor now uses it — `baseClass`
for the eleven object-hierarchy walks, `elementType` for the seq/openArray
element, `returnType`, `genericHead`, `firstGenericParam` — and the two loops
that walked a type's children become `paramTypes` and `kids`.

Left indexed on purpose: a parameter reached by ARGUMENT position
(`typ[i]` in ccgcalls/ccgstmts), a tuple field, and a generic parameter at an
explicit index. There the index is the clearest thing to write.

Every substitution is exact rather than merely close. `[]` with index 0 is
unconditionally `sonsImpl[0]`, so `baseClass`/`returnType`/`genericHead` cannot
diverge; `elementType` is `sonsImpl[^1]` and is used only where the type has a
single son; `paramTypes` and `kids` are literally the loops they replace.

`ast.sons(t: PType)` gets the warning it has been missing. Despite the name it
is not the counterpart of the `sons` ITERATOR over a `PNode`: it returns the
raw seq, and a `tyProc` keeps its parameter types in `n`, so that seq holds
only the return type while `[]`/`len`/`kids` route parameters through
`n[i].sym.typ`. `for x in t.sons` therefore compiles, reads exactly like the
`PNode` idiom, and visits a different set of types — which is what
`ccgutils.encodeType` would have started doing had it been converted to `sons`
rather than `kids`. Marking the proc deprecated and rebuilding shows one call
site in the whole compiler (`previouslyInferred`), so the trap is latent, not
active.

`bnode.nim` also records that there is deliberately no `BType` beside `BNode`:
types stay `PType`s under `newIcBackend` — `BNode.typ` returns one — because
the backend asks them questions (`skipTypes`, `getSize`, `lengthOrd`, the
record walk over `t.n`) that a raw cursor cannot answer.

Pure refactor: all 216 generated `.c` files byte-identical to the parent commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMyRHByv7hhaQJ4Pa1bHbE
This commit is contained in:
araq
2026-08-29 09:05:44 +02:00
parent 437876efbd
commit 2447dfdc7d
6 changed files with 53 additions and 29 deletions

View File

@@ -359,6 +359,18 @@ proc `flags=`*(t: PType, val: TTypeFlags) {.inline.} =
t.flagsImpl = val
proc sons*(t: PType): var TTypeSeq {.inline.} =
## The RAW child seq. Despite the name this is NOT the counterpart of the
## `sons` ITERATOR over a `PNode`, and it is not the way to walk a type's
## children — use `kids` / `ikids` / `paramTypes` / `signature`, or the named
## accessors (`returnType`, `baseClass`, `elementType`, `indexType`,
## `genericHead`, ...), which say WHICH child they mean.
##
## The difference is not cosmetic. A `tyProc` keeps its parameter types in
## `n`, not here — `setSons` asserts `sonsImpl.len <= 1` for one — so `[]`,
## `len` and every iterator built on them route parameters through
## `n[i].sym.typ`, while this seq holds only the return type. `for x in
## t.sons` therefore compiles, looks like the `PNode` idiom, and silently
## visits a different set of types.
if t.state == Partial: loadType(t)
result = t.sonsImpl

View File

@@ -61,10 +61,23 @@
## remaining subscripts are writes that build a fresh `nkProcDef`
## (`theProc[namePos] = ...`), which a `Cursor` backend will not do at all, and
## accesses to a `PType`, a `string`, a `seq` or a `Table`, none of which are
## `BNode`s. `PType` is the trap to watch for: `ast.sons(t: PType)` is a `proc`
## returning `var TTypeSeq`, NOT the iterator of the same name, so `t[i]` there
## means something else entirely. The `firstSon`/`secondSon`/`lastSon`/`son`
## family is defined for `PNode` only, so a mistaken base does not compile.
## `BNode`s. The `firstSon`/`secondSon`/`lastSon`/`son` family is defined for
## `PNode` only, so a mistaken base does not compile.
##
## A `PType` has its own vocabulary and its own reason for preferring it: `t[0]`
## is the return type, the base class, the index type or the generic head
## depending on the kind, and `ast.sons(t: PType)` is a `proc` returning the raw
## seq — NOT the iterator of the same name — which for a `tyProc` does not hold
## the parameters at all. Reach for `returnType` / `baseClass` / `elementType` /
## `genericHead` and the `kids` / `ikids` / `paramTypes` / `signature`
## iterators, which name the child and go through `[]`.
##
## There is deliberately no `BType` alongside `BNode`. Types stay `PType`s even
## under `newIcBackend` — `typ` below returns one — because the backend asks
## them semantic questions (`skipTypes`, `getSize`, `lengthOrd`, the record
## walk over `t.n`) that a raw cursor cannot answer. `ast2nif` already
## materializes them lazily from the module's type index, which is the seam
## that matters on that side.
import ast, lineinfos

View File

@@ -412,7 +412,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
elif not isObjLackingTypeField(ty):
genGenericAsgn(p, dest, src, flags)
elif containsGarbageCollectedRef(ty):
if ty[0].isNil and asgnComplexity(ty.n) <= 4 and
if ty.baseClass.isNil and asgnComplexity(ty.n) <= 4 and
needAssignCall notin flags: # calls might contain side effects
discard getTypeDesc(p.module, ty)
internalAssert p.config, ty.n != nil
@@ -1040,7 +1040,7 @@ proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope;
break
if not p.module.compileToCpp:
r = dotField(r, "Sup")
ty = ty[0]
ty = ty.baseClass
if result == nil: internalError(p.config, field.info, "genCheckedRecordField")
proc genRecordField(p: BProc, e: PNode, d: var TLoc) =
@@ -2336,7 +2336,7 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc, noinit = false) =
let name = if noinit: "setLengthSeqUninit" else: "setLengthSeqV2"
call.snippet = cCast(rt, cgCall(p, name, pExpr, rti, rb,
isTrivialTypesToSnippet(t.skipTypes(abstractInst)[0])))
isTrivialTypesToSnippet(t.skipTypes(abstractInst).elementType)))
genAssignment(p, a, call, {})
gcUsage(p.config, e)
@@ -4127,7 +4127,7 @@ proc genConstTuple(p: BProc, n: PNode; isConst: bool; tup: PType; result: var Bu
genBracedInit(p, it, isConst, tup[i], result)
proc genConstSeq(p: BProc, n: PNode, t: PType; isConst: bool; result: var Builder) =
let base = t.skipTypes(abstractInst)[0]
let base = t.skipTypes(abstractInst).elementType
let tmpName = getTempName(p.module)
# genBracedInit can modify cfsStrData, we need an intermediate builder:
@@ -4160,7 +4160,7 @@ proc genConstSeq(p: BProc, n: PNode, t: PType; isConst: bool; result: var Builde
result.add cCast(typ = getTypeDesc(p.module, t), value = cAddr(tmpName))
proc genConstSeqV2(p: BProc, n: PNode, t: PType; isConst: bool; result: var Builder) =
let base = t.skipTypes(abstractInst)[0]
let base = t.skipTypes(abstractInst).elementType
let payload = getTempName(p.module)
# genBracedInit can modify cfsStrData, we need an intermediate builder:

View File

@@ -59,10 +59,10 @@ proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string =
result = "_Z" # Common prefix in Itanium ABI
var params = ""
var staticLists = ""
if s.typ.len > 1: #we dont care about the return param
for i in 1..<s.typ.len:
if s.typ[i].isNil: continue
params.add encodeType(m, s.typ[i], staticLists)
if s.typ.paramsLen > 0: # we dont care about the return param
for _, pt in paramTypes(s.typ):
if pt.isNil: continue
params.add encodeType(m, pt, staticLists)
result.add encodeSym(m, s, makeUnique, staticLists)
result.add params
@@ -311,7 +311,7 @@ proc isInvalidReturnType(conf: ConfigRef; typ: PType, isProc = true): bool =
var rettype = typ
var isAllowedCall = true
if isProc:
rettype = rettype[0]
rettype = rettype.returnType
isAllowedCall = typ.callConv in {ccClosure, ccInline, ccNimCall}
if rettype == nil or (isAllowedCall and
getSize(conf, rettype) > conf.target.floatSize*3):
@@ -480,7 +480,7 @@ proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet; kind: TypeDescKind
of tySequence:
let sig = hashType(t, m.config)
if optSeqDestructors in m.config.globalOptions:
if skipTypes(etB[0], typedescInst).kind == tyEmpty:
if skipTypes(etB.elementType, typedescInst).kind == tyEmpty:
internalError(m.config, "cannot map the empty seq type to a C type")
result = cacheGetType(m.forwTypeCache, sig)
@@ -524,7 +524,7 @@ proc seqV2ContentType(m: BModule; t: PType; check: var IntSet) =
if result == "":
discard getTypeDescAux(m, t, check, dkVar)
else:
let dataTyp = getTypeDescAux(m, t.skipTypes(abstractInst)[0], check, dkVar)
let dataTyp = getTypeDescAux(m, t.skipTypes(abstractInst).elementType, check, dkVar)
m.s[cfsTypes].addSimpleStruct(m, name = result & "_Content", baseType = ""):
m.s[cfsTypes].addField(name = "cap", typ = NimInt)
m.s[cfsTypes].addField(name = "data",
@@ -715,7 +715,7 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
# need to pass hidden parameter:
params.addParam(paramBuilder, name = param.locImpl.snippet & "Len_" & $j, typ = NimInt)
inc(j)
arr = arr[0].skipTypes({tySink})
arr = arr.elementType.skipTypes({tySink})
if t.returnType != nil and isInvalidReturnType(m.config, t):
var arr = t.returnType
var typ: Snippet
@@ -915,7 +915,7 @@ proc resolveStarsInCppType(typ: PType, idx, stars: int): PType =
result = typ[idx]
for i in 1..stars:
if result != nil and result.kidsLen > 0:
result = if result.kind == tyGenericInst: result[FirstGenericParamAt]
result = if result.kind == tyGenericInst: result.firstGenericParam
else: result.elemType
proc getOpenArrayDesc(m: BModule; t: PType, check: var IntSet; kind: TypeDescKind): Rope =
@@ -1462,7 +1462,7 @@ proc discriminatorTableName(m: BModule; objtype: PType, d: PSym): Rope =
# bugfix: we need to search the type that contains the discriminator:
var objtype = objtype.skipTypes(abstractPtrs)
while lookupInRecord(objtype.n, d.name) == nil:
objtype = objtype[0].skipTypes(abstractPtrs)
objtype = objtype.baseClass.skipTypes(abstractPtrs)
if objtype.sym == nil:
internalError(m.config, d.info, "anonymous obj with discriminator")
result = "NimDT_$1_$2" % [rope($hashType(objtype, m.config)), rope(d.name.s.mangle)]
@@ -1861,7 +1861,7 @@ proc getObjDepth(t: PType): int16 =
result = -1
while x != nil:
x = skipTypes(x, skipPtrs)
x = x[0]
x = x.baseClass
inc(result)
proc genDisplayElem(d: MD5Digest): uint32 =
@@ -1877,7 +1877,7 @@ proc genDisplay(result: var Builder, m: BModule; t: PType, depth: int) =
while x != nil:
x = skipTypes(x, skipPtrs)
seqs[i] = cIntValue(genDisplayElem(MD5Digest(hashType(x, m.config))))
x = x[0]
x = x.baseClass
inc i
var arr: StructInitializer

View File

@@ -92,7 +92,7 @@ proc ccgIntroducedPtr*(conf: ConfigRef; s: PSym, retType: PType): bool =
result = true
elif (optByRef in s.options) or (getSize(conf, pt) > conf.target.floatSize * 3):
result = true # requested anyway
elif (tfFinal in pt.flags) and (pt[0] == nil):
elif (tfFinal in pt.flags) and (pt.baseClass == nil):
result = false # no need, because no subtyping possible
else:
result = true # ordinary objects are always passed by reference,
@@ -148,7 +148,7 @@ proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
of tyObject, tyEnum, tyDistinct, tyUserTypeClass, tyGenericParam:
result = encodeSym(m, t.sym)
of tyGenericInst, tyUserTypeClassInst, tyGenericBody:
result = encodeName(t[0].sym.name.s)
result = encodeName(t.genericHead.sym.name.s)
result.add "I"
for i in 1..<t.len - 1:
result.add encodeType(m, t[i], staticLists)
@@ -160,8 +160,7 @@ proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
of tySequence: encodeName("seq")
else: encodeName(kindName)
result.add "I"
for i in 0..<t.len:
let s = t[i]
for s in kids(t):
if s.isNil: continue
result.add encodeType(m, s, staticLists)
result.add "E"

View File

@@ -645,9 +645,9 @@ proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: var TLoc,
if mode == constructRefObj: r = cDeref(r)
var s = skipTypes(t, abstractInst)
if not p.module.compileToCpp:
while s.kind == tyObject and s[0] != nil:
while s.kind == tyObject and s.baseClass != nil:
r = dotField(r, "Sup")
s = skipTypes(s[0], skipPtrs)
s = skipTypes(s.baseClass, skipPtrs)
if optTinyRtti in p.config.globalOptions:
p.s(section).addFieldAssignment(r, "m_type", genTypeInfoV2(p.module, t, a.lode.info))
else:
@@ -680,9 +680,9 @@ proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: var TLoc,
if mode == constructRefObj: r = cDeref(r)
var s = skipTypes(t, abstractInst)
if not p.module.compileToCpp:
while s.kind == tyObject and s[0] != nil and s.sym.magic != mException:
while s.kind == tyObject and s.baseClass != nil and s.sym.magic != mException:
r = dotField(r, "Sup")
s = skipTypes(s[0], skipPtrs)
s = skipTypes(s.baseClass, skipPtrs)
p.s(section).addFieldAssignment(r, "name", makeCString(t.skipTypes(abstractInst).sym.name.s))
proc genRefAssign(p: BProc, dest, src: TLoc)