Files
Nim/compiler/ast.nim
araq 2447dfdc7d 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
2026-08-29 09:05:44 +02:00

1804 lines
58 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# abstract syntax tree + symbol table
import
lineinfos, options, idents, int128, wordrecg
import std/[tables, hashes]
from std/strutils import toLowerAscii
when defined(nimPreviewSlimSystem):
import std/assertions
export int128
import nodekinds
export nodekinds
import astdef
export astdef
when not defined(nimKochBootstrap):
import ast2nif
when not defined(nimKochBootstrap):
var program* {.threadvar.}: DecodeContext
proc setupProgram*(config: ConfigRef; cache: IdentCache) =
when not defined(nimKochBootstrap):
program = createDecodeContext(config, cache)
proc setIcMainModule*(fileIdx: FileIndex) =
## Tells the IC loader which module is being compiled fresh, so that
## re-exports of that module's symbols by dependencies are not loaded as
## duplicate stubs.
when not defined(nimKochBootstrap):
ast2nif.setMainModule(program, fileIdx)
template loadSym(s: PSym) =
## Loads a symbol from NIF file if it's in Partial state.
when not defined(nimKochBootstrap):
ast2nif.loadSym(program, s)
template loadType(t: PType) =
## Loads a type from NIF file if it's in Partial state.
when not defined(nimKochBootstrap):
ast2nif.loadType(program, t)
proc loadSymCallback*(s: PSym) {.nimcall.} =
loadSym(s)
proc loadTypeCallback*(t: PType) {.nimcall.} =
loadType(t)
proc ensureMutable*(s: PSym) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
proc ensureMutable*(t: PType) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
proc backendEnsureMutable*(s: PSym) {.inline.} =
#assert s.state != Sealed
# ^ IC review this later
if s.state == Partial: loadSym(s)
proc backendEnsureMutable*(t: PType) {.inline.} =
#assert t.state != Sealed
# ^ IC review this later
if t.state == Partial: loadType(t)
proc unsealForTransform*(t: PType) {.inline.} =
## The transformer/lambda lifting also run inside `nim m` when the VM
## compiles a LOADED routine (macro evaluation, `getImpl`). Their mutations
## are process-local — transformed bodies are never written back to a NIF —
## so downgrade the loaded type to mutable, mirroring the `cmdNifC` loader
## which loads everything `Complete` for exactly this reason (see
## `ast2nif.loadedState`).
if t.state == Partial: loadType(t)
if t.state == Sealed: t.state = Complete
proc owner*(s: PSym): lent PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.ownerFieldImpl
proc owner*(s: PType): lent PSym {.inline.} =
if s.state == Partial: loadType(s)
result = s.ownerFieldImpl
proc setOwner*(s: PSym; owner: PSym) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.ownerFieldImpl = owner
proc setOwner*(s: PType; owner: PSym) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadType(s)
s.ownerFieldImpl = owner
proc kind*(s: PSym): TSymKind {.inline.} =
if s.state == Partial: loadSym(s)
result = s.kindImpl
proc `kind=`*(s: PSym, val: TSymKind) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.kindImpl = val
proc gcUnsafetyReason*(s: PSym): lent PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.gcUnsafetyReasonImpl
proc `gcUnsafetyReason=`*(s: PSym, val: PSym) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.gcUnsafetyReasonImpl = val
proc transformedBody*(s: PSym): lent PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.transformedBodyImpl
proc `transformedBody=`*(s: PSym, val: PNode) {.inline.} =
#assert s.state != Sealed
# Make an exception here for this misfeature...
if s.state == Partial: loadSym(s)
s.transformedBodyImpl = val
proc guard*(s: PSym): lent PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.guardImpl
proc `guard=`*(s: PSym, val: PSym) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.guardImpl = val
proc bitsize*(s: PSym): int {.inline.} =
if s.state == Partial: loadSym(s)
result = s.bitsizeImpl
proc `bitsize=`*(s: PSym, val: int) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.bitsizeImpl = val
proc alignment*(s: PSym): int {.inline.} =
if s.state == Partial: loadSym(s)
result = s.alignmentImpl
proc `alignment=`*(s: PSym, val: int) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.alignmentImpl = val
proc magic*(s: PSym): TMagic {.inline.} =
if s.state == Partial: loadSym(s)
result = s.magicImpl
proc `magic=`*(s: PSym, val: TMagic) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.magicImpl = val
proc typ*(s: PSym): lent PType {.inline.} =
if s.state == Partial: loadSym(s)
result = s.typImpl
proc `typ=`*(s: PSym, val: PType) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.typImpl = val
proc info*(s: PSym): TLineInfo {.inline.} =
if s.state == Partial: loadSym(s)
result = s.infoImpl
proc `info=`*(s: PSym, val: TLineInfo) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.infoImpl = val
when defined(nimsuggest):
proc endInfo*(s: PSym): TLineInfo {.inline.} =
if s.state == Partial: loadSym(s)
result = s.endInfoImpl
proc `endInfo=`*(s: PSym, val: TLineInfo) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.endInfoImpl = val
proc hasUserSpecifiedType*(s: PSym): bool {.inline.} =
if s.state == Partial: loadSym(s)
result = s.hasUserSpecifiedTypeImpl
proc `hasUserSpecifiedType=`*(s: PSym, val: bool) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.hasUserSpecifiedTypeImpl = val
proc flags*(s: PSym): TSymFlags {.inline.} =
if s.state == Partial: loadSym(s)
result = s.flagsImpl
proc `flags=`*(s: PSym, val: TSymFlags) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.flagsImpl = val
proc ast*(s: PSym): lent PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.astImpl
proc `ast=`*(s: PSym, val: PNode) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.astImpl = val
proc options*(s: PSym): TOptions {.inline.} =
if s.state == Partial: loadSym(s)
result = s.optionsImpl
proc `options=`*(s: PSym, val: TOptions) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.optionsImpl = val
proc position*(s: PSym): int {.inline.} =
if s.state == Partial: loadSym(s)
result = s.positionImpl
proc `position=`*(s: PSym, val: int) {.inline.} =
# No `Sealed` guard: the VM reuses `position` as a register slot while compiling
# a macro for execution (see `vmgen.genGenericParams`), which under IC may be a
# macro loaded from a NIF file. The macro is run, not code-generated, so this
# scratch mutation is harmless.
if s.state == Partial: loadSym(s)
s.positionImpl = val
proc offset*(s: PSym): int32 {.inline.} =
if s.state == Partial: loadSym(s)
result = s.offsetImpl
proc `offset=`*(s: PSym, val: int32) {.inline.} =
#assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.offsetImpl = val
proc loc*(s: PSym): TLoc {.inline.} =
if s.state == Partial: loadSym(s)
result = s.locImpl
proc `loc=`*(s: PSym, val: TLoc) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.locImpl = val
proc annex*(s: PSym): lent PLib {.inline.} =
if s.state == Partial: loadSym(s)
result = s.annexImpl
proc `annex=`*(s: PSym, val: PLib) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.annexImpl = val
when hasFFI:
proc cname*(s: PSym): string {.inline.} =
if s.state == Partial: loadSym(s)
result = s.cnameImpl
proc `cname=`*(s: PSym, val: string) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.cnameImpl = val
proc constraint*(s: PSym): lent PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.constraintImpl
proc `constraint=`*(s: PSym, val: PNode) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.constraintImpl = val
proc instantiatedFrom*(s: PSym): lent PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.instantiatedFromImpl
proc `instantiatedFrom=`*(s: PSym, val: PSym) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.instantiatedFromImpl = val
proc setSnippet*(s: PSym; val: sink string) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.locImpl.snippet = val
proc incl*(s: PSym; flag: TSymFlag) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.flagsImpl.incl(flag)
proc incl*(s: PSym; flags: set[TSymFlag]) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.flagsImpl.incl(flags)
proc incl*(s: PSym; flag: TLocFlag) {.inline.} =
#assert s.state != Sealed
# locImpl is a backend field so do not protect it against mutations
if s.state == Partial: loadSym(s)
s.locImpl.flags.incl(flag)
proc excl*(s: PSym; flag: TSymFlag) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.flagsImpl.excl(flag)
when defined(nimsuggest):
proc allUsages*(s: PSym): var seq[TLineInfo] {.inline.} =
if s.state == Partial: loadSym(s)
result = s.allUsagesImpl
proc `allUsages=`*(s: PSym, val: sink seq[TLineInfo]) {.inline.} =
# No `assert s.state != Sealed`: `allUsagesImpl` is nimsuggest-only usage
# tracking, NOT part of the NIF-serialized symbol. nimsuggest loads symbols
# as `Sealed` (ast2nif.loadedState under cmdM) yet `suggestSym` legitimately
# records usages on them; the getter likewise doesn't assert.
if s.state == Partial: loadSym(s)
s.allUsagesImpl = val
# Accessor procs for TType fields
proc callConv*(t: PType): TCallingConvention {.inline.} =
if t.state == Partial: loadType(t)
result = t.callConvImpl
proc `callConv=`*(t: PType, val: TCallingConvention) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.callConvImpl = val
proc flags*(t: PType): TTypeFlags {.inline.} =
if t.state == Partial: loadType(t)
result = t.flagsImpl
proc `flags=`*(t: PType, val: TTypeFlags) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
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
proc `sons=`*(t: PType, val: sink TTypeSeq) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.sonsImpl = val
proc n*(t: PType): lent PNode {.inline.} =
if t.state == Partial: loadType(t)
result = t.nImpl
proc `n=`*(t: PType, val: PNode) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.nImpl = val
proc sym*(t: PType): lent PSym {.inline.} =
if t.state == Partial: loadType(t)
result = t.symImpl
proc `sym=`*(t: PType, val: PSym) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.symImpl = val
proc size*(t: PType): BiggestInt {.inline.} =
if t.state == Partial: loadType(t)
result = t.sizeImpl
proc `size=`*(t: PType, val: BiggestInt) {.inline.} =
backendEnsureMutable t
t.sizeImpl = val
proc align*(t: PType): int16 {.inline.} =
if t.state == Partial: loadType(t)
result = t.alignImpl
proc `align=`*(t: PType, val: int16) {.inline.} =
backendEnsureMutable t
t.alignImpl = val
proc paddingAtEnd*(t: PType): int16 {.inline.} =
if t.state == Partial: loadType(t)
result = t.paddingAtEndImpl
proc `paddingAtEnd=`*(t: PType, val: int16) {.inline.} =
backendEnsureMutable t
t.paddingAtEndImpl = val
proc loc*(t: PType): TLoc {.inline.} =
if t.state == Partial: loadType(t)
result = t.locImpl
proc `loc=`*(t: PType, val: TLoc) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.locImpl = val
proc typeInst*(t: PType): lent PType {.inline.} =
if t.state == Partial: loadType(t)
result = t.typeInstImpl
proc `typeInst=`*(t: PType, val: PType) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.typeInstImpl = val
proc incl*(t: PType; flag: TTypeFlag) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.flagsImpl.incl(flag)
proc incl*(t: PType; flags: set[TTypeFlag]) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.flagsImpl.incl(flags)
proc excl*(t: PType; flag: TTypeFlag) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.flagsImpl.excl(flag)
proc excl*(t: PType; flags: set[TTypeFlag]) {.inline.} =
assert t.state != Sealed
if t.state == Partial: loadType(t)
t.flagsImpl.excl(flags)
proc typ*(n: PNode): lent PType {.inline.} =
result = n.typField
if result == nil and nfLazyType in n.flags:
result = n.sym.typ
proc `typ=`*(n: PNode, val: sink PType) {.inline.} =
n.typField = val
template nodeId(n: PNode): int = cast[int](n)
type Gconfig = object
# we put comments in a side channel to avoid increasing `sizeof(TNode)`, which
# reduces memory usage given that `PNode` is the most allocated type by far.
comments: Table[int, string] # nodeId => comment
useIc*: bool
var gconfig {.threadvar.}: Gconfig
proc setUseIc*(useIc: bool) = gconfig.useIc = useIc
proc comment*(n: PNode): string =
if nfHasComment in n.flags:
# NIF-based IC doesn't serialize comments, but the comment table is keyed by
# the node's address (`nodeId`), which is unique among live nodes; a loaded
# node that carries `nfHasComment` simply has no entry here (its comment was
# set in another process), so `getOrDefault` safely returns "" for it while
# in-process VM macro nodes (e.g. newCommentStmtNode) still round-trip.
result = gconfig.comments.getOrDefault(n.nodeId)
else:
result = ""
nodeCommentReader = proc(n: PNode): string {.nimcall.} = comment(n)
proc `comment=`*(n: PNode, a: string) =
let id = n.nodeId
if a.len > 0:
# if needed, we could periodically cleanup gconfig.comments when its size increases,
# to ensure only live nodes (and with nfHasComment) have an entry in gconfig.comments;
# for compiling compiler, the waste is very small:
# num calls to newNodeImpl: 14984160 (num of PNode allocations)
# size of gconfig.comments: 33585
# num of nodes with comments that were deleted and hence wasted: 3081
n.flags.incl nfHasComment
gconfig.comments[id] = a
elif nfHasComment in n.flags:
n.flags.excl nfHasComment
gconfig.comments.del(id)
nodeCommentWriter = proc(n: PNode; s: string) {.nimcall.} = n.comment = s
# BUGFIX: a module is overloadable so that a proc can have the
# same name as an imported module. This is necessary because of
# the poor naming choices in the standard library.
proc getPIdent*(a: PNode): PIdent {.inline.} =
## Returns underlying `PIdent` for `{nkSym, nkIdent}`, or `nil`.
case a.kind
of nkSym: a.sym.name
of nkIdent: a.ident
of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: a.sons[0].sym.name
else: nil
template id*(a: PSym): int = toId(a.itemId)
template id*(a: PType): int = toId(a.bindingId)
type
IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here.
module*: int32
symId*: int32
typeId*: int32
sealed*: bool
backendMinted*: bool
disambTable*: CountTable[PIdent]
proc idGeneratorFromModule*(m: PSym): IdGenerator =
assert m.kind == skModule
result = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0, disambTable: initCountTable[PIdent]())
result.disambTable.inc m.name
proc idGeneratorForBackend*(m: PSym): IdGenerator =
## Like `idGeneratorFromModule`, but for IC codegen (`nim nifc`): symbols and
## types minted fresh during codegen (transf labels/temps, lifted hooks, type
## copies) must not collide with the itemIds the NIF loader synthesizes for
## lazily-loaded symbols/types of the same module — those come from a
## per-module load-order counter that keeps running while codegen mints its
## own ids. A collision corrupts itemId-keyed tables, e.g. `transf`'s inline
## iterator mapping then substitutes a random loaded sym (a call's callee)
## with a `:tmp` block label. Backend-minted ids carry a marker bit in the
## module half (see `itemids.backendItemId`), so the two id spaces are
## disjoint by construction.
assert m.kind == skModule
result = IdGenerator(module: m.itemId.module, symId: 0, typeId: 0,
backendMinted: true, disambTable: initCountTable[PIdent]())
result.disambTable.inc m.name
proc idGeneratorForPackage*(nextIdWillBe: int32): IdGenerator =
result = IdGenerator(module: PackageModuleId, symId: nextIdWillBe - 1'i32, typeId: 0, disambTable: initCountTable[PIdent]())
proc nextSymId(x: IdGenerator): ItemId {.inline.} =
assert(not x.sealed)
when not defined(nimKochBootstrap):
if x.backendMinted:
# Share the loader's per-module backend counter so a freshly-minted
# backend sym never collides with an `@bk` sym loaded from the module's
# `.t.bif` (see ast2nif.nextBackendSymItem).
let it = nextBackendSymItem(program, x.module)
if it >= 0'i32:
return backendItemId(x.module, it)
inc x.symId
result = if x.backendMinted: backendItemId(x.module, x.symId)
else: itemId(x.module, x.symId)
proc nextTypeId*(x: IdGenerator): ItemId {.inline.} =
assert(not x.sealed)
when not defined(nimKochBootstrap):
if x.backendMinted:
# Share the loader's per-module backend TYPE counter (seeded from the
# module's `(unusedid)`) so a freshly-minted backend type sits ABOVE every
# loaded type — never colliding with a frontend type's `toId` (the bug that
# crashed cgen's `getTypeDescAux` cycle check on `AsyncBufferRef`). Mirrors
# `nextSymId` (see ast2nif.nextBackendTypeItem).
let it = nextBackendTypeItem(program, x.module)
if it >= 0'i32:
return backendItemId(x.module, it)
inc x.typeId
result = if x.backendMinted: backendItemId(x.module, x.typeId)
else: itemId(x.module, x.typeId)
when false:
proc nextId*(x: IdGenerator): ItemId {.inline.} =
inc x.item
result = x[]
when false:
proc storeBack*(dest: var IdGenerator; src: IdGenerator) {.inline.} =
assert dest.ItemId.module == src.ItemId.module
if dest.ItemId.item > src.ItemId.item:
echo dest.ItemId.item, " ", src.ItemId.item, " ", src.ItemId.module
assert dest.ItemId.item <= src.ItemId.item
dest = src
var ggDebug* {.deprecated.}: bool ## convenience switch for trying out things
proc isCallExpr*(n: PNode): bool =
result = n.kind in nkCallKinds
proc discardSons*(father: PNode)
proc safeArrLen*(n: PNode): int {.inline.} =
## works for array-like objects (strings passed as openArray in VM).
if n.kind in {nkStrLit..nkTripleStrLit}: result = n.strVal.len
elif n.kind in {nkNone..nkFloat128Lit}: result = 0
else: result = n.len
proc add*(father, son: PNode) =
assert son != nil
father.sons.add(son)
proc addAllowNil*(father, son: PNode) {.inline.} =
father.sons.add(son)
proc add*(father, son: PType) =
ensureMutable father
assert father.kind != tyProc or father.sonsImpl.len == 0
assert son != nil
father.sonsImpl.add son
proc addAllowNil*(father, son: PType) {.inline.} =
ensureMutable father
assert father.kind != tyProc or father.sonsImpl.len == 0
father.sonsImpl.add son
proc `[]`*(n: PType, i: int): PType {.inline.} =
if n.state == Partial: loadType(n)
if n.kind == tyProc and i > 0:
assert n.nImpl[i] != nil and n.nImpl[i].sym != nil
n.nImpl[i].sym.typ
else:
n.sonsImpl[i]
proc `[]=`*(n: PType, i: int; x: PType) {.inline.} =
if n.state == Partial: loadType(n)
if n.kind == tyProc and i > 0:
assert n.nImpl[i] != nil and n.nImpl[i].sym != nil
n.nImpl[i].sym.typ = x
else:
n.sonsImpl[i] = x
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.} =
if n.state == Partial: loadType(n)
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.
## Currently only supports routineDefs + {nkTypeDef}.
case n.kind
of routineDefs:
if n[pragmasPos].kind != nkEmpty: result = n[pragmasPos]
else: result = nil
of nkTypeDef:
#[
type F3*{.deprecated: "x3".} = int
TypeSection
TypeDef
PragmaExpr
Postfix
Ident "*"
Ident "F3"
Pragma
ExprColonExpr
Ident "deprecated"
StrLit "x3"
Empty
Ident "int"
]#
if n[0].kind == nkPragmaExpr:
result = n[0][1]
else:
result = nil
else:
# support as needed for `nkIdentDefs` etc.
result = nil
if result != nil:
assert result.kind == nkPragma, $(result.kind, n.kind)
proc extractPragma*(s: PSym): PNode =
## gets the pragma node of routine/type/var/let/const symbol `s`
if s.kind in routineKinds: # bug #24167
let astVal = s.ast
if astVal != nil and astVal[pragmasPos] != nil and astVal[pragmasPos].kind != nkEmpty:
result = astVal[pragmasPos]
else:
result = nil
elif s.kind in {skType, skVar, skLet, skConst}:
let astVal = s.ast
if astVal != nil and astVal.len > 0:
if astVal[0].kind == nkPragmaExpr and astVal[0].len > 1:
# s.ast = nkTypedef / nkPragmaExpr / [nkSym, nkPragma]
result = astVal[0][1]
else:
result = nil
else:
result = nil
else:
result = nil
assert result == nil or result.kind == nkPragma
proc skipPragmaExpr*(n: PNode): PNode =
## if pragma expr, give the node the pragmas are applied to,
## otherwise give node itself
if n.kind == nkPragmaExpr:
result = n[0]
else:
result = n
proc setInfoRecursive*(n: PNode, info: TLineInfo) =
## set line info recursively
if n != nil:
for i in 0..<n.safeLen: setInfoRecursive(n[i], info)
n.info = info
proc newAtom*(ident: PIdent, info: TLineInfo): PNode =
result = newNode(nkIdent, info)
result.ident = ident
proc newAtom*(kind: TNodeKind, intVal: BiggestInt, info: TLineInfo): PNode =
result = newNode(kind, info)
result.intVal = intVal
proc newAtom*(kind: TNodeKind, floatVal: BiggestFloat, info: TLineInfo): PNode =
result = newNode(kind, info)
result.floatVal = floatVal
proc newAtom*(kind: TNodeKind; strVal: sink string; info: TLineInfo): PNode =
result = newNode(kind, info)
result.strVal = strVal
proc newTree*(kind: TNodeKind; info: TLineInfo; children: varargs[PNode]): PNode =
result = newNodeI(kind, info)
if children.len > 0:
result.info = children[0].info
result.sons = @children
proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode =
result = newNode(kind)
if children.len > 0:
result.info = children[0].info
result.sons = @children
proc newTreeI*(kind: TNodeKind; info: TLineInfo; children: varargs[PNode]): PNode =
result = newNodeI(kind, info)
if children.len > 0:
result.info = children[0].info
result.sons = @children
proc newTreeIT*(kind: TNodeKind; info: TLineInfo; typ: PType; children: varargs[PNode]): PNode =
result = newNodeIT(kind, info, typ)
if children.len > 0:
result.info = children[0].info
result.sons = @children
template previouslyInferred*(t: PType): PType =
if t.sons.len > 1: t.last else: nil
when false:
import tables, strutils
var x: CountTable[string]
addQuitProc proc () {.noconv.} =
for k, v in pairs(x):
echo k
echo v
proc newSym*(symKind: TSymKind, name: PIdent, idgen: IdGenerator; owner: PSym,
info: TLineInfo; options: TOptions = {}): PSym =
# generates a symbol and initializes the hash field too
assert not name.isNil
let id = nextSymId idgen
result = PSym(name: name, kindImpl: symKind, flagsImpl: {}, infoImpl: info, itemId: id,
optionsImpl: options, ownerFieldImpl: owner, offsetImpl: defaultOffset,
disamb: getOrDefault(idgen.disambTable, name).int32)
idgen.disambTable.inc name
when false:
if id.module == 48 and id.item == 39:
writeStackTrace()
echo "kind ", symKind, " ", name.s
if owner != nil: echo owner.name.s
proc astdef*(s: PSym): PNode =
# get only the definition (initializer) portion of the ast
let astVal = s.ast
if astVal != nil and astVal.kind in {nkIdentDefs, nkConstDef}:
astVal[2]
else:
astVal
proc isMetaType*(t: PType): bool =
return t.kind in tyMetaTypes or
(t.kind == tyStatic and t.n == nil) or
tfHasMeta in t.flags
proc isUnresolvedStatic*(t: PType): bool =
return t.kind == tyStatic and t.n == nil
proc linkTo*(t: PType, s: PSym): PType {.discardable.} =
t.sym = s
s.typImpl = t
result = t
proc linkTo*(s: PSym, t: PType): PSym {.discardable.} =
t.sym = s
s.typImpl = t
result = s
template fileIdx*(c: PSym): FileIndex =
# XXX: this should be used only on module symbols
c.position().FileIndex
template filename*(c: PSym): string =
# XXX: this should be used only on module symbols
c.position().FileIndex.toFilename
proc appendToModule*(m: PSym, n: PNode) =
## The compiler will use this internally to add nodes that will be
## appended to the module after the sem pass
if m.astImpl == nil:
m.astImpl = newNode(nkStmtList)
else:
assert m.astImpl.kind == nkStmtList
m.astImpl.add(n)
proc copyStrTable*(dest: var TStrTable, src: TStrTable) =
dest.counter = src.counter
setLen(dest.data, src.data.len)
for i in 0..high(src.data): dest.data[i] = src.data[i]
proc copyIdTable*[T](dest: var TIdTable[T], src: TIdTable[T]) =
dest.counter = src.counter
newSeq(dest.data, src.data.len)
for i in 0..high(src.data): dest.data[i] = src.data[i]
proc copyObjectSet*(dest: var TObjectSet, src: TObjectSet) =
dest.counter = src.counter
setLen(dest.data, src.data.len)
for i in 0..high(src.data): dest.data[i] = src.data[i]
proc discardSons*(father: PNode) =
father.sons = @[]
proc withInfo*(n: PNode, info: TLineInfo): PNode =
# XXX Dead code. Remove
n.info = info
return n
proc newSymNode*(sym: PSym): PNode =
result = newNode(nkSym)
result.sym = sym
result.typField = sym.typ
if result.typField == nil and nifcBackendActive:
# See the two-arg overload in astdef: in the NIF backend cg stage a sym node
# built from a not-yet-typed stub must track the symbol's type lazily.
result.flags.incl nfLazyType
result.info = sym.info
proc newOpenSym*(n: PNode): PNode {.inline.} =
result = newTreeI(nkOpenSym, n.info, n)
proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
result = newNode(kind)
result.intVal = intVal
proc newIntNode*(kind: TNodeKind, intVal: Int128): PNode =
result = newNode(kind)
result.intVal = castToInt64(intVal)
proc lastSon*(n: PNode): lent PNode {.inline.} = n.sons[^1]
template setLastSon*(n: PNode, s: PNode) = n.sons[^1] = s
template firstSon*(n: PNode): PNode = n.sons[0]
template secondSon*(n: PNode): PNode = n.sons[1]
template hasSon*(n: PNode): bool = n.len > 0
template has2Sons*(n: PNode): bool = n.len > 1
proc replaceFirstSon*(n, newson: PNode) {.inline.} =
n.sons[0] = newson
proc replaceSon*(n: PNode; i: int; newson: PNode) {.inline.} =
n.sons[i] = newson
proc last*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
if n.kind == tyProc and n.nImpl.len > 1:
n.nImpl[^1].sym.typ
else:
n.sonsImpl[^1]
proc elementType*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[^1]
proc skipModifier*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[^1]
proc indexType*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[0]
proc baseClass*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[0]
proc base*(t: PType): lent PType {.inline.} =
if t.state == Partial: loadType(t)
result = t.sonsImpl[0]
proc returnType*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[0]
proc setReturnType*(n, r: PType) {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0] = r
proc setIndexType*(n, idx: PType) {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0] = idx
proc firstParamType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
if n.kind == tyProc:
n.nImpl[1].sym.typ
else:
n.sonsImpl[1]
proc firstGenericParam*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[1]
proc typeBodyImpl*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[^1]
proc genericHead*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[0]
proc skipTypes*(t: PType, kinds: TTypeKinds): PType =
## Used throughout the compiler code to test whether a type tree contains or
## doesn't contain a specific type/types - it is often the case that only the
## last child nodes of a type tree need to be searched. This is a really hot
## path within the compiler!
result = t
while result.kind in kinds: result = last(result)
proc newIntTypeNode*(intVal: BiggestInt, typ: PType): PNode =
let kind = skipTypes(typ, abstractVarRange).kind
case kind
of tyInt: result = newNode(nkIntLit)
of tyInt8: result = newNode(nkInt8Lit)
of tyInt16: result = newNode(nkInt16Lit)
of tyInt32: result = newNode(nkInt32Lit)
of tyInt64: result = newNode(nkInt64Lit)
of tyChar: result = newNode(nkCharLit)
of tyUInt: result = newNode(nkUIntLit)
of tyUInt8: result = newNode(nkUInt8Lit)
of tyUInt16: result = newNode(nkUInt16Lit)
of tyUInt32: result = newNode(nkUInt32Lit)
of tyUInt64: result = newNode(nkUInt64Lit)
of tyBool, tyEnum:
# XXX: does this really need to be the kind nkIntLit?
result = newNode(nkIntLit)
of tyStatic: # that's a pre-existing bug, will fix in another PR
result = newNode(nkIntLit)
else: raiseAssert $kind
result.intVal = intVal
result.typField = typ
proc newIntTypeNode*(intVal: Int128, typ: PType): PNode =
# XXX: introduce range check
newIntTypeNode(castToInt64(intVal), typ)
proc newFloatNode*(kind: TNodeKind, floatVal: BiggestFloat): PNode =
result = newNode(kind)
result.floatVal = floatVal
proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode,
params,
name, pattern, genericParams,
pragmas, exceptions: PNode): PNode =
result = newNodeI(kind, info)
result.sons = @[name, pattern, genericParams, params,
pragmas, exceptions, body]
const
AttachedOpToStr*: array[TTypeAttachedOp, string] = [
"=wasMoved", "=destroy", "=copy", "=dup", "=sink", "=trace", "=deepcopy"]
proc `$`*(s: PSym): string =
if s != nil:
result = s.name.s & "@" & $s.id
else:
result = "<nil>"
proc len*(n: PType): int {.inline.} =
if n.state == Partial: loadType(n)
if n.kind == tyProc:
result = if n.nImpl == nil: 0 else: n.nImpl.len
else:
result = n.sonsImpl.len
proc sameTupleLengths*(a, b: PType): bool {.inline.} =
result = a.len == b.len
iterator tupleTypePairs*(a, b: PType): (int, PType, PType) =
for i in 0 ..< a.len:
yield (i, a[i], b[i])
iterator underspecifiedPairs*(a, b: PType; start = 0; without = 0): (PType, PType) =
# XXX Figure out with what typekinds this is called.
for i in start ..< min(a.len, b.len) + without:
yield (a[i], b[i])
proc signatureLen*(t: PType): int {.inline.} =
result = t.len
proc paramsLen*(t: PType): int {.inline.} =
result = t.len - 1
proc genericParamsLen*(t: PType): int {.inline.} =
assert t.kind == tyGenericInst
result = t.len - 2 # without 'head' and 'body'
proc genericInvocationParamsLen*(t: PType): int {.inline.} =
assert t.kind == tyGenericInvocation
result = t.len - 1 # without 'head'
proc kidsLen*(t: PType): int {.inline.} =
result = t.len
proc genericParamHasConstraints*(t: PType): bool {.inline.} = t.len > 0
proc hasElementType*(t: PType): bool {.inline.} = t.len > 0
proc isEmptyTupleType*(t: PType): bool {.inline.} = t.len == 0
proc isSingletonTupleType*(t: PType): bool {.inline.} = t.len == 1
proc genericConstraint*(t: PType): PType {.inline.} = t[0]
iterator genericInstParams*(t: PType): (bool, PType) =
for i in 1..<t.len-1:
yield (i!=1, t[i])
iterator genericInstParamPairs*(a, b: PType): (int, PType, PType) =
for i in 1..<min(a.len, b.len)-1:
yield (i-1, a[i], b[i])
iterator genericInvocationParams*(t: PType): (bool, PType) =
for i in 1..<t.len:
yield (i!=1, t[i])
iterator genericInvocationAndBodyElements*(a, b: PType): (PType, PType) =
for i in 1..<a.len:
yield (a[i], b[i-1])
iterator genericInvocationParamPairs*(a, b: PType): (bool, PType, PType) =
for i in 1..<a.len:
if i >= b.len:
yield (false, nil, nil)
else:
yield (true, a[i], b[i])
iterator genericBodyParams*(t: PType): (int, PType) =
for i in 0..<t.len-1:
yield (i, t[i])
iterator userTypeClassInstParams*(t: PType): (bool, PType) =
for i in 1..<t.len-1:
yield (i!=1, t[i])
iterator ikids*(t: PType): (int, PType) =
for i in 0..<t.len: yield (i, t[i])
const
FirstParamAt* = 1
FirstGenericParamAt* = 1
iterator paramTypes*(t: PType): (int, PType) =
for i in FirstParamAt..<t.len: yield (i, t[i])
iterator paramTypePairs*(a, b: PType): (PType, PType) =
for i in FirstParamAt..<a.len: yield (a[i], b[i])
template paramTypeToNodeIndex*(x: int): int = x
iterator kids*(t: PType): PType =
for i in 0..<t.len: yield t[i]
iterator signature*(t: PType): PType =
# yields return type + parameter types
for i in 0..<t.len: yield t[i]
proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType = nil): PType =
let id = nextTypeId idgen
result = PType(kind: kind, ownerFieldImpl: owner, sizeImpl: defaultSize,
alignImpl: defaultAlignment, itemId: id,
bindingId: id, sonsImpl: @[])
if son != nil:
assert kind != tyProc
result.sonsImpl.add son
when false:
if result.itemId.module == 55 and result.itemId.item == 2:
echo "KNID ", kind
writeStackTrace()
when defined(icDbg):
if kind == tyOpenArray:
echo "NEWTYPE openArray id=", id.module, ".", id.item,
" owner=", (if owner != nil: owner.name.s else: "nil")
echo getStackTrace()
proc setSons*(dest: PType; sons: sink seq[PType]) {.inline.} =
assert dest.kind != tyProc or sons.len <= 1
dest.sonsImpl = sons
proc setSon*(dest: PType; son: sink PType) {.inline.} =
dest.sonsImpl = @[son]
proc setSonsLen*(dest: PType; len: int) {.inline.} =
assert dest.kind != tyProc or len <= 1
setLen(dest.sonsImpl, len)
proc mergeLoc(a: var TLoc, b: TLoc) =
if a.k == low(typeof(a.k)): a.k = b.k
if a.storage == low(typeof(a.storage)): a.storage = b.storage
a.flags.incl b.flags
if a.lode == nil: a.lode = b.lode
if a.snippet == "": a.snippet = b.snippet
proc newSons*(father: PNode, length: int) =
setLen(father.sons, length)
proc newSons*(father: PType, length: int) =
assert father.kind != tyProc or length <= 1
setLen(father.sonsImpl, length)
proc truncateInferredTypeCandidates*(t: PType) {.inline.} =
assert t.kind == tyInferred
if t.len > 1:
setLen(t.sonsImpl, 1)
proc assignType*(dest, src: PType) =
dest.kind = src.kind
dest.flagsImpl = src.flags
dest.callConvImpl = src.callConv
dest.nImpl = src.n
dest.sizeImpl = src.size
dest.alignImpl = src.align
# this fixes 'type TLock = TSysLock':
if src.sym != nil:
if dest.sym != nil:
var destFlags = dest.sym.flags
var srcFlags = src.sym.flags
dest.sym.flagsImpl = destFlags + (srcFlags - {sfUsed, sfExported})
if dest.sym.annex == nil: dest.sym.annexImpl = src.sym.annex
mergeLoc(dest.sym.locImpl, src.sym.loc)
else:
dest.symImpl = src.sym
if src.kind == tyProc:
# `tyProc` uses only `sonsImpl[0]` to store return type.
# parameter symbols and types are stored in `nImpl`.
assert src.sonsImpl.len <= 1
if src.len > 0:
setLen(dest.sonsImpl, 1)
dest.sonsImpl[0] = src.sonsImpl[0]
else:
newSons(dest, src.len)
for i in 0..<src.len: dest[i] = src[i]
proc copyType*(t: PType, idgen: IdGenerator, owner: PSym): PType =
result = newType(t.kind, idgen, owner)
assignType(result, t)
result.symImpl = t.sym # backend-info should not be copied
proc exactReplica*(t: PType; idgen: IdGenerator): PType =
## Copy that INHERITS `bindingId` — the generic-param binding tables
## (`LayeredIdTable`) key on it, so the copy must keep matching its original
## there — while getting its own `itemId`, like every other type. The two
## remaining callers are `semtypinst.instCopyType` (a partially instantiated
## meta type must still bind in the next instantiation round) and the
## `tfUnresolved` typedesc replica in `semtypes.semTypeIdent`; everything
## else that used to come through here is a plain `copyType`.
##
## Do not "simplify" this to share `itemId` as well: `itemId` is the
## serialization identity, and replicas sharing it serialized as duplicate
## defs under one NIF name, which the loader collapsed into a single type —
## losing their flag differences (use-site `tfUnresolved` typedescs) or
## their structure (meta instance bodies shadowing a generic's canonical
## body).
result = PType(kind: t.kind, ownerFieldImpl: t.owner, sizeImpl: defaultSize,
alignImpl: defaultAlignment, itemId: nextTypeId(idgen),
bindingId: t.bindingId)
assignType(result, t)
result.symImpl = t.sym # backend-info should not be copied
proc copySym*(s: PSym; idgen: IdGenerator): PSym =
result = newSym(s.kind, s.name, idgen, s.owner, s.info, s.options)
#result.astImpl = nil # BUGFIX; was: s.ast which made problems
result.typImpl = s.typ
result.flagsImpl = s.flags
result.magicImpl = s.magic
result.optionsImpl = s.options
result.positionImpl = s.position
result.locImpl = s.loc
result.annexImpl = s.annex # BUGFIX
result.constraintImpl = s.constraint
if result.kind in {skVar, skLet, skField}:
result.guardImpl = s.guard
result.bitsizeImpl = s.bitsize
result.alignmentImpl = s.alignment
proc createModuleAlias*(s: PSym, idgen: IdGenerator, newIdent: PIdent, info: TLineInfo;
options: TOptions): PSym =
result = newSym(s.kind, newIdent, idgen, s.owner, info, options)
# keep ID!
result.astImpl = s.ast
#result.id = s.id # XXX figure out what to do with the ID.
result.flagsImpl = s.flags
result.optionsImpl = s.options
result.positionImpl = s.position
result.locImpl = s.loc
result.annexImpl = s.annex
proc initStrTable*(): TStrTable =
result = TStrTable(counter: 0)
newSeq(result.data, StartSize)
proc initIdTable*[T](): TIdTable[T] =
result = TIdTable[T](counter: 0)
newSeq(result.data, StartSize)
proc resetIdTable*[T](x: var TIdTable[T]) =
x.counter = 0
# clear and set to old initial size:
setLen(x.data, 0)
setLen(x.data, StartSize)
proc initObjectSet*(): TObjectSet =
result = TObjectSet(counter: 0)
newSeq(result.data, StartSize)
proc initNodeTable*(ignoreTypes=false): TNodeTable =
result = TNodeTable(counter: 0, ignoreTypes: ignoreTypes)
newSeq(result.data, StartSize)
proc skipTypes*(t: PType, kinds: TTypeKinds; maxIters: int): PType =
result = t
var i = maxIters
while result.kind in kinds:
result = last(result)
dec i
if i == 0: return nil
proc skipTypesOrNil*(t: PType, kinds: TTypeKinds): PType =
## same as skipTypes but handles 'nil'
result = t
while result != nil and result.kind in kinds:
if result.state == Partial: loadType(result)
if result.sonsImpl.len == 0: return nil
result = last(result)
proc isGCedMem*(t: PType): bool {.inline.} =
result = t.kind in {tyString, tyRef, tySequence} or
t.kind == tyProc and t.callConv == ccClosure
proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
owner.incl elem.flags * {tfHasMeta, tfTriggersCompileTime}
if tfNotNil in elem.flags:
if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvocation}:
owner.incl tfNotNil
if elem.isMetaType:
owner.incl tfHasMeta
let mask = elem.flags * {tfHasAsgn, tfHasOwned}
if mask != {} and propagateHasAsgn:
let o2 = owner.skipTypes({tyGenericInst, tyAlias, tySink})
if o2.kind in {tyTuple, tyObject, tyArray,
tySequence, tyString, tySet, tyDistinct}:
if o2.state == Sealed:
# During the original compilation, propagateToOwner set tfHasAsgn/tfHasOwned on the type before it was sealed
# On IC reload, the sealed type already has those flags
assert mask <= o2.flags, "IC bug: sealed type missing propagated flags"
else:
o2.incl mask
owner.incl mask
if owner.kind notin {tyProc, tyGenericInst, tyGenericBody,
tyGenericInvocation, tyPtr}:
let elemB = elem.skipTypes({tyGenericInst, tyAlias, tySink})
if elemB.isGCedMem or tfHasGCedMem in elemB.flags:
# for simplicity, we propagate this flag even to generics. We then
# ensure this doesn't bite us in sempass2.
owner.incl tfHasGCedMem
proc rawAddSon*(father, son: PType; propagateHasAsgn = true) =
ensureMutable father
if father.kind != tyProc or father.sonsImpl.len == 0:
father.sonsImpl.add(son)
if not son.isNil: propagateToOwner(father, son, propagateHasAsgn)
proc addSonNilAllowed*(father, son: PNode) =
father.sons.add(son)
proc delSon*(father: PNode, idx: int) =
if father.len == 0: return
for i in idx..<father.len - 1: father[i] = father[i + 1]
father.sons.setLen(father.len - 1)
proc copyNode*(src: PNode): PNode =
# does not copy its sons!
if src == nil:
return nil
result = newNode(src.kind)
result.info = src.info
result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags
result.comment = src.comment
when defined(useNodeIds):
if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id
case src.kind
of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
of nkFloatLiterals: result.floatVal = src.floatVal
of nkSym: result.sym = src.sym
of nkIdent: result.ident = src.ident
of nkStrLit..nkTripleStrLit: result.strVal = src.strVal
else: discard
when defined(nimsuggest):
result.endInfo = src.endInfo
template transitionNodeKindCommon(k: TNodeKind) {.dirty.} =
let obj {.inject.} = n[]
n[] = TNode(kind: k, typField: n.typ, info: obj.info, flags: obj.flags)
# n.comment = obj.comment # shouldn't be needed, the address doesnt' change
when defined(useNodeIds):
n.id = obj.id
proc transitionSonsKind*(n: PNode, kind: range[nkComesFrom..nkTupleConstr]) =
transitionNodeKindCommon(kind)
n.sons = obj.sons
proc transitionIntKind*(n: PNode, kind: range[nkCharLit..nkUInt64Lit]) =
transitionNodeKindCommon(kind)
n.intVal = obj.intVal
proc transitionIntToFloatKind*(n: PNode, kind: range[nkFloatLit..nkFloat128Lit]) =
transitionNodeKindCommon(kind)
n.floatVal = BiggestFloat(obj.intVal)
proc transitionNoneToSym*(n: PNode) =
transitionNodeKindCommon(nkSym)
template transitionSymKindCommon*(k: TSymKind) =
# Under IC the symbol may still be an unloaded stub (`skStub`); materialise it
# first so its kind-specific fields (read below as `obj.*`) actually exist.
if s.state == Partial: loadSym(s)
let obj {.inject.} = s[]
s[] = TSym(kindImpl: k, itemId: obj.itemId, magicImpl: obj.magicImpl, typImpl: obj.typImpl, name: obj.name,
infoImpl: obj.infoImpl, ownerFieldImpl: obj.ownerFieldImpl, flagsImpl: obj.flagsImpl, astImpl: obj.astImpl,
optionsImpl: obj.optionsImpl, positionImpl: obj.positionImpl, offsetImpl: obj.offsetImpl,
disamb: obj.disamb, locImpl: obj.locImpl, annexImpl: obj.annexImpl, constraintImpl: obj.constraintImpl,
instantiatedFromImpl: obj.instantiatedFromImpl)
when hasFFI:
s.cnameImpl = obj.cnameImpl
when defined(nimsuggest):
s.allUsagesImpl = obj.allUsagesImpl
proc transitionGenericParamToType*(s: PSym) =
transitionSymKindCommon(skType)
proc transitionRoutineSymKind*(s: PSym, kind: range[skProc..skTemplate]) =
transitionSymKindCommon(kind)
s.gcUnsafetyReasonImpl = obj.gcUnsafetyReasonImpl
s.transformedBodyImpl = obj.transformedBodyImpl
proc transitionToLet*(s: PSym) =
transitionSymKindCommon(skLet)
s.guardImpl = obj.guardImpl
s.bitsizeImpl = obj.bitsizeImpl
s.alignmentImpl = obj.alignmentImpl
template copyNodeImpl(dst, src, processSonsStmt) =
if src == nil: return
dst = newNode(src.kind)
dst.info = src.info
when defined(nimsuggest):
result.endInfo = src.endInfo
dst.typ = src.typ
dst.flags = src.flags * PersistentNodeFlags
dst.comment = src.comment
when defined(useNodeIds):
if dst.id == nodeIdToDebug:
echo "COMES FROM ", src.id
case src.kind
of nkCharLit..nkUInt64Lit: dst.intVal = src.intVal
of nkFloatLiterals: dst.floatVal = src.floatVal
of nkSym: dst.sym = src.sym
of nkIdent: dst.ident = src.ident
of nkStrLit..nkTripleStrLit: dst.strVal = src.strVal
else: processSonsStmt
proc shallowCopy*(src: PNode): PNode =
# does not copy its sons, but provides space for them:
copyNodeImpl(result, src):
newSeq(result.sons, src.len)
proc copyTree*(src: PNode): PNode =
# copy a whole syntax tree; performs deep copying
copyNodeImpl(result, src):
newSeq(result.sons, src.len)
for i in 0..<src.len:
result[i] = copyTree(src[i])
proc copyTreeWithoutNode*(src, skippedNode: PNode): PNode =
copyNodeImpl(result, src):
result.sons = newSeqOfCap[PNode](src.len)
for n in src.sons:
if n != skippedNode:
result.sons.add copyTreeWithoutNode(n, skippedNode)
proc hasSonWith*(n: PNode, kind: TNodeKind): bool =
for i in 0..<n.len:
if n[i].kind == kind:
return true
result = false
proc hasNilSon*(n: PNode): bool =
for i in 0..<n.safeLen:
if n[i] == nil:
return true
elif hasNilSon(n[i]):
return true
result = false
proc containsNode*(n: PNode, kinds: TNodeKinds): bool =
result = false
if n == nil: return
case n.kind
of nkEmpty..nkNilLit: result = n.kind in kinds
else:
for i in 0..<n.len:
if n.kind in kinds or containsNode(n[i], kinds): return true
proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool =
case n.kind
of nkEmpty..nkNilLit, nkFormalParams: result = n.kind == kind
else:
for i in 0..<n.len:
if (n[i].kind == kind) or hasSubnodeWith(n[i], kind):
return true
result = false
proc getInt*(a: PNode): Int128 =
case a.kind
of nkCharLit, nkUIntLit..nkUInt64Lit:
result = toInt128(cast[uint64](a.intVal))
of nkInt8Lit..nkInt64Lit:
result = toInt128(a.intVal)
of nkIntLit:
# XXX: enable this assert
# assert a.typ.kind notin {tyChar, tyUint..tyUInt64}
result = toInt128(a.intVal)
else:
raiseRecoverableError("cannot extract number from invalid AST node")
proc getInt64*(a: PNode): int64 {.deprecated: "use getInt".} =
case a.kind
of nkCharLit, nkUIntLit..nkUInt64Lit, nkIntLit..nkInt64Lit:
result = a.intVal
else:
raiseRecoverableError("cannot extract number from invalid AST node")
proc getFloat*(a: PNode): BiggestFloat =
case a.kind
of nkFloatLiterals: result = a.floatVal
of nkCharLit, nkUIntLit..nkUInt64Lit, nkIntLit..nkInt64Lit:
result = BiggestFloat a.intVal
else:
raiseRecoverableError("cannot extract number from invalid AST node")
#doAssert false, "getFloat"
#internalError(a.info, "getFloat")
#result = 0.0
proc getStr*(a: PNode): string =
case a.kind
of nkStrLit..nkTripleStrLit: result = a.strVal
of nkNilLit:
# let's hope this fixes more problems than it creates:
result = ""
else:
raiseRecoverableError("cannot extract string from invalid AST node")
#doAssert false, "getStr"
#internalError(a.info, "getStr")
#result = ""
proc getStrOrChar*(a: PNode): string =
case a.kind
of nkStrLit..nkTripleStrLit: result = a.strVal
of nkCharLit..nkUInt64Lit: result = $chr(int(a.intVal))
else:
raiseRecoverableError("cannot extract string from invalid AST node")
#doAssert false, "getStrOrChar"
#internalError(a.info, "getStrOrChar")
#result = ""
proc isGenericParams*(n: PNode): bool {.inline.} =
## used to judge whether a node is generic params.
n != nil and n.kind == nkGenericParams
proc isGenericRoutine*(n: PNode): bool {.inline.} =
n != nil and n.kind in callableDefs and n[genericParamsPos].isGenericParams
proc isGenericRoutineStrict*(s: PSym): bool {.inline.} =
## determines if this symbol represents a generic routine
## the unusual name is so it doesn't collide and eventually replaces
## `isGenericRoutine`
s.kind in skProcKinds and s.ast.isGenericRoutine
proc isGenericRoutine*(s: PSym): bool {.inline.} =
## determines if this symbol represents a generic routine or an instance of
## one. This should be renamed accordingly and `isGenericRoutineStrict`
## should take this name instead.
##
## Warning/XXX: Unfortunately, it considers a proc kind symbol flagged with
## sfFromGeneric as a generic routine. Instead this should likely not be the
## case and the concepts should be teased apart:
## - generic definition
## - generic instance
## - either generic definition or instance
s.kind in skProcKinds and (sfFromGeneric in s.flags or
s.ast.isGenericRoutine)
proc skipGenericOwner*(s: PSym): PSym =
## Generic instantiations are owned by their originating generic
## symbol. This proc skips such owners and goes straight to the owner
## of the generic itself (the module or the enclosing proc).
result = if s.kind == skModule:
s
elif s.kind in skProcKinds and sfFromGeneric in s.flags and s.owner.kind != skModule:
s.owner.owner
else:
s.owner
proc originatingModule*(s: PSym): PSym =
result = s
while result.kind != skModule: result = result.owner
proc isRoutine*(s: PSym): bool {.inline.} =
result = s.kind in skProcKinds
proc isCompileTimeProc*(s: PSym): bool {.inline.} =
result = s.kind == skMacro or
s.kind in {skProc, skFunc} and sfCompileTime in s.flags
proc hasPattern*(s: PSym): bool {.inline.} =
result = isRoutine(s) and s.ast[patternPos].kind != nkEmpty
iterator pairs*(n: PNode): tuple[i: int, n: PNode] =
for i in 0..<n.safeLen: yield (i, n[i])
proc isAtom*(n: PNode): bool {.inline.} =
result = n.kind >= nkNone and n.kind <= nkNilLit
proc isEmptyType*(t: PType): bool {.inline.} =
## 'void' and 'typed' types are often equivalent to 'nil' these days:
result = t == nil or t.kind in {tyVoid, tyTyped}
proc makeStmtList*(n: PNode): PNode =
if n.kind == nkStmtList:
result = n
else:
result = newNodeI(nkStmtList, n.info)
result.add n
proc skipStmtList*(n: PNode): PNode =
if n.kind in {nkStmtList, nkStmtListExpr}:
for i in 0..<n.len-1:
if n[i].kind notin {nkEmpty, nkCommentStmt}: return n
result = n.lastSon
else:
result = n
proc toVar*(typ: PType; kind: TTypeKind; idgen: IdGenerator): PType =
## If ``typ`` is not a tyVar then it is converted into a `var <typ>` and
## returned. Otherwise ``typ`` is simply returned as-is.
result = typ
if typ.kind != kind:
result = newType(kind, idgen, typ.owner, typ)
proc toRef*(typ: PType; idgen: IdGenerator): PType =
## If ``typ`` is a tyObject then it is converted into a `ref <typ>` and
## returned. Otherwise ``typ`` is simply returned as-is.
result = typ
if typ.skipTypes({tyAlias, tyGenericInst}).kind == tyObject:
result = newType(tyRef, idgen, typ.owner, typ)
proc toObject*(typ: PType): PType =
## If ``typ`` is a tyRef then its immediate son is returned (which in many
## cases should be a ``tyObject``).
## Otherwise ``typ`` is simply returned as-is.
let t = typ.skipTypes({tyAlias, tyGenericInst})
if t.kind == tyRef: t.elementType
else: typ
proc toObjectFromRefPtrGeneric*(typ: PType): PType =
#[
See also `toObject`.
Finds the underlying `object`, even in cases like these:
type
B[T] = object f0: int
A1[T] = ref B[T]
A2[T] = ref object f1: int
A3 = ref object f2: int
A4 = object f3: int
]#
result = typ
while true:
case result.kind
of tyGenericBody: result = result.last
of tyRef, tyPtr, tyGenericInst, tyGenericInvocation, tyAlias: result = result[0]
# automatic dereferencing is deep, refs #18298.
else: break
# result does not have to be object type
proc isImportedException*(t: PType; conf: ConfigRef): bool =
assert t != nil
if conf.exc != excCpp:
return false
let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst})
result = base.sym != nil and {sfCompileToCpp, sfImportc} * base.sym.flags != {}
proc isInfixAs*(n: PNode): bool =
return n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.id == ord(wAs)
proc skipColon*(n: PNode): PNode =
result = n
if n.kind == nkExprColonExpr:
result = n[1]
proc findUnresolvedStatic*(n: PNode): PNode =
if n.kind == nkSym and n.typ != nil and n.typ.kind == tyStatic and n.typ.n == nil:
return n
if n.typ != nil and n.typ.kind == tyTypeDesc:
let t = skipTypes(n.typ, {tyTypeDesc})
if t.kind == tyGenericParam and not t.genericParamHasConstraints:
return n
for son in n:
let n = son.findUnresolvedStatic
if n != nil: return n
return nil
when false:
proc containsNil*(n: PNode): bool =
# only for debugging
if n.isNil: return true
for i in 0..<n.safeLen:
if n[i].containsNil: return true
template hasDestructor*(t: PType): bool = {tfHasAsgn, tfHasOwned} * t.flags != {}
template incompleteType*(t: PType): bool =
t.sym != nil and {sfForward, sfNoForward} * t.sym.flags == {sfForward}
template typeCompleted*(s: PSym) =
incl s, sfNoForward
template detailedInfo*(sym: PSym): string =
sym.name.s
proc isInlineIterator*(typ: PType): bool {.inline.} =
typ.kind == tyProc and tfIterator in typ.flags and typ.callConv != ccClosure
proc isIterator*(typ: PType): bool {.inline.} =
typ.kind == tyProc and tfIterator in typ.flags
proc isClosureIterator*(typ: PType): bool {.inline.} =
typ.kind == tyProc and tfIterator in typ.flags and typ.callConv == ccClosure
proc isClosure*(typ: PType): bool {.inline.} =
typ.kind == tyProc and typ.callConv == ccClosure
proc isNimcall*(s: PSym): bool {.inline.} =
s.typ.callConv == ccNimCall
proc isExplicitCallConv*(s: PSym): bool {.inline.} =
tfExplicitCallConv in s.typ.flags
proc isSinkParam*(s: PSym): bool {.inline.} =
s.kind == skParam and (s.typ.kind == tySink or tfHasOwned in s.typ.flags)
proc isSinkType*(t: PType): bool {.inline.} =
t.kind == tySink or tfHasOwned in t.flags
proc newProcType*(info: TLineInfo; idgen: IdGenerator; owner: PSym): PType =
result = newType(tyProc, idgen, owner)
result.n = newNodeI(nkFormalParams, info)
rawAddSon(result, nil) # return type
# result.n[0] used to be `nkType`, but now it's `nkEffectList` because
# the effects are now stored in there too ... this is a bit hacky, but as
# usual we desperately try to save memory:
result.n.add newNodeI(nkEffectList, info)
proc addParam*(procType: PType; param: PSym) =
param.position = procType.n.len - 1
procType.n.add newSymNode(param)
rawAddSon(procType, param.typ)
const magicsThatCanRaise = {
mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst, mEcho}
proc canRaiseConservative*(fn: PNode): bool =
if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise:
result = false
else:
result = true
proc canRaise*(fn: PNode): bool =
if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or
{sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or
sfGeneratedOp in fn.sym.flags):
result = false
elif fn.kind == nkSym and fn.sym.magic == mEcho:
result = true
elif fn.typ != nil and fn.typ.kind == tyProc and fn.typ.n != nil:
# TODO check for n having sons? or just return false for now if not
if fn.typ.n[0].kind == nkSym:
result = false
else:
# A proc-typed value with no explicit raises slot still has
# unspecified effects, which sempass2 treats conservatively.
# Codegen needs to do the same in order to keep goto-exception
# checks after indirect/closure calls.
result = ((fn.typ.n[0].len < effectListLen) or
fn.typ.n[0][exceptionEffects] == nil or
fn.typ.n[0][exceptionEffects].safeLen > 0)
else:
result = false
proc toHumanStrImpl[T](kind: T, num: static int): string =
result = $kind
result = result[num..^1]
result[0] = result[0].toLowerAscii
proc toHumanStr*(kind: TSymKind): string =
## strips leading `sk`
result = toHumanStrImpl(kind, 2)
proc toHumanStr*(kind: TTypeKind): string =
## strips leading `tk`
result = toHumanStrImpl(kind, 2)
proc skipHiddenAddr*(n: PNode): PNode {.inline.} =
(if n.kind == nkHiddenAddr: n[0] else: n)
proc isNewStyleConcept*(n: PNode): bool {.inline.} =
assert n.kind == nkTypeClassTy
result = n[0].kind == nkEmpty
proc isOutParam*(t: PType): bool {.inline.} = tfIsOutParam in t.flags
const
nodesToIgnoreSet* = {nkNone..pred(nkSym), succ(nkSym)..nkNilLit,
nkTypeSection, nkProcDef, nkConverterDef,
nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
nkExportStmt, nkPragma, nkCommentStmt, nkBreakState,
nkTypeOfExpr, nkMixinStmt, nkBindStmt}
proc isTrue*(n: PNode): bool =
n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
n.kind == nkIntLit and n.intVal != 0
type
TypeMapping* = TIdTable[PType]
SymMapping* = TIdTable[PSym]
template initSymMapping*(): SymMapping = initIdTable[PSym]()
template initTypeMapping*(): TypeMapping = initIdTable[PType]()
proc sameModules*(a, b: PSym): bool {.inline.} =
assert a.kind == skModule and b.kind == skModule
result = a.position == b.position
proc sameOwners*(a, b: PSym): bool =
result = a == b or (a.kind == skModule and b.kind == skModule and a.position == b.position) or a.id == b.id