WIP: nim-check grows one-shot queries for nimsuggest-like functionality

This commit is contained in:
Araq
2026-07-07 22:06:12 +02:00
parent abdf1ca559
commit b6bafdffcd
6 changed files with 57 additions and 13 deletions

View File

@@ -34,6 +34,7 @@ from icconfig import produceIcConfig
when not defined(nimKochBootstrap):
import nifbackend
import deps
import idetools
when not defined(leanCompiler):
import docgen
@@ -415,7 +416,26 @@ proc mainCommand*(graph: ModuleGraph) =
for it in conf.searchPaths: msgWriteln(conf, it.string)
of cmdCheck:
commandCheck(graph)
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)
else:
commandCheck(graph)
of cmdM:
# cmdM uses NIF files, not ROD files
graph.config.symbolFiles = disabledSf

View File

@@ -40,9 +40,10 @@ 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)
# For regular check, we don't need the result
if graph.config.cmd == cmdM:
# 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}:
result = semNode
else:
result = graph.emptyNode
@@ -167,7 +168,10 @@ 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:
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.
newNodeI(nkStmtList, module.info)
else:
nil
@@ -247,7 +251,18 @@ 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.ideActive:
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:
# 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: return
if c.config.cmd == cmdM or c.config.ideCmd in {ideDef, ideUse}: return
for (s, info) in c.unusedImports:
if sfUsed notin s.flags:
message(c.config, info, warnUnusedImportX, s.name.s)

View File

@@ -2892,9 +2892,10 @@ 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:
if optCompress in c.config.globalOptions or c.config.cmd == cmdM or
c.config.ideCmd in {ideDef, ideUse}:
# New resolve the include filenames to string literals that contain absolute paths,
# nicer for IC:
# nicer for IC (also for a `--def`/`--usages` query, which emits `.s.bif`):
resolvedIncStmt = newNodeI(nkIncludeStmt, n.info)
result.add resolvedIncStmt
else:

View File

@@ -2033,7 +2033,11 @@ 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:
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.
discard "don't run staticExec for 'nim suggest'"
regs[ra].node.strVal = ""
else:

View File

@@ -359,7 +359,9 @@ 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):
if c.config.cmd != cmdCheck:
# 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.prc.regInfo[i].inUse and c.prc.regInfo[i].kind in {slotTempUnknown,
slotTempInt,
slotTempFloat,
@@ -1598,9 +1600,11 @@ 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.m.errorOutputs != {}:
if c.config.cmd == cmdCheck and c.config.ideCmd == ideNone 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
# 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.
localError(c.config, n.info, "cannot evaluate at compile time: " & n.renderTree)
c.cannotEval = true
return