Files
Nim/compiler/ast.nim
Araq 512d2a8f26 IC: revert the (ht . <sym>) lazy-type pin — it broke sem
The previous commit made `(ht . <sym>)` — a sym node the writer gave an
EXPLICITLY nil type — load back with `nfLazyType`, so `ast.typ` answered
`sym.typ` instead of nil. The stated reason was to remove a load-order
dependence, and the direction was wrong: that nil is load-bearing.

`writeSymNode` only emits the wrapper when the node's own type DIFFERED from
its symbol's, so a nil there says the node genuinely had no type while the
symbol had one. A type symbol used as a VALUE is exactly that shape:
`newException(KeyError, ...)` passes a typedesc, whose node carries no type
while the symbol carries the object type. Handing it `sym.typ` makes sem read
the typedesc as an expression of the type it denotes, and `--ic:on`
compilation of anything instantiating `tables.[]` dies with "only a 'ref
object' can be raised". A four-line program is enough:

    import std/tables
    var t = initTable[string, int]()
    t["a"] = 1
    echo t["a"]

The load-order dependence is real but is not fixed by pinning the flag EITHER
way — setting it breaks sem as above, clearing it would strip the fallback
from the not-yet-loaded-stub population that `nifcBackendActive` exists to
serve. Left alone deliberately, with the reasoning recorded at the site.
`bnode.typ` answers the faithful nil, and the grinder excludes this one shape
via `hasExplicitNilType` — narrowly, only when the cursor says nil and the AST
is saying exactly the symbol's type.

Why the suite did not catch it: `tests/ic` passed 39/39 throughout. The same
four-line program reproduces from the scratchpad and from the repo root, and
PASSES under `tests/ic` — `--skipParentCfg --skipProjCfg` makes it fail there
too, so `tests/config.nims` is what masks it, most plausibly because
evaluating a NimScript config runs the VM and perturbs the very load order the
bug depends on. A test file under `tests/` therefore cannot guard this class,
and no test is added rather than one that passes on the buggy compiler.

Also in this commit, and the reason the bug was found at all:

* `effectsOf` / `raisesNothing` replace the raw subscripting of `fn.typ.n` in
  `canRaiseImpl`, so the effect-list layout is written down in one place and
  the templates carry no knowledge of it. `raisesNothing` is stated as the
  NEGATIVE on purpose — the safe default is "can raise", so the one narrow
  shape that licenses dropping an exception check is the one spelled out, and
  an unanticipated shape falls conservative by construction.

* `-d:icCanRaiseLog` logs every `canRaiseDisp` verdict keyed by name, disamb
  and OWNING MODULE, with the deciding branch. What "the canRaise helpers work
  on a `.bif`" means is that the type the decoder materialises carries the same
  effect list the from-source one did — a claim about the WRITER that the
  BNode/PNode grinder structurally cannot make, since both spellings ask the
  same `PType` and agree however wrong it is. The only oracle is the same
  program built without IC: 234 callees comparable, 0 disagreeing, 23 of them
  reaching the effect-list branch in both builds.

  Two instrumentation bugs worth recording, because both produced confident
  wrong numbers first: keying by name+disamb alone collided (`len.0` names a
  different routine per module) and reported one false disagreement; and the
  branch marker was a global that `canRaiseDisp` left stale on its early
  return, which inflated effect-list coverage from 23 to a claimed 142.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEF7FJvUkGKvG9LSGuEaNR
2026-08-30 08:54:46 +02:00

1878 lines
62 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}
# `canRaise` and `canRaiseConservative` are asked by the C backend, which is
# migrating to reading routine bodies straight off a `.bif` `Cursor` rather than
# off a materialised `PNode` tree (see `compiler/bnode.nim`). Both predicates
# only ever look at a node's `kind`, `sym` and `typ`, so ONE body serves either
# spelling -- but `BNode` is defined in `bnode.nim`, which imports this module,
# so the `BNode` overloads cannot live here. The bodies therefore live in
# templates and `bnode.nim` instantiates them for its own node type: one source
# of truth, no import cycle, and no second copy to keep in sync.
#
# The effect list is reached through `effectsOf` / `raisesNothing` rather than
# by subscripting `fn.typ.n`, so the templates below contain no knowledge of the
# layout and the `BNode` instantiation inherits none. `fn.typ` stays a `PType`
# in both spellings -- there is deliberately no `BType` (see `bnode.nim`) -- so
# what "works on a `.bif`" means for these two is that the type the decoder
# materialises must carry the same effect list the from-source one did. That is
# a claim about the WRITER, not about the vocabulary, and it is checked
# separately: `-d:icCanRaiseLog` logs every answer, and the same program built
# with and without `--ic:on` must produce the same verdicts.
when defined(icCanRaiseLog):
var canRaiseBranch* = 0
## Which branch decided the last answer: 1 = the symbol's magic/flags,
## 2 = `mEcho`, 3 = the EFFECT LIST reached through `effectsOf`, 4 = the
## conservative predicate, 5 = short-circuited in `canRaiseDisp` before
## either predicate ran, 0 = fell through. Only branch 3 reads anything
## that had to survive a `.bif` round trip, so a differential in which no
## callee reaches it would prove nothing about the writer — which is the
## whole point of running the differential. See `-d:icCanRaiseLog`.
template markCanRaiseBranch*(n: int) =
when defined(icCanRaiseLog): canRaiseBranch = n
template canRaiseConservativeImpl*(fnArg: typed): bool =
block:
let fn = fnArg
markCanRaiseBranch 4
not (fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise)
proc effectsOf*(t: PType): PNode {.inline.} =
## The `nkEffectList` a proc type carries as child 0 of its formal-params
## node, with the parameters following from index 1 (`newProcType` builds it
## that way; `cgen` reads the params back with `sonsFrom(prc.typ.n, 1)`).
##
## Named rather than subscripted so that the layout is written down in ONE
## place. `.n` here is a TYPE's node, never a routine body, so it is always
## fully materialised and `firstSon` is safe — the `nfLazyBody` hazard that
## makes raw child access dangerous elsewhere (see `astdef.sons`) cannot reach
## it. A proc type always has this child; `t.n` with no children is not a
## shape the writer or sem produces, and this deliberately does not paper over
## one appearing.
result = if t.n == nil: nil else: t.n.firstSon
proc raisesNothing*(effects: PNode): bool =
## Whether an effect list says DEFINITIVELY that nothing is raised: it is long
## enough to have a raises slot at all, the slot is present, and it is empty.
##
## Every other shape — a list too short to carry the slot, an absent slot, a
## non-empty one — means the effects are unspecified or non-empty, and a
## caller must assume a raise. Stating it as the NEGATIVE is the point: the
## safe default has to be "can raise", so the one narrow case that licenses
## dropping an exception check is the one spelled out here, and a shape nobody
## anticipated falls on the conservative side by construction rather than by
## luck.
result = effects != nil and effects.len >= effectListLen and
effects[exceptionEffects] != nil and
effects[exceptionEffects].safeLen == 0
template canRaiseImpl*(fnArg: typed): bool =
block:
let fn = fnArg
var res: bool
if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or
{sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or
sfGeneratedOp in fn.sym.flags):
markCanRaiseBranch 1
res = false
elif fn.kind == nkSym and fn.sym.magic == mEcho:
markCanRaiseBranch 2
res = true
elif fn.typ != nil and fn.typ.kind == tyProc and fn.typ.n != nil:
markCanRaiseBranch 3
let effects = effectsOf(fn.typ)
if effects.kind == nkSym:
# The historical shape: slot 0 used to be an `nkType` before the effects
# moved in (see `newProcType`). Nothing to read, so nothing licenses a
# raise.
res = 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.
res = not raisesNothing(effects)
else:
markCanRaiseBranch 0
res = false
res
proc canRaiseConservative*(fn: PNode): bool = canRaiseConservativeImpl(fn)
proc canRaise*(fn: PNode): bool = canRaiseImpl(fn)
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