diff --git a/compiler/commands.nim b/compiler/commands.nim index d7dbd3e8b8..d7f0ad735b 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -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) = diff --git a/compiler/deps.nim b/compiler/deps.nim index f46a7f01da..44dfa007d9 100644 --- a/compiler/deps.nim +++ b/compiler/deps.nim @@ -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) diff --git a/compiler/idetools.nim b/compiler/idetools.nim index 71834ec50c..e140e08322 100644 --- a/compiler/idetools.nim +++ b/compiler/idetools.nim @@ -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. diff --git a/compiler/main.nim b/compiler/main.nim index 2a90765350..e27960f589 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -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 diff --git a/compiler/nim.nim b/compiler/nim.nim index 0d2a0a95a4..789657e216 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -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 diff --git a/compiler/options.nim b/compiler/options.nim index d16c42904d..283f7e04c3 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -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, diff --git a/compiler/pipelines.nim b/compiler/pipelines.nim index 7e9bd3a8ea..aed7944b1c 100644 --- a/compiler/pipelines.nim +++ b/compiler/pipelines.nim @@ -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 diff --git a/compiler/sem.nim b/compiler/sem.nim index cf33a68d98..7f777a7add 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -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) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 8ec82bd3cf..ab3a30a29e 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -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: diff --git a/compiler/vm.nim b/compiler/vm.nim index 90f6d43880..0d9ab99b2e 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -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: diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index c49380a31e..a560665b68 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -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