much better implementation behind 'nim track' now

This commit is contained in:
Araq
2026-07-08 09:29:46 +02:00
parent d6a50e39c0
commit 46f7967804
11 changed files with 47 additions and 66 deletions

View File

@@ -509,6 +509,7 @@ proc parseCommand*(command: string): Command =
of "nifc": cmdNifC # generate C from NIF files
of "ic": cmdIc # generate .build.nif for nifmake
of "icconfig": cmdIcConfig # produce the precompiled config artifact
of "track": cmdTrack # IDE goto-def / find-usages over `nim ic`'s NIF output
else: cmdUnknown
proc setCmd*(conf: ConfigRef, cmd: Command) =

View File

@@ -1134,8 +1134,12 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string
b.endTree() # stmts
proc commandIc*(conf: ConfigRef) =
## Main entry point for `nim ic`
proc commandIc*(conf: ConfigRef; frontendOnly = false) =
## Main entry point for `nim ic`. With `frontendOnly` (used by `nim track` for
## IDE queries) it runs only Phase 1 — the incremental nifler + `nim m`
## frontend that writes every module's `.s.bif` — and skips the whole-program
## backend (`nim nifc` -> C -> link), which a goto-def / find-usages scan does
## not need.
when not defined(nimKochBootstrap):
let nifler = findNifler()
if nifler.len == 0:
@@ -1275,10 +1279,12 @@ proc commandIc*(conf: ConfigRef) =
if nifmake.len == 0:
rawMessage(conf, hintSuccess, "run:" & " nifmake run" & parallel & " " & buildFile)
# without nifmake we can only print the manual commands; emit the
# backend's too (best effort — discovery cannot run) and stop.
let backendFile = generateBackendBuildFile(c, forwardedArgs)
rawMessage(conf, hintSuccess, "generated: " & backendFile)
rawMessage(conf, hintSuccess, "run:" & " nifmake run" & parallel & " " & backendFile)
# backend's too (best effort — discovery cannot run) and stop. An IDE
# query (`frontendOnly`) needs no backend, so skip it there.
if not frontendOnly:
let backendFile = generateBackendBuildFile(c, forwardedArgs)
rawMessage(conf, hintSuccess, "generated: " & backendFile)
rawMessage(conf, hintSuccess, "run:" & " nifmake run" & parallel & " " & backendFile)
return
let cmd = quoteShell(nifmake) & " run" & parallel & " " & quoteShell(buildFile)
rawMessage(conf, hintExecuting, cmd)
@@ -1322,7 +1328,9 @@ proc commandIc*(conf: ConfigRef) =
# Phase 2 — backend (whole-program `nim nifc`), run once over the now-final
# graph. Kept a separate nifmake run so backend rebuilds are decided purely
# by nifmake's input mtimes, independent of frontend discovery.
if frontendOk:
# An IDE query (`frontendOnly`) stops after Phase 1: the `.s.bif` it scans
# are all produced by the frontend; codegen + link would be wasted work.
if frontendOk and not frontendOnly:
let backendFile = generateBackendBuildFile(c, forwardedArgs)
rawMessage(conf, hintSuccess, "generated: " & backendFile)
let cmd = quoteShell(nifmake) & " run" & parallel & " " & quoteShell(backendFile)

View File

@@ -7,13 +7,13 @@
# distribution, for details about the copyright.
#
## NIF-based goto-definition / find-all-usages for `nim check`.
## NIF-based goto-definition / find-all-usages for `nim track`.
##
## This is the mainline-Nim port of nimony's `idetools.nim`. It answers a
## `--def:FILE,LINE,COL` / `--usages:FILE,LINE,COL` query by *scanning the
## `.s.bif` files* (binary NIF, see `dist/nimony/src/lib/bif.nim`) that the
## preceding `nim check` compile emitted into the nimcache directory — NOT by
## re-running sem. NIF distinguishes a definition (`SymbolDef` token) from a use
## preceding `nim ic` frontend (`nim track`) emitted into the nimcache directory
## — NOT by re-running sem. NIF distinguishes a definition (`SymbolDef` token) from a use
## (`Symbol` token) syntactically, so goto-def / find-uses become plain token
## scans over type-checked NIF, which is more reliable than the classic PSym
## engine because generics and macros are type-checked in the NIF too.

View File

@@ -416,26 +416,21 @@ proc mainCommand*(graph: ModuleGraph) =
for it in conf.searchPaths: msgWriteln(conf, it.string)
of cmdCheck:
commandCheck(graph)
of cmdTrack:
# `nim track --def:/--usages:/--track:` — IDE goto-definition / find-usages.
# Runs `nim ic`'s incremental frontend (nifler + per-module `nim m`, so only
# changed modules recompile and each writes a faithful, VM-executed `.s.bif`
# — covering stdlib too), then scans those NIF files (idetools.runIdeQuery).
# Shares the `nim ic` nimcache dir, so a prior `nim ic` build is reused.
setUseIc(true)
wantMainModule(conf)
setOutFile(conf)
when not defined(nimKochBootstrap):
if conf.ideCmd in {ideDef, ideUse}:
# `nim check --def:`/`--usages:`: run a whole-project check that also
# emits each cleanly-compiled module's `.s.bif` (see
# pipelines.shouldWriteNif), then scan those NIF files to answer the
# goto-definition / find-usages query. The NIF writer needs the IC setup
# `cmdM` uses (disabled rod files + `useIc`) to serialize full module
# content; we keep `cmd == cmdCheck` for its error-tolerant sem and the
# VM guards that run compile-time code faithfully for this mode (see
# vm.nim/vmgen.nim), so the emitted NIF reflects macro/gorge symbols.
graph.config.symbolFiles = disabledSf
setUseIc(true)
excl conf.features, Feature.vtables
createDir(getNimcacheDir(conf).string)
commandCheck(graph)
runIdeQuery(conf)
else:
commandCheck(graph)
commandIc(conf, frontendOnly = true)
runIdeQuery(conf)
else:
commandCheck(graph)
rawMessage(conf, errGenerated, "nim track not available in bootstrap build")
of cmdM:
# cmdM uses NIF files, not ROD files
graph.config.symbolFiles = disabledSf

View File

@@ -120,7 +120,7 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
# so `loadConfigs` replays it instead of re-parsing the `nim.cfg` chain — the
# driver runs on the exact same config its children will. See icconfig.nim.
when not defined(nimKochBootstrap):
if conf.cmd == cmdIc:
if conf.cmd in {cmdIc, cmdTrack}:
ensureIcConfig(conf)
var graph = newModuleGraph(cache, conf)
@@ -134,7 +134,7 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
if conf.selectedGC == gcUnselected:
if conf.backend in {backendC, backendCpp, backendObjc} or
(conf.cmd in cmdDocLike and conf.backend != backendJs) or
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM}:
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM, cmdTrack}:
initOrcDefines(conf)
if conf.selectedStrings == stringSso and

View File

@@ -205,6 +205,7 @@ type
cmdNifC # generate C code from NIF files
cmdIc # generate .build.nif for nifmake
cmdIcConfig # `nim ic`'s precompiled-config producer (writes ic_config.cfg.nif)
cmdTrack # `nim track --def/--usages`: IC frontend build + NIF scan for IDE queries
const
cmdBackends* = {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,

View File

@@ -40,10 +40,9 @@ proc processPipeline(graph: ModuleGraph; semNode: PNode; bModule: PPassContext):
of GenDependPass:
result = addDotDependency(bModule, semNode)
of SemPass:
# Return the semantic node for cmdM (NIF generation needs it), and likewise
# for a `--def`/`--usages` query under cmdCheck which emits `.s.bif` too.
# For regular check, we don't need the result.
if graph.config.cmd == cmdM or graph.config.ideCmd in {ideDef, ideUse}:
# Return the semantic node for cmdM (NIF generation needs it)
# For regular check, we don't need the result
if graph.config.cmd == cmdM:
result = semNode
else:
result = graph.emptyNode
@@ -168,10 +167,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
s = stream
graph.interactive = stream.kind == llsStdIn
var topLevelStmts =
if optCompress in graph.config.globalOptions or graph.config.cmd == cmdM or
graph.config.ideCmd in {ideDef, ideUse}:
# A `--def`/`--usages` query emits every module's `.s.bif` under cmdCheck
# (see shouldWriteNif below), which needs the collected top-level stmts.
if optCompress in graph.config.globalOptions or graph.config.cmd == cmdM:
newNodeI(nkStmtList, module.info)
else:
nil
@@ -251,18 +247,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
# current strongly-connected import group (`--icGroup`) are the exception:
# they are compiled from source here, so each must write its own NIF.
let shouldWriteNif =
if graph.config.ideCmd in {ideDef, ideUse}:
# `nim check --def:`/`--usages:` query: this whole-project check runs
# under cmdCheck and emits each cleanly-compiled, non-dirty PROJECT
# module's `.s.bif` so `idetools.runIdeQuery` can scan them afterwards.
# Stdlib modules are skipped: they serialize robustly only via `nim ic`'s
# per-module `cmdM` build, and the common query targets a project symbol
# (whose def + uses all live in project modules). Skip the edited buffer
# and any module reached after an error (incomplete NIF).
graph.config.errorCounter == 0 and
not belongsToStdlib(graph, module) and
graph.config.m.fileInfos[module.position].dirtyFile.isEmpty
elif graph.config.ideActive:
if graph.config.ideActive:
# nimsuggest (cmdM): persist NIF for cleanly-compiled, SAVED modules so
# later queries load them instead of recompiling. Never persist the
# actively edited buffer (it may hold unsaved/incomplete code) nor a

View File

@@ -926,7 +926,7 @@ proc semWithPContext*(c: PContext, n: PNode): PNode =
#if c.config.ideActive: findSuggest(c, n)
proc reportUnusedModules(c: PContext) =
if c.config.cmd == cmdM or c.config.ideCmd in {ideDef, ideUse}: return
if c.config.cmd == cmdM: return
for (s, info) in c.unusedImports:
if sfUsed notin s.flags:
message(c.config, info, warnUnusedImportX, s.name.s)

View File

@@ -2892,10 +2892,9 @@ proc incMod(c: PContext, n: PNode, it: PNode, includeStmtResult, resolvedIncStmt
proc evalInclude(c: PContext, n: PNode): PNode =
result = newNodeI(nkStmtList, n.info)
var resolvedIncStmt: PNode = nil
if optCompress in c.config.globalOptions or c.config.cmd == cmdM or
c.config.ideCmd in {ideDef, ideUse}:
if optCompress in c.config.globalOptions or c.config.cmd == cmdM:
# New resolve the include filenames to string literals that contain absolute paths,
# nicer for IC (also for a `--def`/`--usages` query, which emits `.s.bif`):
# nicer for IC:
resolvedIncStmt = newNodeI(nkIncludeStmt, n.info)
result.add resolvedIncStmt
else:

View File

@@ -2033,11 +2033,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
inc pc
let rd = c.code[pc].regA
createStr regs[ra]
if defined(nimsuggest) or (c.config.cmd == cmdCheck and c.config.ideCmd == ideNone):
# Don't run staticExec for plain `nim check` / `nim suggest`. A
# `--def`/`--usages` query (ideCmd != ideNone) is the exception: it wants
# faithful compile-time execution so the emitted `.bif` — which the query
# then scans — reflects macro/gorge-produced symbols. See idetools.nim.
if defined(nimsuggest) or c.config.cmd == cmdCheck:
discard "don't run staticExec for 'nim suggest'"
regs[ra].node.strVal = ""
else:

View File

@@ -359,9 +359,7 @@ proc genBlock(c: PCtx; n: PNode; dest: var TDest) =
#if c.prc.regInfo[i].kind in {slotFixedVar, slotFixedLet}:
if i != dest:
when not defined(release):
# A `--def`/`--usages` query runs the VM faithfully (ideCmd != ideNone),
# so re-arm the leaking-temporary assert for it as in a real compile.
if c.config.cmd != cmdCheck or c.config.ideCmd != ideNone:
if c.config.cmd != cmdCheck:
if c.prc.regInfo[i].inUse and c.prc.regInfo[i].kind in {slotTempUnknown,
slotTempInt,
slotTempFloat,
@@ -1600,11 +1598,9 @@ proc setSlot(c: PCtx; v: PSym) =
v.positionImpl = getFreeRegister(c, if v.kind == skLet: slotFixedLet else: slotFixedVar, start = 1)
template cannotEval(c: PCtx; n: PNode) =
if c.config.cmd == cmdCheck and c.config.ideCmd == ideNone and c.config.m.errorOutputs != {}:
if c.config.cmd == cmdCheck and c.config.m.errorOutputs != {}:
# nim check command with no error outputs doesn't need to cascade here,
# includes `tryConstExpr` case which should not continue generating code.
# A `--def`/`--usages` query (ideCmd != ideNone) falls through to the hard
# `globalError` below so its compile-time evaluation is faithful.
# includes `tryConstExpr` case which should not continue generating code
localError(c.config, n.info, "cannot evaluate at compile time: " & n.renderTree)
c.cannotEval = true
return