mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-06 07:19:09 +00:00
automate the testing; bugfixes
This commit is contained in:
@@ -2172,6 +2172,12 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
var t = next(s) # skip dot
|
||||
var cont = true
|
||||
let exportTag = pool.tags.getOrIncl"export"
|
||||
# Top-level `let`/`var` sections are loaded even without LoadFullAst: they may
|
||||
# declare `{.compileTime.}` globals whose VM slots the importer initializes
|
||||
# eagerly (pipelines.initLoadedCompileTimeGlobals), which needs them visible in
|
||||
# `topLevel`. They sit in the module header before `(implementation)`.
|
||||
let letTag = pool.tags.getOrIncl(toNifTag(nkLetSection))
|
||||
let varTag = pool.tags.getOrIncl(toNifTag(nkVarSection))
|
||||
while cont and t.kind != EofToken:
|
||||
if t.kind == ParLe:
|
||||
if t.tagId == replayTag:
|
||||
@@ -2271,8 +2277,9 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
result.reexportedModules.add (mname, msuffix)
|
||||
elif t.tagId == implTag:
|
||||
cont = false
|
||||
elif LoadFullAst in flags:
|
||||
# Parse the full statement
|
||||
elif LoadFullAst in flags or t.tagId == letTag or t.tagId == varTag:
|
||||
# Parse the full statement. let/var sections are loaded unconditionally
|
||||
# (see above) so `{.compileTime.}` globals reach the eager initializer.
|
||||
var buf = createTokenBuf(50)
|
||||
nextSubtree(s, buf, t)
|
||||
t = next(s) # skip ParRi
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
import std / [os, tables, sets, times, osproc, algorithm, strtabs, strutils, syncio]
|
||||
import options, msgs, lineinfos, pathutils, condsyms,
|
||||
modulepaths, extccomp, cnif
|
||||
modulepaths, extccomp, cnif, platform
|
||||
|
||||
import "../dist/nimony/src/lib" / [nifstreams, bitabs, nifreader, nifbuilder]
|
||||
import "../dist/nimony/src/gear2" / modnames
|
||||
@@ -196,8 +196,11 @@ proc resolveInclude(c: DepContext; origin, toResolve: string): string =
|
||||
|
||||
proc traverseDeps(c: var DepContext; pair: FilePair; current: Node)
|
||||
|
||||
proc processInclude(c: var DepContext; includePath: string; current: Node) =
|
||||
let resolved = resolveInclude(c, current.files[current.files.len - 1].nimFile, includePath)
|
||||
proc processInclude(c: var DepContext; includePath: string; current: Node; origin: string) =
|
||||
# `origin` = the file the `include` literally appears in (an included file's
|
||||
# own nested includes/imports must resolve relative to IT, not the importing
|
||||
# module's main file).
|
||||
let resolved = resolveInclude(c, origin, includePath)
|
||||
if resolved.len == 0 or not fileExists(resolved):
|
||||
return
|
||||
|
||||
@@ -220,8 +223,16 @@ proc getsImplicitImports(c: DepContext; nimFile: string): bool =
|
||||
## system.nim and never reaches them). Stdlib == under conf.libpath.
|
||||
not isRelativeTo(nimFile, c.config.libpath.string)
|
||||
|
||||
proc processImport(c: var DepContext; importPath: string; current: Node) =
|
||||
let resolved = resolveImport(c, current.files[0].nimFile, importPath)
|
||||
proc processImport(c: var DepContext; importPath: string; current: Node; origin: string) =
|
||||
# `origin` = the file the `import` literally appears in. Crucial for imports
|
||||
# inside `include`d files: e.g. `system.nim` includes `system/excpt.nim`, which
|
||||
# does `import stacktraces` — that must resolve relative to `excpt.nim`
|
||||
# (lib/system/) → `lib/system/stacktraces.nim`, NOT relative to `system.nim`
|
||||
# (lib/) which has no `stacktraces.nim`. Resolving against the main file silently
|
||||
# dropped the `system → stacktraces` edge, so stacktraces was a separate SCC in
|
||||
# the static round and got re-grouped (and recompiled with divergent type ids)
|
||||
# only after the post-sem `.s.deps` revealed the edge.
|
||||
let resolved = resolveImport(c, origin, importPath)
|
||||
if resolved.len == 0 or not fileExists(resolved):
|
||||
return
|
||||
|
||||
@@ -290,6 +301,47 @@ proc evalCondIdent(c: DepContext; v: string): bool =
|
||||
c.scanningMain
|
||||
else: true
|
||||
|
||||
proc constIdentValue(c: DepContext; ident: string): string =
|
||||
## String value of a compile-time platform constant that appears in `when`
|
||||
## guards, or "" when unknown. Mirrors the compiler's magics so the scanner
|
||||
## evaluates e.g. `when hostOS == "standalone"` the SAME way the real compile
|
||||
## does. Without this the comparison is "unknown" → the conservative `true`,
|
||||
## which is WRONG once negated (`else:` branches emit `not (==)`), so a real
|
||||
## conditional `include`/`import` is dropped (e.g. system's `else: include
|
||||
## excpt`, hiding `import stacktraces`).
|
||||
# Must match the compiler's magics EXACTLY, incl. case: `hostOS`/`hostCPU` etc.
|
||||
# fold to the lower-cased platform name (see semfold.nim mHostOS/mHostCPU), and
|
||||
# user code compares against lower-case literals (`when hostOS == "linux"`).
|
||||
case ident
|
||||
of "hostOS": result = toLowerAscii(platform.OS[c.config.target.targetOS].name)
|
||||
of "hostCPU": result = toLowerAscii(platform.CPU[c.config.target.targetCPU].name)
|
||||
of "buildOS": result = toLowerAscii(platform.OS[c.config.target.hostOS].name)
|
||||
of "buildCPU": result = toLowerAscii(platform.CPU[c.config.target.hostCPU].name)
|
||||
else: result = ""
|
||||
|
||||
proc readOperandValue(c: DepContext; s: var Stream): string =
|
||||
## Read one operand of an `==`/`!=` infix and return its string value (a string
|
||||
## literal verbatim, a platform-constant ident resolved, anything else ""), fully
|
||||
## consuming the operand (subtrees are skipped) so the caller stays in sync.
|
||||
let t = next(s)
|
||||
case t.kind
|
||||
of StringLit: result = pool.strings[t.litId]
|
||||
of Ident: result = constIdentValue(c, pool.strings[t.litId])
|
||||
of ParLe:
|
||||
result = ""
|
||||
skipSubtree(s, t)
|
||||
else: result = ""
|
||||
|
||||
proc evalCondCmp(c: DepContext; s: var Stream; isEq: bool): bool =
|
||||
## Evaluate `a == b` / `a != b`. Both operands known → real result; otherwise
|
||||
## fall back to `true` (the conservative direction for a bare comparison).
|
||||
let v1 = readOperandValue(c, s)
|
||||
let v2 = readOperandValue(c, s)
|
||||
if v1.len > 0 and v2.len > 0:
|
||||
result = (v1 == v2) == isEq
|
||||
else:
|
||||
result = true
|
||||
|
||||
proc evalCondExpr(c: DepContext; s: var Stream): bool =
|
||||
## Read exactly one condition expression from `s` and return its truth
|
||||
## value. Consumes tokens whether the expression is recognised or not so
|
||||
@@ -326,6 +378,8 @@ proc evalCondExpr(c: DepContext; s: var Stream): bool =
|
||||
result = evalCondExpr(c, s)
|
||||
if not result: result = evalCondExpr(c, s)
|
||||
else: skipSubtree(s, next(s))
|
||||
of "==", "!=":
|
||||
result = evalCondCmp(c, s, name == "==")
|
||||
else:
|
||||
result = true
|
||||
# Drain whatever remains until the matching ParRi.
|
||||
@@ -416,6 +470,8 @@ proc whenMarkerHolds(c: DepContext; s: var Stream): bool =
|
||||
of "or":
|
||||
ok = evalCondExpr(c, s)
|
||||
if not ok: ok = evalCondExpr(c, s)
|
||||
of "==", "!=":
|
||||
ok = evalCondCmp(c, s, name == "==")
|
||||
else:
|
||||
ok = true
|
||||
# finish the subtree
|
||||
@@ -580,15 +636,15 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
|
||||
# be treated as modules. Both still create a real dependency on `m`.
|
||||
for importPath in parseImportPath(s, t):
|
||||
if importPath.len > 0:
|
||||
processImport(c, importPath, current)
|
||||
processImport(c, importPath, current, pair.nimFile)
|
||||
else:
|
||||
while t.kind != ParRi and t.kind != EofToken:
|
||||
for importPath in parseImportPath(s, t):
|
||||
if importPath.len > 0:
|
||||
if tag == "include":
|
||||
processInclude(c, importPath, current)
|
||||
processInclude(c, importPath, current, pair.nimFile)
|
||||
else:
|
||||
processImport(c, importPath, current)
|
||||
processImport(c, importPath, current, pair.nimFile)
|
||||
# Drain any remaining tokens of this node (e.g. the symbol list of a
|
||||
# `fromimport`), up to and including the node's closing ')'.
|
||||
var depth = 1
|
||||
|
||||
@@ -280,6 +280,42 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
|
||||
result = true
|
||||
|
||||
proc loadedDefSym(defs: PNode): PSym =
|
||||
## The defined symbol of a let/var entry as it loads back from a NIF: the
|
||||
## section child is a bare `nkSym` (the `(sd …)` reference), but be defensive
|
||||
## about the from-source shapes too (`nkIdentDefs`, a pragma-wrapped name).
|
||||
case defs.kind
|
||||
of nkSym: result = defs.sym
|
||||
of nkPragmaExpr:
|
||||
result = if defs.len > 0: loadedDefSym(defs[0]) else: nil
|
||||
of nkIdentDefs, nkConstDef:
|
||||
result = if defs.len > 0: loadedDefSym(defs[0]) else: nil
|
||||
else: result = nil
|
||||
|
||||
proc initLoadedCompileTimeGlobals(graph: ModuleGraph; module: PSym; topLevel: PNode) =
|
||||
## Eagerly initialize the compile-time globals (`let/var {.compileTime.}`) of a
|
||||
## module restored from a NIF. In a normal sem these VM slots are filled by
|
||||
## `setupCompileTimeVar` (semstmts) as the section is semchecked; a NIF-loaded
|
||||
## module is never semchecked, so without this a macro or compile-time proc that
|
||||
## reads such a global finds a nil slot. The lazy `vmgen.genGlobalInit` fallback
|
||||
## is order-fragile across proc boundaries (it emits the init at the first
|
||||
## VM-gen'd reference, which need not be the first one executed), so the init has
|
||||
## to happen here, once, before any of the module's code can run. The symbol's
|
||||
## own `ast` is the `nkIdentDefs` (initializer included); re-wrap it in a section
|
||||
## exactly as semstmts does and hand it to the same evaluator.
|
||||
if topLevel == nil: return
|
||||
let idgen = idGeneratorFromModule(module)
|
||||
for stmt in topLevel:
|
||||
if stmt.kind notin {nkLetSection, nkVarSection}: continue
|
||||
for defs in stmt:
|
||||
let s = loadedDefSym(defs)
|
||||
if s != nil and s.kind in {skLet, skVar} and
|
||||
{sfCompileTime, sfGlobal} <= s.flags and
|
||||
s.ast != nil and s.ast.kind == nkIdentDefs:
|
||||
var sect = newNodeI(stmt.kind, s.info)
|
||||
sect.add s.ast
|
||||
setupCompileTimeVar(module, idgen, graph, sect)
|
||||
|
||||
proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags; fromModule: PSym = nil): PSym =
|
||||
var flags = flags
|
||||
if fileIdx == graph.config.projectMainIdx2: flags.incl sfMainModule
|
||||
@@ -345,6 +381,9 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
# Replay state changes from the loaded NIF module
|
||||
if result.ast != nil:
|
||||
replayStateChanges(result, graph)
|
||||
# Fill the VM slots of the module's `{.compileTime.}` globals now (sem
|
||||
# would have, but a NIF-loaded module is never semchecked).
|
||||
initLoadedCompileTimeGlobals(graph, result, precomp.topLevel)
|
||||
return result # Return early, don't process from source
|
||||
let path = toFullPath(graph.config, fileIdx)
|
||||
let filename = AbsoluteFile path
|
||||
|
||||
@@ -1782,8 +1782,15 @@ proc genGlobalInit(c: PCtx; n: PNode; s: PSym) =
|
||||
# This is rather hard to support, due to the laziness of the VM code
|
||||
# generator. See tests/compile/tmacro2 for why this is necessary:
|
||||
# var decls{.compileTime.}: seq[NimNode] = @[]
|
||||
# Load the slot's ADDRESS (not its value): the lazy initializer must REPLACE
|
||||
# the null slot, which `opcWrDeref` only does for an `rkNodeAddr` target
|
||||
# (`nAddr[] = n` for refs). With `opcLdGlobal` the slot value is loaded and for
|
||||
# a ref-typed global that value is an `nkNilLit` ("nil ref"); writing through it
|
||||
# hits the VM's nil-deref guard ("attempt to access a nil address"). This path
|
||||
# is reached for compile-time globals whose defining module is restored from a
|
||||
# NIF under `nim ic` (so `setupCompileTimeVar` never ran to eagerly init them).
|
||||
let dest = c.getTemp(s.typ)
|
||||
c.gABx(n, opcLdGlobal, dest, s.position)
|
||||
c.gABx(n, opcLdGlobalAddr, dest, s.position)
|
||||
if s.astdef != nil:
|
||||
let tmp = c.genx(s.astdef)
|
||||
c.genAdditionalCopy(n, opcWrDeref, dest, 0, tmp)
|
||||
|
||||
29
koch.nim
29
koch.nim
@@ -600,19 +600,38 @@ proc xtemp(cmd: string) =
|
||||
finally:
|
||||
copyExe(d / "bin" / "nim_backup".exe, d / "bin" / "nim".exe)
|
||||
|
||||
proc icTest(args: string) =
|
||||
temp("")
|
||||
let inp = os.parseCmdLine(args)[0]
|
||||
proc runIcTestFile(inp: string) =
|
||||
## Compile a single `tests/ic` file with `nim ic`, once per `#!EDIT!#` fragment
|
||||
## (each fragment is the file's source after that incremental edit). Only checks
|
||||
## that `nim ic` exits 0 — the produced binary's output is not verified here.
|
||||
let content = readFile(inp)
|
||||
let nimExe = getAppDir() / "bin" / "nim_temp".exe
|
||||
var i = 0
|
||||
for fragment in content.split("#!EDIT!#"):
|
||||
let file = inp.replace(".nim", "_temp.nim")
|
||||
writeFile(file, fragment)
|
||||
var cmd = nimExe & " ic --hint:Conf:off --warnings:off "
|
||||
cmd.add quoteShell(file)
|
||||
exec(cmd)
|
||||
inc i
|
||||
|
||||
# The `tests/ic` files that `nim ic` must keep compiling. Multi-module tests rely
|
||||
# on a sibling helper (`timp` -> `myimp`, `tcompiletimeglobal` -> `mctglobal`),
|
||||
# which exercises the NIF import/load path the single-file tests do not.
|
||||
const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils",
|
||||
"tcompiletimeglobal"]
|
||||
|
||||
proc icTest(args: string) =
|
||||
temp("")
|
||||
let parsed = os.parseCmdLine(args)
|
||||
if parsed.len > 0 and parsed[0].len > 0:
|
||||
# `koch ic <file>`: run just that file.
|
||||
runIcTestFile(parsed[0])
|
||||
else:
|
||||
# `koch ic`: the full regression set we want to keep working — the test
|
||||
# suite plus both self-host bootstraps (`bootic` and `bootic -d:release`).
|
||||
for t in icSuite:
|
||||
runIcTestFile("tests" / "ic" / (t & ".nim"))
|
||||
bootic("", skipIntegrityCheck = false)
|
||||
bootic("-d:release", skipIntegrityCheck = false)
|
||||
|
||||
proc buildDrNim(args: string) =
|
||||
if not dirExists("dist/nimz3"):
|
||||
|
||||
Reference in New Issue
Block a user