mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-02 03:43:41 +00:00
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
1878 lines
62 KiB
Nim
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
|