massive PType refactor; the compiler moves to a NIF based type representation internally

This commit is contained in:
Araq
2026-07-08 11:32:50 +02:00
parent abdf1ca559
commit bd0de5f9aa
6 changed files with 507 additions and 147 deletions

View File

@@ -144,18 +144,19 @@ proc lowerSwap*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PNod
result.add newFastAsgnStmt(n[2], tempAsNode)
proc createObj*(g: ModuleGraph; idgen: IdGenerator; owner: PSym, info: TLineInfo; final=true): PType =
result = newType(tyObject, idgen, owner)
var b = openType(tyObject, idgen, owner)
if final:
rawAddSon(result, nil)
incl result, tfFinal
b.addRaw nil
b.incl tfFinal
else:
rawAddSon(result, getCompilerProc(g, "RootObj").typ)
result.n = newNodeI(nkRecList, info)
b.addRaw getCompilerProc(g, "RootObj").typ
b.setN newNodeI(nkRecList, info)
let s = newSym(skType, getIdent(g.cache, "Env_" & toFilename(g.config, info) & "_" & $owner.name.s),
idgen, owner, info, owner.options)
incl s.flagsImpl, sfAnon
b.setSym s
result = finish b
s.typ = result
result.sym = s
template fieldCheck {.dirty.} =
when false:
@@ -385,8 +386,9 @@ proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode =
proc genAddrOf*(n: PNode; idgen: IdGenerator; typeKind = tyPtr): PNode =
result = newNodeI(nkAddr, n.info, 1)
result[0] = n
result.typ = newType(typeKind, idgen, n.typ.owner)
result.typ.rawAddSon(n.typ)
var b = openType(typeKind, idgen, n.typ.owner)
b.addRaw n.typ
result.typ = finish b
proc genDeref*(n: PNode; k = nkHiddenDeref): PNode =
result = newNodeIT(k, n.info,

View File

@@ -11,9 +11,10 @@
import
ast, msgs, platform, idents,
modulegraphs, lineinfos, types
modulegraphs, lineinfos, types, typebuilders
export createMagic
export typebuilders
proc nilOrSysInt*(g: ModuleGraph): PType = g.sysTypes[tyInt]
@@ -91,24 +92,13 @@ proc getFloatLitType*(g: ModuleGraph; literal: PNode): PType =
result = newSysType(g, tyFloat, size=8)
result.n = literal
proc skipIntLit*(t: PType; id: IdGenerator): PType {.inline.} =
if t.n != nil and t.kind in {tyInt, tyFloat}:
result = copyType(t, id, t.owner)
result.n = nil
else:
result = t
proc addSonSkipIntLit*(father, son: PType; id: IdGenerator) =
let s = son.skipIntLit(id)
father.add(s)
propagateToOwner(father, s)
proc makeVarType*(owner: PSym; baseType: PType; idgen: IdGenerator; kind = tyVar): PType =
if baseType.kind == kind:
result = baseType
else:
result = newType(kind, idgen, owner)
addSonSkipIntLit(result, baseType, idgen)
var b = openType(kind, idgen, owner)
b.add baseType
result = finish b
proc getCompilerProc*(g: ModuleGraph; name: string): PSym =
let ident = getIdent(g.cache, name)
@@ -158,8 +148,9 @@ proc getMagicEqSymForType*(g: ModuleGraph; t: PType; info: TLineInfo): PSym =
"can't find magic equals operator for type kind " & $t.kind)
proc makePtrType*(baseType: PType; idgen: IdGenerator): PType =
result = newType(tyPtr, idgen, baseType.owner)
addSonSkipIntLit(result, baseType, idgen)
var b = openType(tyPtr, idgen, baseType.owner)
b.add baseType
result = finish b
proc makeAddr*(n: PNode; idgen: IdGenerator): PNode =
if n.kind == nkHiddenAddr:

154
compiler/nifcmain.nim Normal file
View File

@@ -0,0 +1,154 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Backend-only driver for `nim ic`'s C-generation stages (the `nifc` command:
## `--icBackendStage:lower|cg|merge|emit|link`). This produces the separate
## `bin/nifc` binary that `deps.nim` invokes per rule instead of re-entering the
## monolithic `nim` compiler.
##
## Crucially, it imports NEITHER `main`/`pipelines` NOR `cmdlinehelper`/`nimconf`.
## Those are the only two edges that pull the frontend semantic analyzer (`sem`)
## and the NimScript VM (`scriptconfig`) into the ordinary compiler binary. The
## backend graph (`nifbackend` -> `cgen`/`ast2nif`/`transf`/`injectdestructors`/
## `modulegraphs`) is entirely sem-free, and config is replayed sem-free from the
## precompiled `.cfg.nif` via `icconfig.applyIcConfig` (no file read, no VM run).
##
## Keeping `sem` out of the closure is the prerequisite for building this binary
## with `-d:nimBackend`, under which `astdef` swaps `PNode` to a cursor-backed
## value representation: `sem`'s pervasive `PNode(kind: ...)` literal construction
## could not compile against such a type, but it is no longer linked here.
import std/[os, parseopt, strutils]
when defined(nimPreviewSlimSystem):
import std/assertions
import
commands, options, msgs, extccomp, idents, lineinfos,
pathutils, modulegraphs, condsyms, platform, modules
import "../dist/checksums/src/checksums/sha1"
from ast import setUseIc
from ast2nif import registerNifAstTags
import icconfig
import nifbackend
proc hashMainCompilationParams(conf: ConfigRef): string =
## Mirrors `main.hashMainCompilationParams` (inlined to avoid importing `main`,
## which pulls in `pipelines`/`sem`).
var state = newSha1State()
state.update os.getAppFilename()
state.update conf.commandLine
state.update $conf.projectFull
result = $SecureHash(state.finalize())
proc setOutFile(conf: ConfigRef) =
## Mirrors `main.setOutFile` (inlined, same reason).
if conf.outFile.isEmpty:
var base = conf.projectName
if optUseNimcache in conf.globalOptions:
base.add "_" & hashMainCompilationParams(conf)
let targetName =
if optGenDynLib in conf.globalOptions:
platform.OS[conf.target.targetOS].dllFrmt % base
elif optGenStaticLib in conf.globalOptions:
(if conf.target.targetOS == osWindows: "$1.lib" else: "lib$1.a") % base
else: base & platform.OS[conf.target.targetOS].exeExt
conf.outFile = RelativeFile targetName
proc addCmdPrefix(result: var string, kind: CmdLineKind) =
case kind
of cmdLongOption: result.add "--"
of cmdShortOption: result.add "-"
of cmdArgument, cmdEnd: discard
proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
## Slim copy of `nim.processCmdLine` (no nimble-lock probing, no stdin project):
## the `nifc` child is always launched by `deps.nim` with an explicit project
## NIF and forwarded switches.
var p = parseopt.initOptParser(cmd)
var argsCount = 0
config.commandLine.setLen 0
while true:
parseopt.next(p)
case p.kind
of cmdEnd: break
of cmdLongOption, cmdShortOption:
config.commandLine.add " "
config.commandLine.addCmdPrefix p.kind
config.commandLine.add p.key.quoteShell
if p.val.len > 0:
config.commandLine.add ':'
config.commandLine.add p.val.quoteShell
processSwitch(pass, p, config)
of cmdArgument:
config.commandLine.add " "
config.commandLine.add p.key.quoteShell
if processArgument(pass, p, argsCount, config): break
proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
# NIF tag registration must run before any NIF read/write, independent of
# module init order (see ast2nif.registerNifAstTags).
registerNifAstTags()
condsyms.initDefines(conf.symbols)
defineSymbol(conf.symbols, "nim_compiler")
if paramCount() == 0:
rawMessage(conf, errGenerated, "nifc: no arguments (expected a NIF project)")
return
# Pass 1: learn the command (`nifc`), the project NIF, and switches including
# `--icPreparsedConfig` (needed before config replay below).
processCmdLine(passCmd1, "", conf)
if conf.projectName != "":
setFromProjectName(conf, conf.projectName)
else:
conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile getCurrentDir())
var graph = newModuleGraph(cache, conf)
# Sem-free config: replay the precompiled `.cfg.nif` produced once by the
# `nim icconfig` process. No `nimconf`, no `scriptconfig`, no VM. A missing or
# format-incompatible artifact is fatal here (unlike the frontend, this binary
# has no fallback config parser on purpose).
setDefaultLibpath(conf)
if conf.icPreparsedConfig.len == 0 or not applyIcConfig(conf, conf.icPreparsedConfig):
rawMessage(conf, errGenerated,
"nifc backend requires a valid precompiled config (--icPreparsedConfig)")
return
if conf.backend != backendJs: extccomp.initVars(conf)
# Pass 2: command-line switches override the replayed config.
processCmdLine(passCmd2, "", conf)
if conf.selectedGC == gcUnselected:
initOrcDefines(conf)
if conf.cmd != cmdNifC:
rawMessage(conf, errGenerated, "nifc: only the 'nifc' command is supported")
return
# cmdNifC arm, mirroring `main.mainCommand`:
setUseIc(true)
excl conf.features, Feature.vtables
wantMainModule(conf)
setOutFile(conf)
# `main.commandNifC` body, inlined:
extccomp.initVars(conf)
if not extccomp.ccHasSaneOverflow(conf):
conf.symbols.defineSymbol("nimEmulateOverflowChecks")
nifbackend.generateCode(graph, conf.projectMainIdx)
when compileOption("gc", "refc"):
GC_disableMarkAndSweep()
let conf = newConfigRef()
handleCmdLine(newIdentCache(), conf)
msgQuit(int8(conf.errorCounter > 0))

View File

@@ -17,7 +17,9 @@ when defined(nimPreviewSlimSystem):
import
options, ast, msgs, idents, renderer,
magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable,
types, lowerings, trees, parampatterns, astalgo
types, lowerings, trees, parampatterns, astalgo, typebuilders
export typebuilders
type
TOptionEntry* = object # entries to put on a stack for pragma parsing
@@ -445,8 +447,14 @@ proc addToLib*(lib: PLib, sym: PSym) =
proc newTypeS*(kind: TTypeKind; c: PContext; son: sink PType = nil): PType =
result = newType(kind, c.idgen, getCurrOwner(c), son = son)
proc openType*(c: PContext; kind: TTypeKind): TypeBuilder {.inline.} =
## `PContext`-flavored `openType`: the type is owned by the current owner.
openType(kind, c.idgen, getCurrOwner(c))
proc makePtrType*(owner: PSym, baseType: PType; idgen: IdGenerator): PType =
result = newType(tyPtr, idgen, owner, skipIntLit(baseType, idgen))
var b = openType(tyPtr, idgen, owner)
b.addKeep skipIntLit(baseType, idgen) # son= fast path: skip int-lit, no propagate
result = finish b
proc makePtrType*(c: PContext, baseType: PType): PType =
makePtrType(getCurrOwner(c), baseType, c.idgen)
@@ -459,27 +467,33 @@ proc makeTypeWithModifier*(c: PContext,
if modifier in {tyVar, tyLent, tyTypeDesc} and baseType.kind == modifier:
result = baseType
else:
result = newTypeS(modifier, c, skipIntLit(baseType, c.idgen))
var b = openType(c, modifier)
b.addKeep skipIntLit(baseType, c.idgen)
result = finish b
proc makeVarType*(c: PContext, baseType: PType; kind = tyVar): PType =
if baseType.kind == kind:
result = baseType
else:
result = newTypeS(kind, c, skipIntLit(baseType, c.idgen))
var b = openType(c, kind)
b.addKeep skipIntLit(baseType, c.idgen)
result = finish b
proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
let typedesc = newTypeS(tyTypeDesc, c)
incl typedesc.flagsImpl, tfCheckedForDestructor
internalAssert(c.config, typ != nil)
typedesc.addSonSkipIntLit(typ, c.idgen)
var b = openType(c, tyTypeDesc)
b.incl tfCheckedForDestructor
b.add typ
let typedesc = finish b
let sym = newSym(skType, c.cache.idAnon, c.idgen, getCurrOwner(c), info,
c.config.options).linkTo(typedesc)
result = newSymNode(sym, info)
proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
result = newTypeS(tyFromExpr, c)
assert n != nil
result.n = n
var b = openType(c, tyFromExpr)
b.setN n
result = finish b
when false:
proc newTypeWithSons*(owner: PSym, kind: TTypeKind, sons: seq[PType];
@@ -493,42 +507,49 @@ when false:
proc makeStaticExpr*(c: PContext, n: PNode): PNode =
result = newNodeI(nkStaticExpr, n.info)
result.sons = @[n]
result.typ = if n.typ != nil and n.typ.kind == tyStatic: n.typ
else: newTypeS(tyStatic, c, n.typ)
result.typ =
if n.typ != nil and n.typ.kind == tyStatic: n.typ
else:
var b = openType(c, tyStatic)
b.addKeep n.typ
finish b
proc makeAndType*(c: PContext, t1, t2: PType): PType =
result = newTypeS(tyAnd, c)
result.rawAddSon t1
result.rawAddSon t2
propagateToOwner(result, t1)
propagateToOwner(result, t2)
result.flagsImpl.incl((t1.flags + t2.flags) * {tfHasStatic})
result.flagsImpl.incl tfHasMeta
var b = openType(c, tyAnd)
b.addRaw t1
b.addRaw t2
b.propagateFrom t1
b.propagateFrom t2
b.incl((t1.flags + t2.flags) * {tfHasStatic})
b.incl tfHasMeta
result = finish b
proc makeOrType*(c: PContext, t1, t2: PType): PType =
var b = openType(c, tyOr)
if t1.kind != tyOr and t2.kind != tyOr:
result = newTypeS(tyOr, c)
result.rawAddSon t1
result.rawAddSon t2
b.addRaw t1
b.addRaw t2
else:
result = newTypeS(tyOr, c)
template addOr(t1) =
if t1.kind == tyOr:
for x in t1.kids: result.rawAddSon x
for x in t1.kids: b.addRaw x
else:
result.rawAddSon t1
b.addRaw t1
addOr(t1)
addOr(t2)
propagateToOwner(result, t1)
propagateToOwner(result, t2)
result.incl((t1.flags + t2.flags) * {tfHasStatic})
result.incl tfHasMeta
b.propagateFrom t1
b.propagateFrom t2
b.incl((t1.flags + t2.flags) * {tfHasStatic})
b.incl tfHasMeta
result = finish b
proc makeNotType*(c: PContext, t1: PType): PType =
result = newTypeS(tyNot, c, son = t1)
propagateToOwner(result, t1)
result.flagsImpl.incl(t1.flags * {tfHasStatic})
result.flagsImpl.incl tfHasMeta
var b = openType(c, tyNot)
b.addKeep t1
b.propagateFrom t1
b.incl(t1.flags * {tfHasStatic})
b.incl tfHasMeta
result = finish b
proc nMinusOne(c: PContext; n: PNode): PNode =
result = newTreeI(nkCall, n.info, newSymNode(getSysMagic(c.graph, n.info, "pred", mPred)), n)
@@ -536,19 +557,22 @@ proc nMinusOne(c: PContext; n: PNode): PNode =
# Remember to fix the procs below this one when you make changes!
proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType =
let intType = getSysType(c.graph, n.info, tyInt)
result = newTypeS(tyRange, c, son = intType)
var b = openType(c, tyRange)
b.addKeep intType
if n.typ != nil and n.typ.n == nil:
result.incl tfUnresolved
result.n = newTreeI(nkRange, n.info, newIntTypeNode(0, intType),
b.incl tfUnresolved
b.setN newTreeI(nkRange, n.info, newIntTypeNode(0, intType),
makeStaticExpr(c, nMinusOne(c, n)))
result = finish b
template rangeHasUnresolvedStatic*(t: PType): bool =
tfUnresolved in t.flags
proc errorType*(c: PContext): PType =
## creates a type representing an error state
result = newTypeS(tyError, c)
result.flagsImpl.incl tfCheckedForDestructor
var b = openType(c, tyError)
b.incl tfCheckedForDestructor
result = finish b
proc errorNode*(c: PContext, n: PNode): PNode =
result = newNodeI(nkEmpty, n.info)
@@ -585,9 +609,10 @@ proc makeRangeType*(c: PContext; first, last: BiggestInt;
var n = newNodeI(nkRange, info)
n.add newIntTypeNode(first, intType)
n.add newIntTypeNode(last, intType)
result = newTypeS(tyRange, c)
result.n = n
addSonSkipIntLit(result, intType, c.idgen) # basetype of range
var b = openType(c, tyRange)
b.setN n
b.add intType # basetype of range
result = finish b
proc isSelf*(t: PType): bool {.inline.} =
## Is this the magical 'Self' type from concepts?
@@ -597,8 +622,10 @@ proc makeTypeDesc*(c: PContext, typ: PType): PType =
if typ.kind == tyTypeDesc and not isSelf(typ):
result = typ
else:
result = newTypeS(tyTypeDesc, c, skipIntLit(typ, c.idgen))
incl result, tfCheckedForDestructor
var b = openType(c, tyTypeDesc)
b.addKeep skipIntLit(typ, c.idgen)
b.incl tfCheckedForDestructor
result = finish b
proc symFromType*(c: PContext; t: PType, info: TLineInfo): PSym =
if t.sym != nil: return t.sym

View File

@@ -44,6 +44,18 @@ proc reusePrev(prev: PType): bool {.inline.} =
# partial object marks sym as `sfForward`
(sfForward in prev.sym.flags or prev.sym.magic != mNone)))
proc openType(c: PContext; kind: TTypeKind; prev: PType): TypeBuilder =
## Prev-aware `openType`. This folds the identity decision into one place: for
## a forward-declared / partial `prev` the reserved *name* is kept and its
## *tree structure* is rebuilt in place; otherwise a fresh type (and identity)
## is minted -- at the same sequence point as `newTypeS`, so type ids stay
## byte-identical. Callers become the uniform "build structure, then `finish`".
if reusePrev(prev):
if prev.kind == tyForward: prev.kind = kind
result = reopen(prev, c.idgen)
else:
result = openType(c, kind)
proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext, son: sink PType): PType =
if reusePrev(prev):
result = prev
@@ -54,16 +66,15 @@ proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext, son: sink PType):
#if kind == tyError: result.flags.incl tfCheckedForDestructor
proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType =
if reusePrev(prev):
result = prev
if result.kind == tyForward: result.kind = kind
else:
result = newTypeS(kind, c)
# centralized on the `openType` chokepoint: keep the reserved name of a
# forward/partial `prev`, else mint a fresh identity.
finish openType(c, kind, prev)
proc newConstraint(c: PContext, k: TTypeKind): PType =
result = newTypeS(tyBuiltInTypeClass, c)
result.incl tfCheckedForDestructor
result.addSonSkipIntLit(newTypeS(k, c), c.idgen)
var b = openType(c, tyBuiltInTypeClass)
b.incl tfCheckedForDestructor
b.add newTypeS(k, c)
result = finish b
proc skipGenericPrev(prev: PType): PType =
result = prev
@@ -98,15 +109,19 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
counterSet = initPackedSet[BiggestInt]()
counter = 0
base = nil
result = newOrPrevType(tyEnum, prev, c)
result.n = newNodeI(nkEnumTy, n.info)
var b = openType(c, tyEnum, prev)
b.setN newNodeI(nkEnumTy, n.info)
checkMinSonsLen(n, 1, c.config)
if n[0].kind != nkEmpty:
base = semTypeNode(c, n[0][0], nil)
if base.kind != tyEnum:
localError(c.config, n[0].info, "inheritance only works with an enum")
counter = toInt64(lastOrd(c.config, base)) + 1
rawAddSon(result, base)
b.addRaw base
result = finish b
# the type each enum field belongs to; a field referring to its own enum is
# the canonical self-reference that will migrate to an id-based `TypePair.id`.
let self = typePair(result)
let isPure = result.sym != nil and sfPure in result.sym.flags
var symbols: TStrTable = initStrTable()
var hasNull = false
@@ -176,7 +191,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
elif counterSet.containsOrIncl(counter):
localError(c.config, n[i].info, errDuplicateAliasInEnumX % e.name.s)
e.typ = result
e.typ = self.decl
e.position = int(counter)
let symNode = newSymNode(e)
if identToReplace != nil and c.config.cmd notin cmdDocLike:
@@ -216,10 +231,11 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
setToStringProc(c.graph, result, genEnumToStrProc(result, n.info, c.graph, c.idgen))
proc semSet(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tySet, prev, c)
var b = openType(c, tySet, prev)
if n.len == 2 and n[1].kind != nkEmpty:
var base = semTypeNode(c, n[1], nil)
addSonSkipIntLit(result, base, c.idgen)
b.add base
result = finish b
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
if base.kind notin {tyGenericParam, tyGenericInvocation}:
if base.kind == tyForward:
@@ -230,45 +246,49 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
localError(c.config, n.info, errSetTooBig)
else:
localError(c.config, n.info, errXExpectsOneTypeParam % "set")
addSonSkipIntLit(result, errorType(c), c.idgen)
b.add errorType(c)
result = finish b
proc semContainerArg(c: PContext; n: PNode, kindStr: string; result: PType) =
proc semContainerArg(c: PContext; n: PNode, kindStr: string; b: var TypeBuilder) =
if n.len == 2:
var base = semTypeNode(c, n[1], nil)
if base.kind == tyVoid:
localError(c.config, n.info, errTIsNotAConcreteType % typeToString(base))
addSonSkipIntLit(result, base, c.idgen)
b.add base
else:
localError(c.config, n.info, errXExpectsOneTypeParam % kindStr)
addSonSkipIntLit(result, errorType(c), c.idgen)
b.add errorType(c)
proc semContainer(c: PContext, n: PNode, kind: TTypeKind, kindStr: string,
prev: PType): PType =
result = newOrPrevType(kind, prev, c)
semContainerArg(c, n, kindStr, result)
var b = openType(c, kind, prev)
semContainerArg(c, n, kindStr, b)
result = finish b
proc semVarargs(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tyVarargs, prev, c)
var b = openType(c, tyVarargs, prev)
if n.len == 2 or n.len == 3:
var base = semTypeNode(c, n[1], nil)
addSonSkipIntLit(result, base, c.idgen)
b.add base
if n.len == 3:
result.n = newIdentNode(considerQuotedIdent(c, n[2]), n[2].info)
b.setN newIdentNode(considerQuotedIdent(c, n[2]), n[2].info)
else:
localError(c.config, n.info, errXExpectsOneTypeParam % "varargs")
addSonSkipIntLit(result, errorType(c), c.idgen)
b.add errorType(c)
result = finish b
proc semVarOutType(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType =
if n.len == 1:
result = newOrPrevType(tyVar, prev, c)
result.flags = flags
var b = openType(c, tyVar, prev)
b.setFlags flags
var base = semTypeNode(c, n[0], nil)
if base.kind == tyTypeDesc and not isSelf(base):
base = base[0]
if base.kind == tyVar:
localError(c.config, n.info, "type 'var var' is not allowed")
base = base[0]
addSonSkipIntLit(result, base, c.idgen)
b.add base
result = finish b
else:
result = newConstraint(c, tyVar)
@@ -379,22 +399,23 @@ proc isRecursiveType*(t: PType): bool =
var cycleDetector = initIntSet()
isRecursiveType(t, cycleDetector)
proc addSonSkipIntLitChecked(c: PContext; father, son: PType; it: PNode, id: IdGenerator) =
let s = son.skipIntLit(id)
father.add(s)
proc addSonSkipIntLitChecked(c: PContext; b: var TypeBuilder; son: PType; it: PNode) =
let s = son.skipIntLit(c.idgen)
b.addKeep s
if isRecursiveType(s):
localError(c.config, it.info, "illegal recursion in type '" & typeToString(s) & "'")
else:
propagateToOwner(father, s)
b.propagateFrom s
proc semDistinct(c: PContext, n: PNode, prev: PType): PType =
if n.len == 0: return newConstraint(c, tyDistinct)
if prevIsKind(prev, tyDistinct):
# the symbol already has a distinct type (likely resem), don't create a new type
return skipGenericPrev(prev)
result = newOrPrevType(tyDistinct, prev, c)
addSonSkipIntLitChecked(c, result, semTypeNode(c, n[0], nil), n[0], c.idgen)
if n.len > 1: result.n = n[1]
var b = openType(c, tyDistinct, prev)
addSonSkipIntLitChecked(c, b, semTypeNode(c, n[0], nil), n[0])
if n.len > 1: b.setN n[1]
result = finish b
proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
assert isRange(n)
@@ -557,29 +578,33 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
# ensure we only construct a tyArray when there was no error (bug #3048):
# bug #6682: Do not propagate initialization requirements etc for the
# index type:
result = newOrPrevType(tyArray, prev, c, indx)
addSonSkipIntLit(result, base, c.idgen)
var b = openType(c, tyArray, prev)
b.addKeep indx
b.add base
result = finish b
else:
localError(c.config, n.info, errArrayExpectsTwoTypeParams)
result = newOrPrevType(tyError, prev, c)
proc semIterableType(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tyIterable, prev, c)
var b = openType(c, tyIterable, prev)
if n.len == 2:
let base = semTypeNode(c, n[1], nil)
addSonSkipIntLit(result, base, c.idgen)
b.add base
result = finish b
else:
localError(c.config, n.info, errXExpectsOneTypeParam % "iterable")
result = newOrPrevType(tyError, prev, c)
proc semOrdinal(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tyOrdinal, prev, c)
var b = openType(c, tyOrdinal, prev)
if n.len == 2:
var base = semTypeNode(c, n[1], nil)
if base.kind != tyGenericParam:
if not isOrdinalType(base):
localError(c.config, n[1].info, errOrdinalTypeExpected % typeToString(base, preferDesc))
addSonSkipIntLit(result, base, c.idgen)
b.add base
result = finish b
else:
localError(c.config, n.info, errXExpectsOneTypeParam % "ordinal")
result = newOrPrevType(tyError, prev, c)
@@ -587,10 +612,11 @@ proc semOrdinal(c: PContext, n: PNode, prev: PType): PType =
proc semAnonTuple(c: PContext, n: PNode, prev: PType): PType =
if n.len == 0:
localError(c.config, n.info, errTypeExpected)
result = newOrPrevType(tyTuple, prev, c)
var b = openType(c, tyTuple, prev)
for it in n:
let t = semTypeNode(c, it, nil)
addSonSkipIntLitChecked(c, result, t, it, c.idgen)
addSonSkipIntLitChecked(c, b, t, it)
result = finish b
proc firstRange(config: ConfigRef, t: PType): PNode =
if t.skipModifier().kind in tyFloat..tyFloat64:
@@ -1163,7 +1189,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
t = t.base
if t.kind == tyVoid:
localError(c.config, n.info, "type '$1 void' is not allowed" % kind.toHumanStr)
result = newOrPrevType(kind, prev, c)
var b = openType(c, kind, prev)
var isNilable = false
var wrapperKind = tyNone
# check every except the last is an object:
@@ -1179,23 +1205,26 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
elif region.skipTypes({tyGenericInst, tyAlias, tySink}).kind notin {
tyError, tyObject}:
message c.config, n[i].info, errGenerated, "region needs to be an object type"
addSonSkipIntLit(result, region, c.idgen)
b.add region
else:
message(c.config, n.info, warnDeprecated, "region for pointer types is deprecated")
addSonSkipIntLit(result, region, c.idgen)
addSonSkipIntLit(result, t, c.idgen)
b.add region
b.add t
result = finish b
if tfPartial in result.flags:
if result.elementType.kind == tyObject: incl(result.elementType, tfPartial)
# if not isNilable: result.flags.incl tfNotNil
case wrapperKind
of tyOwned:
if optOwnedRefs in c.config.globalOptions:
let t = newTypeS(tyOwned, c, result)
t.incl tfHasOwned
result = t
var wrap = openType(c, tyOwned)
wrap.addKeep result
wrap.incl tfHasOwned
result = finish wrap
of tySink:
let t = newTypeS(tySink, c, result)
result = t
var wrap = openType(c, tySink)
wrap.addKeep result
result = finish wrap
else: discard
if result.kind == tyRef and
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc} and
@@ -1299,7 +1328,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
let base = (if lifted != nil: lifted else: paramType.base)
if base.isMetaType and procKind == skMacro:
localError(c.config, info, errMacroBodyDependsOnGenericTypes % paramName)
result = addImplicitGeneric(c, newTypeS(tyStatic, c, base),
var b = openType(c, tyStatic)
b.addKeep base
result = addImplicitGeneric(c, finish b,
paramTypId, info, genericParams, paramName)
if result != nil: result.incl({tfHasStatic, tfUnresolved})
@@ -1311,9 +1342,10 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
paramTypId.id == getIdent(c.cache, "type").id):
# XXX Why doesn't this check for tyTypeDesc instead?
paramTypId = nil
let t = newTypeS(tyTypeDesc, c, paramType.base)
incl t, tfCheckedForDestructor
result = addImplicitGeneric(c, t, paramTypId, info, genericParams, paramName)
var b = openType(c, tyTypeDesc)
b.addKeep paramType.base
b.incl tfCheckedForDestructor
result = addImplicitGeneric(c, finish b, paramTypId, info, genericParams, paramName)
else:
result = nil
of tyDistinct:
@@ -1342,9 +1374,12 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
# Maybe there is another better place to associate
# the seq type class with the seq identifier.
if paramType.kind == tySequence and paramType.elementType.kind == tyNone:
let typ = newTypeS(tyBuiltInTypeClass, c,
newTypeS(paramType.kind, c))
result = addImplicitGeneric(c, typ, paramTypId, info, genericParams, paramName)
# allocate the inner son first so the type-id order matches the old
# argument-evaluation order (inner before outer).
let inner = newTypeS(paramType.kind, c)
var b = openType(c, tyBuiltInTypeClass)
b.addKeep inner
result = addImplicitGeneric(c, finish b, paramTypId, info, genericParams, paramName)
else:
result = nil
for i in 0..<paramType.len:
@@ -1553,8 +1588,11 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
# which will prevent other types from matching - clearly a very
# surprising behavior. We must instead fix the expected type of
# the proc to be the unbound typedesc type:
typ = newTypeS(tyTypeDesc, c, newTypeS(tyNone, c))
typ.incl tfCheckedForDestructor
let none = newTypeS(tyNone, c)
var b = openType(c, tyTypeDesc)
b.addKeep none
b.incl tfCheckedForDestructor
typ = finish b
elif def.typ != nil and def.typ.kind != tyFromExpr: # def.typ can be void
# if def.typ != nil and def.typ.kind != tyNone:
@@ -1870,9 +1908,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType =
if prev != nil and (prev.kind == tyGenericBody or
typeExpr.kind in {tyObject, tyEnum, tyDistinct, tyForward, tyGenericBody}):
result = newTypeS(tyAlias, c)
result.rawAddSon typeExpr
result.sym = prev.sym
var b = openType(c, tyAlias)
b.addRaw typeExpr
b.setSym prev.sym
result = finish b
if prev.kind != tyGenericBody:
assignType(prev, result)
else:
@@ -1880,9 +1919,10 @@ proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType =
proc fixupTypeOf(c: PContext, prev: PType, typ: PType) =
if prev != nil:
let result = newTypeS(tyAlias, c)
result.rawAddSon typ
result.sym = prev.sym
var b = openType(c, tyAlias)
b.addRaw typ
b.setSym prev.sym
let result = finish b
if prev.kind != tyGenericBody:
assignType(prev, result)
@@ -1950,7 +1990,9 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
inherited = n[2]
var owner = getCurrOwner(c)
var candidateTypeSlot = newTypeS(tyAlias, c, c.errorType)
var slotB = openType(c, tyAlias)
slotB.addKeep c.errorType
var candidateTypeSlot = finish slotB
result = newOrPrevType(tyUserTypeClass, prev, c, son = candidateTypeSlot)
result.incl tfCheckedForDestructor
result.n = n
@@ -2070,10 +2112,11 @@ proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym =
result = errorSym(c, n)
proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType =
result = newOrPrevType(tyStatic, prev, c)
var b = openType(c, tyStatic, prev)
var base = semTypeNode(c, childNode, nil).skipTypes({tyTypeDesc, tyAlias})
result.rawAddSon(base)
result.incl tfHasStatic
b.addRaw base
b.incl tfHasStatic
result = finish b
proc semTypeOfImpl(c: PContext; n: PNode): PNode =
var m = BiggestInt 1 # typeOfIter
@@ -2401,16 +2444,18 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
result.rawAddSon(semTypeNode(c, n[i], nil))
of mDistinct:
checkSonsLen(n, 2, c.config)
result = newOrPrevType(tyDistinct, prev, c)
addSonSkipIntLit(result, semTypeNode(c, n[1], nil), c.idgen)
var b = openType(c, tyDistinct, prev)
b.add semTypeNode(c, n[1], nil)
result = finish b
of mVar:
checkSonsLen(n, 2, c.config)
result = newOrPrevType(tyVar, prev, c)
var b = openType(c, tyVar, prev)
var base = semTypeNode(c, n[1], nil)
if base.kind in {tyVar, tyLent}:
localError(c.config, n.info, "type 'var var' is not allowed")
base = base[0]
addSonSkipIntLit(result, base, c.idgen)
b.add base
result = finish b
of mRef: result = semAnyRef(c, n, tyRef, prev)
of mPtr: result = semAnyRef(c, n, tyPtr, prev)
of mTuple: result = semTuple(c, n, prev)
@@ -2506,7 +2551,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
of nkProcTy, nkIteratorTy:
if n.len == 0 or n[0].kind == nkEmpty:
# 0 length or empty param list with possible pragmas imply typeclass
result = newTypeS(tyBuiltInTypeClass, c)
var b = openType(c, tyBuiltInTypeClass)
let child = newTypeS(tyProc, c)
if n.kind == nkIteratorTy:
child.incl tfIterator
@@ -2518,7 +2563,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
s.typ = child
# for now only call convention pragmas supported in proc typeclass
pragma(c, s, n[1], {FirstCallConv..LastCallConv})
result.addSonSkipIntLit(child, c.idgen)
b.add child
result = finish b
else:
let symKind = if n.kind == nkIteratorTy: skIterator else: skProc
result = semProcTypeWithScope(c, n, prev, symKind)
@@ -2647,7 +2693,9 @@ proc processMagicType(c: PContext, m: PSym) =
else: localError(c.config, m.info, errTypeExpected)
proc semGenericConstraints(c: PContext, x: PType): PType =
result = newTypeS(tyGenericParam, c, x)
var b = openType(c, tyGenericParam)
b.addKeep x
result = finish b
proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
@@ -2674,8 +2722,11 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
if typ.kind != tyStatic or typ.len == 0:
if typ.kind == tyTypeDesc:
if typ.elementType.kind == tyNone:
typ = newTypeS(tyTypeDesc, c, newTypeS(tyNone, c))
incl typ, tfCheckedForDestructor
let none = newTypeS(tyNone, c)
var b = openType(c, tyTypeDesc)
b.addKeep none
b.incl tfCheckedForDestructor
typ = finish b
else:
typ = semGenericConstraints(c, typ)
@@ -2683,7 +2734,9 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
def = semConstExpr(c, def)
if typ == nil:
if def.typ.kind != tyTypeDesc:
typ = newTypeS(tyStatic, c, def.typ)
var b = openType(c, tyStatic)
b.addKeep def.typ
typ = finish b
else:
# the following line fixes ``TV2*[T:SomeNumber=TR] = array[0..1, T]``
# from manyloc/named_argument_bug/triengine:

133
compiler/typebuilders.nim Normal file
View File

@@ -0,0 +1,133 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## `TypeBuilder`: a small, TokenBuf-shaped surface for constructing a `PType`.
##
## The compiler builds types by creating a mutable `TType` and then poking its
## sons/flags/`n` field into place at various call sites. This module funnels
## that construction through a builder object instead:
##
## ```nim
## var b = openType(kind, idgen, owner) # or openType(c, kind) with a PContext
## b.incl someFlag
## b.add someSon # skip int-lit + propagate flags
## b.setN someNode
## result = finish b
## ```
##
## Today the backing store is a mutable `TType`, so `finish` simply returns it
## and there is no runtime cost. The point of routing construction through the
## builder is that the backing store can later become a nifcore `TokenBuf` --
## with `openType` becoming `openTag`, the `add*` family becoming token/subtree
## appends, and `finish` becoming `beginRead` yielding a read-only cursor --
## *without touching any call site*.
##
## Contract: fully configure the type between `openType` and `finish`, and treat
## the `finish` result as immutable. Types that need their identity before their
## body is complete (recursive / deferred object types, e.g. `semEnum` and
## object bodies) are out of scope and keep using the direct `newType` /
## `rawAddSon` API for now; they will be modeled via symbol indirection later.
import ast
from itemids import ItemId
type
SymId* = ItemId
## The stable identity of a type. Today an `ItemId`; this alias marks every
## place that will migrate to a content-based nifcore `SymId` later (once
## stable, content-derived names land -- so generic instances dedup across
## modules and processes). Keeping the alias means that migration is a
## one-line change here rather than a churn across call sites.
TypePair* = object
## A type as an (identity, tree) pair: `id` names it, `decl` is its tree.
## Today `decl.itemId == id`, so the pair is a thin, forward-looking handle
## -- the handle a *named* type's body uses to refer to itself (owner /
## recursive references). Its real payoff arrives when `decl` becomes a
## nameless `NifCursor` and those self/forward references go through `id`.
id*: SymId
decl*: PType
proc typePair*(t: PType): TypePair {.inline.} =
TypePair(id: t.itemId, decl: t)
proc skipIntLit*(t: PType; id: IdGenerator): PType {.inline.} =
if t.n != nil and t.kind in {tyInt, tyFloat}:
result = copyType(t, id, t.owner)
result.n = nil
else:
result = t
proc addSonSkipIntLit*(father, son: PType; id: IdGenerator) =
let s = son.skipIntLit(id)
father.add(s)
propagateToOwner(father, s)
type
TypeBuilder* = object
t: PType
idgen {.cursor.}: IdGenerator
## non-owning: the id generator outlives every builder (it lives for the
## whole compilation), so it must not be reference-counted here.
proc openType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym): TypeBuilder {.inline.} =
## Begins a fresh type of the given `kind`. Mirrors `newType`.
TypeBuilder(t: newType(kind, idgen, owner), idgen: idgen)
proc add*(b: var TypeBuilder; son: PType) {.inline.} =
## Adds a son, skipping an int-literal wrapper and propagating type flags to
## the owner. Mirrors `addSonSkipIntLit` -- the common case.
addSonSkipIntLit(b.t, son, b.idgen)
proc addRaw*(b: var TypeBuilder; son: PType; propagateHasAsgn = true) {.inline.} =
## Adds a son verbatim (no int-lit skip) but still propagates type flags.
## Mirrors `rawAddSon`.
rawAddSon(b.t, son, propagateHasAsgn)
proc addKeep*(b: var TypeBuilder; son: PType) {.inline.} =
## Adds a son verbatim: no int-lit skip and no flag propagation. Mirrors the
## `newType(..., son = x)` fast path -- including that a nil son is skipped
## (produces a childless type) rather than added.
if son != nil: b.t.add son
proc reopen*(t: PType; idgen: IdGenerator): TypeBuilder {.inline.} =
## Continues building an *existing* type in place, preserving its identity
## (`itemId`). Used to bind a freshly-built structure onto the reserved name
## of a forward-declared / partial type -- the "distinguish name from tree"
## case: the name (`t`) stays, only its tree structure is (re)built.
TypeBuilder(t: t, idgen: idgen)
proc setN*(b: var TypeBuilder; n: PNode) {.inline.} =
b.t.n = n
proc setFlags*(b: var TypeBuilder; flags: TTypeFlags) {.inline.} =
## Replaces the whole flag set (assignment, not union). Mirrors `t.flags = x`.
b.t.flags = flags
proc incl*(b: var TypeBuilder; flag: TTypeFlag) {.inline.} =
b.t.incl flag
proc incl*(b: var TypeBuilder; flags: TTypeFlags) {.inline.} =
b.t.incl flags
proc propagateFrom*(b: var TypeBuilder; son: PType; propagateHasAsgn = true) {.inline.} =
## Propagates a son type's properties (flags, owner) into the type under
## construction. Mirrors a bare `propagateToOwner(result, son)`.
propagateToOwner(b.t, son, propagateHasAsgn)
proc setCallConv*(b: var TypeBuilder; cc: TCallingConvention) {.inline.} =
b.t.callConv = cc
proc setSym*(b: var TypeBuilder; s: PSym) {.inline.} =
b.t.sym = s
template finish*(b: TypeBuilder): PType =
## Hands out the constructed type. A template so it collapses to a bare field
## read with no call/move/destroy overhead over the old direct construction.
## Later this becomes `beginRead`, yielding a read-only cursor.
b.t