mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-04 12:50:28 +00:00
Grinding a small figdraw-based program under `nim ic` and diffing its output against the classic backend surfaced eight bugs, four of which silently produced a wrong binary rather than an error. Frontend / build graph (`deps.nim`): * Dead `when`-guarded imports were compiled anyway. `when someStrdefine == "x": import y` is `cvUnknown` to the scanner, which conservatively keeps the edge — right for an edge, but it also gave `y` its own `nim m` rule, so a build died on a package the user never installed because they never selected that backend. Track which edges are speculative and drop a speculative subtree that cannot compile; if the guard was in fact live, the discovery fixpoint puts the node back with the honest `cannot open file`. * Deleting a still-imported module went unnoticed: no mtime moves, so nothing re-fires and `nim ic` relinked a stale binary while `nim c` reported `cannot open file`. Report an unresolvable import from a non-speculatively reached module during the graph scan. * Macro-generated imports were discovered once and then forgotten. Discovery only ran after a failure and the graph is re-derived statically every run, so on a warm build the discovered module had no rules at all and editing it changed nothing. Seed the graph from the `.s.deps` sidecars up front. * Config changes invalidated nothing. nifmake decides staleness from file mtimes and never looks at a rule's command line, so `-d:foo=bar` / `--mm:` / `--threads:` regenerated the build file with the new switches and re-fired zero rules. Reify the configuration as a file and make it an input of every rule. * Command-line switches never reached the children: they replay the project's config files, never the driver's argv, so `nim ic --opt:speed` produced a byte-identical debug binary (likewise `--panics`, `--experimental`, `--passC`). Forward the driver's switches, minus the ones that must differ per child. Artifacts and codegen: * A failed `nim m` still wrote its `.s.bif` and cookies, so nifmake saw the rule as satisfied on the next run: `nim ic` then reported success for a program that does not compile, and generated code from error-bearing AST (or hit an internal error in `ccgexprs`). Never persist an artifact when `errorCounter > 0`. * Top-level destructors were never injected. `sfInjectDestructors` lives on the module symbol, which `moduleFromNifFile` rebuilds from scratch, so `genTopLevelStmt` skipped `injectDestructorCalls` entirely: a module-level `block: let h = openHandle()` never ran `=destroy`. Persist the flag as a `(modflags)` record. `injectdestructors` also has to tolerate the `nkReplayAction` entries the loader prepends to `topLevel`. * `nfFirstWrite` / `nfLastRead` were dropped by the serializer. A sym node is written as a bare NIF `SymUse` token, which has nowhere to put node flags, so the frontend's move analysis never reached the backend: EVERY first assignment to a destructor-bearing local compiled as `=sink`, i.e. `=destroy` on still-zeroed memory followed by a copy, and no read was ever a move. Wrap a sym use in `(nflags ...)` when it carries persistent node flags.
39 lines
906 B
Nim
39 lines
906 B
Nim
discard """
|
|
description: '''IC: a macro-generated import stays in the graph across runs'''
|
|
"""
|
|
|
|
#? metamorphic
|
|
|
|
# The static scanner cannot see `parseStmt("import dyn")`. The discovery
|
|
# fixpoint recovers it — but only ran AFTER a failure, and the graph is
|
|
# re-derived statically on every run, so on a warm build the discovered module
|
|
# had no nifler/`nim m` rule at all: editing it changed nothing, forever.
|
|
|
|
#!FILE dyn.nim
|
|
proc hidden*(): string = "first"
|
|
|
|
#!FILE gen.nim
|
|
import std/macros
|
|
|
|
macro generatedImport(): untyped =
|
|
parseStmt("import dyn")
|
|
|
|
generatedImport()
|
|
|
|
proc reveal*(): string = hidden()
|
|
|
|
#!FILE main.nim
|
|
import gen
|
|
echo reveal()
|
|
#!STEP expect: first
|
|
|
|
# the warm build must see this edit
|
|
#!FILE dyn.nim
|
|
proc hidden*(): string = "second"
|
|
#!STEP expect: second
|
|
|
|
# and again, to prove it is not a one-shot recovery
|
|
#!FILE dyn.nim
|
|
proc hidden*(): string = "third"
|
|
#!STEP expect: third
|