mirror of
https://github.com/nim-lang/Nim.git
synced 2026-07-10 19:29:32 +00:00
use IC for nimsuggest (#25915)
This commit is contained in:
@@ -332,7 +332,10 @@ when defined(nimsuggest):
|
||||
result = s.allUsagesImpl
|
||||
|
||||
proc `allUsages=`*(s: PSym, val: sink seq[TLineInfo]) {.inline.} =
|
||||
assert s.state != Sealed
|
||||
# No `assert s.state != Sealed`: `allUsagesImpl` is nimsuggest-only usage
|
||||
# tracking, NOT part of the NIF-serialized symbol. nimsuggest loads symbols
|
||||
# as `Sealed` (ast2nif.loadedState under cmdM) yet `suggestSym` legitimately
|
||||
# records usages on them; the getter likewise doesn't assert.
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.allUsagesImpl = val
|
||||
|
||||
@@ -475,6 +478,8 @@ proc comment*(n: PNode): string =
|
||||
else:
|
||||
result = ""
|
||||
|
||||
nodeCommentReader = proc(n: PNode): string {.nimcall.} = comment(n)
|
||||
|
||||
proc `comment=`*(n: PNode, a: string) =
|
||||
let id = n.nodeId
|
||||
if a.len > 0:
|
||||
@@ -490,6 +495,8 @@ proc `comment=`*(n: PNode, a: string) =
|
||||
n.flags.excl nfHasComment
|
||||
gconfig.comments.del(id)
|
||||
|
||||
nodeCommentWriter = proc(n: PNode; s: string) {.nimcall.} = n.comment = s
|
||||
|
||||
# BUGFIX: a module is overloadable so that a proc can have the
|
||||
# same name as an imported module. This is necessary because of
|
||||
# the poor naming choices in the standard library.
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import std / [assertions, tables, sets]
|
||||
from std / strutils import startsWith, endsWith, contains
|
||||
from std / os import fileExists
|
||||
from std / os import fileExists, dirExists, walkFiles
|
||||
from std / syncio import readFile
|
||||
from std / algorithm import sort
|
||||
import "../dist/checksums/src/checksums" / sha1
|
||||
@@ -131,6 +131,20 @@ proc nifLineInfo(w: var LineInfoWriter; info: TLineInfo): PackedLineInfo =
|
||||
# Must use pool.man since toString uses pool.man to unpack
|
||||
result = pack(pool.man, fid, info.line.int32, info.col)
|
||||
|
||||
proc nifLineInfoWithComment(w: var LineInfoWriter; info: TLineInfo; doc: string): PackedLineInfo =
|
||||
## Like `nifLineInfo` but also attaches `doc` as a NIF `#…#` comment on the
|
||||
## token. Used to carry `##` doc comments, which the AST serialization itself
|
||||
## drops, across a NIF round-trip (the loader reads it back off the info).
|
||||
if doc.len == 0:
|
||||
result = nifLineInfo(w, info)
|
||||
else:
|
||||
let cid = pool.strings.getOrIncl(doc).uint32
|
||||
if info == unknownLineInfo:
|
||||
result = packWithComment(pool.man, NoFile, 0'i32, 0'i32, cid)
|
||||
else:
|
||||
let fid = get(w, info.fileIndex)
|
||||
result = packWithComment(pool.man, fid, info.line.int32, info.col, cid)
|
||||
|
||||
proc oldLineInfo(w: var LineInfoWriter; info: PackedLineInfo): TLineInfo =
|
||||
if info == NoLineInfo:
|
||||
result = unknownLineInfo
|
||||
@@ -185,8 +199,9 @@ type
|
||||
decodedFileIndices: HashSet[FileIndex]
|
||||
locals: HashSet[ItemId] # track proc-local symbols
|
||||
inProc: int
|
||||
#writtenTypes: seq[PType] # types written in this module, to be unloaded later
|
||||
#writtenSyms: seq[PSym] # symbols written in this module, to be unloaded later
|
||||
writtenTypes: seq[PType] # types sealed during this emit; under ideActive
|
||||
writtenSyms: seq[PSym] # they are reset to Complete afterwards so nimsuggest
|
||||
# can keep mutating its still-live query targets
|
||||
writtenPackages: HashSet[string]
|
||||
depSuffixes: HashSet[string] # module suffixes already emitted as `(import ...)` deps
|
||||
|
||||
@@ -361,6 +376,7 @@ proc writeType(w: var Writer; dest: var TokenBuf; typ: PType) =
|
||||
# module (or nowhere), leaving dangling references (e.g. `symbol has no
|
||||
# offset` for a `pointer` type whose itemId.module drifted away).
|
||||
typ.state = Sealed
|
||||
if w.infos.config.ideActive: w.writtenTypes.add typ
|
||||
writeTypeDef(w, dest, typ)
|
||||
else:
|
||||
dest.addSymUse pool.syms.getOrIncl(typeToNifSym(typ, w.infos.config)), NoLineInfo
|
||||
@@ -379,8 +395,25 @@ proc writeLib(w: var Writer; dest: var TokenBuf; lib: PLib) =
|
||||
dest.addStrLit lib.name
|
||||
writeNode w, dest, lib.path
|
||||
|
||||
proc docOfSym(sym: PSym): string =
|
||||
## The `##` doc comment documenting `sym`, if any (mirrors nifler's
|
||||
## docCommentOf). The comment may sit on the decl node itself or as the first
|
||||
## `nkCommentStmt` of a routine body. Carried separately on the sym def's NIF
|
||||
## token because the AST serialization drops comments — nimsuggest needs it
|
||||
## for "find definition" doc hovers.
|
||||
let n = sym.astImpl
|
||||
if n == nil or nodeCommentReader == nil: return ""
|
||||
let own = nodeCommentReader(n)
|
||||
if own.len > 0: return own
|
||||
if sym.kindImpl in routineKinds and n.safeLen > bodyPos:
|
||||
let body = n[bodyPos]
|
||||
if body != nil and body.kind == nkStmtList and body.len > 0 and
|
||||
body[0].kind == nkCommentStmt:
|
||||
return nodeCommentReader(body[0])
|
||||
return ""
|
||||
|
||||
proc writeSymDef(w: var Writer; dest: var TokenBuf; sym: PSym) =
|
||||
dest.addParLe sdefTag, trLineInfo(w, sym.infoImpl)
|
||||
dest.addParLe sdefTag, nifLineInfoWithComment(w.infos, sym.infoImpl, docOfSym(sym))
|
||||
dest.addSymDef pool.syms.getOrIncl(w.toNifSymName(sym)), NoLineInfo
|
||||
# The `x` marker means "importable as a bare identifier into an importer's
|
||||
# scope". Object fields carry `sfExported` (so they are visible via `obj.field`
|
||||
@@ -473,6 +506,7 @@ proc writeSym(w: var Writer; dest: var TokenBuf; sym: PSym) =
|
||||
dest.addDotToken()
|
||||
elif shouldWriteSymDef(w, sym):
|
||||
sym.state = Sealed
|
||||
if w.infos.config.ideActive: w.writtenSyms.add sym
|
||||
writeSymDef(w, dest, sym)
|
||||
else:
|
||||
# NIF has direct support for symbol references so we don't need to use a tag here,
|
||||
@@ -497,6 +531,7 @@ proc writeSymNode(w: var Writer; dest: var TokenBuf; n: PNode; sym: PSym) =
|
||||
nodeTyp = sym.typImpl
|
||||
if shouldWriteSymDef(w, sym):
|
||||
sym.state = Sealed
|
||||
if w.infos.config.ideActive: w.writtenSyms.add sym
|
||||
if nodeTyp != n.sym.typImpl:
|
||||
dest.buildTree hiddenTypeTag, trLineInfo(w, n.info):
|
||||
writeType(w, dest, nodeTyp)
|
||||
@@ -609,6 +644,7 @@ var importTag = registerTag("import")
|
||||
var implTag = registerTag("implementation")
|
||||
var reexpModTag = registerTag("reexpmod")
|
||||
var offerTag = registerTag("offer")
|
||||
var modulesrcTag = registerTag("modulesrc")
|
||||
|
||||
proc registerNifAstTags*() =
|
||||
## (Re)registers ast2nif's NIF tags explicitly. The top-level `registerTag`
|
||||
@@ -640,6 +676,7 @@ proc registerNifAstTags*() =
|
||||
implTag = registerTag("implementation")
|
||||
reexpModTag = registerTag("reexpmod")
|
||||
offerTag = registerTag("offer")
|
||||
modulesrcTag = registerTag("modulesrc")
|
||||
|
||||
proc writeNode(w: var Writer; dest: var TokenBuf; n: PNode; forAst = false) =
|
||||
if n == nil:
|
||||
@@ -1324,6 +1361,14 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
for ct in off.concreteTypes:
|
||||
w.deps.addSymUse pool.syms.getOrIncl(typeToNifSym(ct, w.infos.config)), NoLineInfo
|
||||
w.deps.addParRi
|
||||
# Record this module's own absolute source path. The NIF suffix is a hash of
|
||||
# the (relative) path (gear2/modnames.moduleSuffix) and is NOT reversible, so
|
||||
# the standalone include-graph scanner (`scanIncludeGraph`, used by nimsuggest
|
||||
# cold queries) needs the path written explicitly to map an included file back
|
||||
# to the *source* of its includer without loading the module.
|
||||
w.deps.addParLe modulesrcTag, NoLineInfo
|
||||
w.deps.addStrLit toFullPath(config, FileIndex(thisModule))
|
||||
w.deps.addParRi
|
||||
|
||||
# the implTag is used to tell the loader that the
|
||||
# bottom of the file is the implementation of the module:
|
||||
@@ -1350,10 +1395,22 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
let s = op.sym
|
||||
if s.state != Sealed:
|
||||
s.state = Sealed
|
||||
if config.ideActive: w.writtenSyms.add s
|
||||
writeSymDef w, dest, s
|
||||
|
||||
dest.addParRi()
|
||||
|
||||
# nimsuggest reuses these symbols/types as live, mutable query targets (sem
|
||||
# re-runs, usage tracking, flag updates). Sealing is only needed for intra-emit
|
||||
# dedup; once the NIF is built, un-seal so suggest can keep mutating them
|
||||
# (matches `loadedState` loading Complete under ideActive). The `Sealed` guard
|
||||
# stays in force for a real `nim m`/`nim nifc` build.
|
||||
if config.ideActive:
|
||||
for s in w.writtenSyms:
|
||||
if s.state == Sealed: s.state = Complete
|
||||
for t in w.writtenTypes:
|
||||
if t.state == Sealed: t.state = Complete
|
||||
|
||||
# OnlyIfChanged keeps the mtime of content-identical rewrites: nifmake's
|
||||
# mtime-based `needsRebuild` then prunes the rebuild cascade level by
|
||||
# level, and the nifc backend can trust "semmed NIF older than the cnif
|
||||
@@ -1454,15 +1511,29 @@ proc loadedState(c: DecodeContext): ItemState {.inline.} =
|
||||
## State to give a freshly loaded symbol or type. During the C code generation
|
||||
## phase (`nim nifc`) the backend (lambda lifting, the transformer, etc.)
|
||||
## legitimately mutates the loaded entities and never writes them back to a NIF,
|
||||
## so they must be mutable (`Complete`). During semantic checking (`nim m`) a
|
||||
## loaded entity belongs to an already-compiled dependency and must stay
|
||||
## `Sealed` so accidental mutations are caught.
|
||||
if c.infos.config.cmd == cmdNifC: Complete else: Sealed
|
||||
## so they must be mutable (`Complete`). nimsuggest (`ideActive`) is the same
|
||||
## case: it reuses loaded symbols as live query targets and mutates them during
|
||||
## sem and suggestion bookkeeping (usage tracking, flags) without authoritatively
|
||||
## writing those mutations back (its NIF emits are gated to non-dirty, error-free
|
||||
## modules and re-serialize from the proper state). During a plain `nim m`
|
||||
## semantic check a loaded entity belongs to an already-compiled dependency and
|
||||
## must stay `Sealed` so accidental mutations are caught.
|
||||
if c.infos.config.cmd == cmdNifC or c.infos.config.ideActive: Complete else: Sealed
|
||||
|
||||
proc cursorFromIndexEntry(c: var DecodeContext; module: FileIndex; entry: NifIndexEntry;
|
||||
buf: var TokenBuf): Cursor =
|
||||
let s = addr c.mods[module].stream
|
||||
s.r.jumpTo entry.offset
|
||||
# A seek-load is self-contained: its tokens must decode their relative line
|
||||
# info against `entry.info` ALONE. The stream's `parents` stack can be left at
|
||||
# depth >1 by a prior non-seek read (e.g. loadNifModule reads `(stmts`/
|
||||
# `(implementation` without consuming their `)`), and `parse` only overwrites
|
||||
# parents[0] while `rawNext` reads parents[^1] — so a stale top entry (the last
|
||||
# symbol decoded, e.g. excpt.nim:746) would become the base. This is benign on
|
||||
# a freshly-opened stream (cold/stdin) but corrupts EPC recompiles, which reuse
|
||||
# the per-module stream (the global DecodeContext survives resetAllModules).
|
||||
# Collapse the stack so parse's parentInfo is the sole base.
|
||||
s[].parents.setLen 1
|
||||
nifcursors.parse(s[], buf, entry.info)
|
||||
result = cursorAt(buf, 0)
|
||||
|
||||
@@ -1693,7 +1764,18 @@ proc loadSymStub(c: var DecodeContext; t: SymId; thisModule: string;
|
||||
inc val[]
|
||||
let id = itemId(module.int32, val[])
|
||||
|
||||
let offs = c.getOffset(module, symAsStr)
|
||||
let offs = c.mods[module].index.getOrDefault(symAsStr)
|
||||
if offs.offset == 0:
|
||||
# Only module/package self-syms are never written as `(sd)` entries, so a
|
||||
# missing index offset means this is such a sym — typically the OWNER of an
|
||||
# `include`d symbol (`<module>.0.<suffix>`). Synthesize a resolvable
|
||||
# skModule stub (itemId item-0 = the module self-sym) instead of asserting
|
||||
# "symbol has no offset". `Complete` so accessors never try to lazy-load it.
|
||||
result = PSym(itemId: itemId(module.int32, 0'i32), kindImpl: skModule,
|
||||
name: c.cache.getIdent(sn.name), disamb: sn.count.int32,
|
||||
infoImpl: newLineInfo(module, 1, 1), state: Complete)
|
||||
c.syms[symAsStr] = (result, NifIndexEntry())
|
||||
return result
|
||||
let (stubKind, stubName) = stubKindAndName(c.cache, sn.name)
|
||||
result = PSym(itemId: id, kindImpl: stubKind, name: stubName, disamb: sn.count.int32, state: Partial)
|
||||
c.syms[symAsStr] = (result, offs)
|
||||
@@ -1909,8 +1991,14 @@ proc loadSym*(c: var DecodeContext; s: PSym) =
|
||||
|
||||
# Now parse the symbol definition with all local symbols pre-registered
|
||||
s.infoImpl = c.infos.oldLineInfo(n.info)
|
||||
# The `##` doc comment (if any) rides as a NIF comment on the sym def token;
|
||||
# capture it before advancing, then restore it onto the loaded AST so that
|
||||
# suggest's `extractDocComment` (findDocComment on `s.ast`) finds it.
|
||||
let docId = unpack(pool.man, n.info).comment
|
||||
inc n
|
||||
loadSymFromCursor(c, s, n, c.mods[symsModule].suffix, localSyms)
|
||||
if docId != 0'u32 and s.astImpl != nil and nodeCommentWriter != nil:
|
||||
nodeCommentWriter(s.astImpl, pool.strings[StrId(docId)])
|
||||
|
||||
|
||||
template withNode(c: var DecodeContext; n: var Cursor; result: PNode; kind: TNodeKind; body: untyped) =
|
||||
@@ -2268,6 +2356,10 @@ type
|
||||
## generic instances this module created; modulegraphs.nim rebuilds
|
||||
## `procInstCache` from them so a consumer reuses the instance instead of
|
||||
## re-instantiating it in its own (operator-blind) module scope.
|
||||
includes*: seq[string] # resolved full paths of files this module `include`s;
|
||||
# replayed into `inclToMod` by modulegraphs.nim so that
|
||||
# nimsuggest can map a query in an include file back to
|
||||
# this module (`parentModule`) and recompile it.
|
||||
|
||||
proc loadImport(c: var DecodeContext; s: var Stream; deps: var seq[ModuleSuffix]; tok: var PackedToken) =
|
||||
tok = next(s) # skip `(import`
|
||||
@@ -2285,6 +2377,94 @@ proc loadImport(c: var DecodeContext; s: var Stream; deps: var seq[ModuleSuffix]
|
||||
else:
|
||||
raiseAssert "expected ParRi but got " & $tok.kind
|
||||
|
||||
proc loadInclude(c: var DecodeContext; s: var Stream; includes: var seq[string]; tok: var PackedToken) =
|
||||
## Reads an `(include . . "path"...)` entry written by `trInclude`. The paths
|
||||
## are resolved full paths (see semstmts.evalInclude under cmdM/optCompress).
|
||||
tok = next(s) # skip `(include`
|
||||
if tok.kind == DotToken: tok = next(s) # flags
|
||||
if tok.kind == DotToken: tok = next(s) # type
|
||||
while tok.kind == StringLit:
|
||||
includes.add pool.strings[tok.litId]
|
||||
tok = next(s)
|
||||
if tok.kind == ParRi:
|
||||
tok = next(s)
|
||||
else:
|
||||
raiseAssert "expected ParRi in (include ...) but got " & $tok.kind
|
||||
|
||||
proc scanIncludeGraph*(config: ConfigRef): seq[tuple[includer: string; includes: seq[string]]] =
|
||||
## Standalone "full table" scan of every `<suffix>.nif` in the nimcache: reads
|
||||
## only each module's header records — `(modulesrc "path")` (the includer's own
|
||||
## source) and `(include . . "path"...)` (resolved included files) — and returns
|
||||
## (includerSource, includedSources) pairs for the modules that `include`
|
||||
## anything. No `DecodeContext`, no symbol/index loading: it parses the few dep
|
||||
## tokens at the top of the file and stops at the first non-dep node.
|
||||
##
|
||||
## Used by nimsuggest to answer, for a cold-opened *include* file, "which module
|
||||
## includes me?" without NIF-loading that module — so the includer can be
|
||||
## *source*-compiled (modules that `include` files are never served from NIF).
|
||||
result = @[]
|
||||
let dir = getNimcacheDir(config)
|
||||
if not dirExists(dir.string): return
|
||||
for f in walkFiles((dir / RelativeFile"*.nif").string):
|
||||
# only the primary module NIFs; skip the sidecars
|
||||
# (.iface.nif/.impl.nif/.edges.nif/.s.deps.nif).
|
||||
if f.endsWith(".iface.nif") or f.endsWith(".impl.nif") or
|
||||
f.endsWith(".edges.nif") or f.endsWith(".deps.nif"):
|
||||
continue
|
||||
var s = nifstreams.open(f)
|
||||
var includer = ""
|
||||
var includes: seq[string] = @[]
|
||||
var t = next(s) # (stmts
|
||||
if t.kind == ParLe:
|
||||
t = next(s) # flags dot
|
||||
t = next(s) # type dot
|
||||
t = next(s) # first child (matches loadNifModule's priming)
|
||||
# the dep records (import/include/reexpmod/modulesrc) are written first and
|
||||
# contiguously; stop at the first body node or the (implementation) marker.
|
||||
while t.kind == ParLe:
|
||||
if t.tagId == includeTag or t.tagId == modulesrcTag:
|
||||
let isInc = t.tagId == includeTag
|
||||
t = next(s) # into the node (past its ParLe)
|
||||
while t.kind != ParRi and t.kind != EofToken:
|
||||
if t.kind == StringLit:
|
||||
if isInc: includes.add pool.strings[t.litId]
|
||||
else: includer = pool.strings[t.litId]
|
||||
t = next(s)
|
||||
if t.kind == ParRi: t = next(s) # past the ParRi
|
||||
elif t.tagId == importTag or t.tagId == reexpModTag:
|
||||
t = skip(s, t)
|
||||
else:
|
||||
break
|
||||
close s
|
||||
if includer.len > 0 and includes.len > 0:
|
||||
result.add (includer, includes)
|
||||
|
||||
proc nifModuleHasIncludes*(config: ConfigRef; fileIdx: FileIndex): bool =
|
||||
## Cheap header-only check: does the module's `<suffix>.nif` contain an
|
||||
## `(include ...)` record? Used by nimsuggest (`moduleFromNifFile`) to refuse to
|
||||
## NIF-serve modules that `include` files, so the includer is source-compiled
|
||||
## and the included symbols never round-trip through NIF (which mishandles their
|
||||
## owner/line-info on reload).
|
||||
let f = toNifFilename(config, fileIdx)
|
||||
if not fileExists(f): return false
|
||||
var s = nifstreams.open(f)
|
||||
result = false
|
||||
var t = next(s) # (stmts
|
||||
if t.kind == ParLe:
|
||||
t = next(s) # flags dot
|
||||
t = next(s) # type dot
|
||||
t = next(s) # first child
|
||||
while t.kind == ParLe:
|
||||
if t.tagId == includeTag:
|
||||
result = true
|
||||
break
|
||||
elif t.tagId == modulesrcTag or t.tagId == importTag or
|
||||
t.tagId == reexpModTag:
|
||||
t = skip(s, t)
|
||||
else:
|
||||
break
|
||||
close s
|
||||
|
||||
proc addReexportedEnumFields(c: var DecodeContext; sym: PSym; interf: var TStrTable) =
|
||||
## When a non-pure enum type is (re-)exported, its fields must also become
|
||||
## visible (unqualified) to importers. In a from-source build this happens via
|
||||
@@ -2396,7 +2576,7 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
") in export list of module " & suffix & ", last symbol: " & lastGood
|
||||
t = next(s)
|
||||
elif t.tagId == includeTag:
|
||||
t = skipTree(s)
|
||||
loadInclude(c, s, result.includes, t)
|
||||
elif t.tagId == importTag:
|
||||
loadImport(c, s, result.deps, t)
|
||||
elif t.tagId == reexpModTag:
|
||||
@@ -2445,6 +2625,10 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
t = next(s)
|
||||
if ok and genSym != nil and instSym != nil:
|
||||
result.genericOffers.add (genSym, instSym, cts, paramsCount)
|
||||
elif t.tagId == modulesrcTag:
|
||||
# self-identification record for the standalone include-graph scanner;
|
||||
# not needed by the lazy loader, just skip past it.
|
||||
t = skip(s, t)
|
||||
elif t.tagId == implTag:
|
||||
cont = false
|
||||
elif LoadFullAst in flags or t.tagId == letTag or t.tagId == varTag:
|
||||
|
||||
@@ -1150,3 +1150,11 @@ proc strTableGet*(t: TStrTable, name: PIdent): PSym =
|
||||
if result == nil: break
|
||||
if result.name.id == name.id: break
|
||||
h = nextTry(h, high(t.data))
|
||||
|
||||
# --- doc-comment bridge for the NIF serializer -------------------------------
|
||||
# `ast2nif` (the NIF reader/writer) cannot import `ast` (where the comment
|
||||
# accessor and its `gconfig.comments` side table live) because `ast` imports
|
||||
# `ast2nif`. These hooks are assigned by `ast` and let the serializer carry a
|
||||
# decl's `##` doc comment across a NIF round-trip.
|
||||
var nodeCommentReader*: proc(n: PNode): string {.nimcall.}
|
||||
var nodeCommentWriter*: proc(n: PNode; s: string) {.nimcall.}
|
||||
|
||||
@@ -53,7 +53,8 @@ proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) =
|
||||
proc loadConfigsAndProcessCmdLine*(self: NimProg, cache: IdentCache; conf: ConfigRef;
|
||||
graph: ModuleGraph): bool =
|
||||
if self.suggestMode:
|
||||
conf.setCmd cmdIdeTools
|
||||
conf.setCmd cmdCheck
|
||||
conf.ideActive = true
|
||||
if conf.cmd == cmdNimscript:
|
||||
incl(conf.globalOptions, optWasNimscript)
|
||||
loadConfigs(DefaultConfig, cache, conf, graph.idgen) # load all config files
|
||||
|
||||
@@ -718,6 +718,14 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf.outDir = processPath(conf, arg, info, notRelativeToProj=true)
|
||||
of "usenimcache":
|
||||
processOnOffSwitchG(conf, {optUseNimcache}, arg, pass, info)
|
||||
of "ideimports":
|
||||
# nimsuggest: where the import closure comes from. IC is opt-in.
|
||||
# nif|on load unchanged imports from precompiled NIF (cmdM)
|
||||
# source|off (default) recompile the whole closure from source (cmdCheck)
|
||||
case arg.normalize
|
||||
of "nif", "on", "": conf.ideImportsFromNif = true
|
||||
of "source", "off": conf.ideImportsFromNif = false
|
||||
else: localError(conf, info, "'--ideImports' expects 'nif' or 'source', got: '$1'" % arg)
|
||||
of "docseesrcurl":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.docSeeSrcUrl = arg
|
||||
|
||||
@@ -458,7 +458,7 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
of cmdJsonscript:
|
||||
setOutFile(graph.config)
|
||||
commandJsonScript(graph)
|
||||
of cmdUnknown, cmdNone, cmdIdeTools:
|
||||
of cmdUnknown, cmdNone:
|
||||
rawMessage(conf, errGenerated, "invalid command: " & conf.command)
|
||||
|
||||
if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop, cmdM} and
|
||||
|
||||
@@ -607,10 +607,14 @@ proc loadCompilerProc*(g: ModuleGraph; name: string): PSym =
|
||||
when not defined(nimKochBootstrap):
|
||||
# Try to resolve from NIF for both cmdNifC and cmdM (which uses NIF files)
|
||||
if g.config.cmd in {cmdNifC, cmdM}:
|
||||
# First try system module (most compilerprocs are there)
|
||||
# First try system module (most compilerprocs are there).
|
||||
# Only consult the NIF if it actually exists: under nimsuggest's cold
|
||||
# cache (ideActive) system is compiled from source and has no NIF yet,
|
||||
# in which case the proc is already registered in-memory and the caller
|
||||
# found/falls back to it — so degrade to nil instead of asserting.
|
||||
let systemFileIdx = g.config.m.systemFileIdx
|
||||
if systemFileIdx != InvalidFileIdx and not g.withinSystem:
|
||||
# Only try to load from NIF if the file exists (it may not during initial ic build)
|
||||
if systemFileIdx != InvalidFileIdx and not g.withinSystem and
|
||||
fileExists(toNifFilename(g.config, systemFileIdx)):
|
||||
result = tryResolveCompilerProc(ast.program, name, systemFileIdx)
|
||||
if result != nil:
|
||||
strTableAdd(g.compilerprocs, result)
|
||||
@@ -622,6 +626,7 @@ proc loadCompilerProc*(g: ModuleGraph; name: string): PSym =
|
||||
let module = g.ifaces[moduleIdx].module
|
||||
if module != nil and module.name.s == "threadpool":
|
||||
let threadpoolFileIdx = module.position.FileIndex
|
||||
if not fileExists(toNifFilename(g.config, threadpoolFileIdx)): break
|
||||
result = tryResolveCompilerProc(ast.program, name, threadpoolFileIdx)
|
||||
if result != nil:
|
||||
strTableAdd(g.compilerprocs, result)
|
||||
@@ -1000,6 +1005,17 @@ when not defined(nimKochBootstrap):
|
||||
if not fileExists(toNifFilename(g.config, fileIdx)):
|
||||
return PrecompiledModule(module: nil)
|
||||
|
||||
# NOTE: direction-(c) experiment (refuse to NIF-serve include-bearing modules
|
||||
# under ideActive, forcing a source compile) is disabled — it reproduces the
|
||||
# known sibling-resolution corruption (system.string -> excpt.nim:746). The
|
||||
# cold-include *discovery* scan (scanIncludeGraph) stays; the round-trip
|
||||
# fidelity of included symbols is the separate, still-open loader problem.
|
||||
when false:
|
||||
if g.config.ideActive and not g.withinSystem and
|
||||
fileIdx != g.config.m.systemFileIdx and
|
||||
nifModuleHasIncludes(g.config, fileIdx):
|
||||
return PrecompiledModule(module: nil)
|
||||
|
||||
# Create module symbol
|
||||
let filename = AbsoluteFile toFullPath(g.config, fileIdx)
|
||||
|
||||
@@ -1021,6 +1037,12 @@ when not defined(nimKochBootstrap):
|
||||
let ms = materializeReexportedModule(g, mname, msuffix)
|
||||
if ms != nil:
|
||||
strTableAdd(g.ifaces[fileIdx.int].interf, ms)
|
||||
# Re-establish include->module mapping so nimsuggest's `parentModule` can map
|
||||
# a query in an included file back to this (NIF-loaded) module and recompile
|
||||
# it, exactly as it does for a from-source module. Without this the include
|
||||
# relationship is invisible for NIF-served modules.
|
||||
for incPath in result.includes:
|
||||
g.addIncludeDep(fileIdx, fileInfoIdx(g.config, AbsoluteFile incPath))
|
||||
|
||||
# Rebuild `procInstCache` from this module's generic-instance OFFERS so a
|
||||
# consumer's `genericCacheGet` finds the instance and SKIPS re-running
|
||||
@@ -1063,6 +1085,35 @@ when not defined(nimKochBootstrap):
|
||||
if g.config.cmd == cmdM:
|
||||
loadTransitiveHooks(g, result.deps)
|
||||
|
||||
proc isModuleFile(g: ModuleGraph; fileIdx: FileIndex): bool =
|
||||
let i = fileIdx.int32
|
||||
i >= 0 and i < g.ifaces.len and g.ifaces[i].module != nil
|
||||
|
||||
proc registerIncluderFromNif*(g: ModuleGraph; fileIdx: FileIndex): bool =
|
||||
## Targeted cold-include discovery for nimsuggest: scan the nimcache NIFs
|
||||
## (`scanIncludeGraph`) for a module whose include-set contains *this* file
|
||||
## and register only that single include->module edge in `inclToMod`, so a
|
||||
## query inside the include file resolves its includer via `parentModule`.
|
||||
##
|
||||
## Deliberately targeted: registering *every* include relationship (i.e. also
|
||||
## `system`'s own `include`s) eagerly assigns FileIndexes and pollutes
|
||||
## `inclToMod`, which perturbs the NIF line-info decode of unrelated modules
|
||||
## (`system.string` then resolves into `excpt.nim`). Touch nothing but the
|
||||
## one edge we need.
|
||||
let target = toFullPath(g.config, fileIdx)
|
||||
for (includer, includes) in scanIncludeGraph(g.config):
|
||||
for incFile in includes:
|
||||
if cmpPaths(incFile, target) == 0:
|
||||
g.addIncludeDep(fileInfoIdx(g.config, AbsoluteFile includer), fileIdx)
|
||||
return true
|
||||
result = false
|
||||
|
||||
proc needsIncludeScan*(g: ModuleGraph; fileIdx: FileIndex): bool =
|
||||
## True when `fileIdx` is neither a known module of its own nor an
|
||||
## already-known include file — i.e. a cold-opened file whose includer we
|
||||
## must still discover via `registerIncluderFromNif`.
|
||||
not g.isModuleFile(fileIdx) and not g.inclToMod.hasKey(fileIdx)
|
||||
|
||||
proc configComplete*(g: ModuleGraph) =
|
||||
#rememberStartupConfig(g.startupPackedConfig, g.config)
|
||||
discard
|
||||
|
||||
@@ -351,7 +351,7 @@ proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
|
||||
|
||||
## This is used for 'nim dump' etc. where we don't have nimsuggest
|
||||
## support.
|
||||
#if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
|
||||
#if conf.ideActive and optCDebug notin gGlobalOptions: return
|
||||
let sep = if msgNoUnitSep notin flags: conf.unitSep else: ""
|
||||
if not isNil(conf.writelnHook) and msgSkipHook notin flags:
|
||||
conf.writelnHook(s & sep)
|
||||
@@ -457,8 +457,8 @@ To create a stacktrace, rerun compilation with './koch temp $1 <file>', see $2 f
|
||||
|
||||
proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string, ignoreMsg: bool) =
|
||||
if msg in fatalMsgs:
|
||||
if conf.cmd == cmdIdeTools: log(s)
|
||||
if conf.cmd != cmdIdeTools or msg != errFatal:
|
||||
if conf.ideActive: log(s)
|
||||
if not conf.ideActive or msg != errFatal:
|
||||
quit(conf, msg)
|
||||
if msg >= errMin and msg <= errMax or
|
||||
(msg in warnMin..hintMax and msg in conf.warningAsErrors and not ignoreMsg):
|
||||
@@ -472,7 +472,7 @@ proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string,
|
||||
raiseRecoverableError(s)
|
||||
else:
|
||||
quit(conf, msg)
|
||||
elif eh == doAbort and conf.cmd != cmdIdeTools:
|
||||
elif eh == doAbort and not conf.ideActive:
|
||||
quit(conf, msg)
|
||||
elif eh == doRaise:
|
||||
raiseRecoverableError(s)
|
||||
@@ -503,7 +503,7 @@ proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
|
||||
info = context.info
|
||||
|
||||
proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
|
||||
msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
|
||||
msg >= errGenerated and conf.ideActive and optIdeDebug notin conf.globalOptions
|
||||
|
||||
proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
|
||||
conf.m.fileInfos[fileIdx.int32].lines.add line
|
||||
@@ -661,7 +661,7 @@ proc warningDeprecated*(conf: ConfigRef, info: TLineInfo = gCmdLineInfo, msg = "
|
||||
message(conf, info, warnDeprecated, msg)
|
||||
|
||||
proc internalErrorImpl(conf: ConfigRef; info: TLineInfo, errMsg: string, info2: InstantiationInfo) =
|
||||
if conf.cmd in {cmdIdeTools, cmdCheck} and conf.structuredErrorHook.isNil: return
|
||||
if (conf.ideActive or conf.cmd == cmdCheck) and conf.structuredErrorHook.isNil: return
|
||||
writeContext(conf, info)
|
||||
liMessage(conf, info, errInternal, errMsg, doAbort, info2)
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef; idgen:
|
||||
if conf.cmd == cmdNimscript:
|
||||
showHintConf()
|
||||
conf.configFiles.setLen 0
|
||||
if conf.cmd notin {cmdIdeTools, cmdCheck, cmdDump}:
|
||||
if not conf.ideActive and conf.cmd notin {cmdCheck, cmdDump}:
|
||||
if conf.cmd == cmdNimscript:
|
||||
runNimScriptIfExists(conf.projectFull, isMain = true)
|
||||
else:
|
||||
|
||||
@@ -183,7 +183,6 @@ type
|
||||
cmdCheck # semantic checking for whole project
|
||||
cmdM # only compile a single
|
||||
cmdParse # parse a single file (for debugging)
|
||||
cmdIdeTools # ide tools (e.g. nimsuggest)
|
||||
cmdNimscript # evaluate nimscript
|
||||
cmdDoc0
|
||||
cmdDoc # convert .nim doc comments to HTML
|
||||
@@ -400,6 +399,13 @@ type
|
||||
evalMacroCounter*: int
|
||||
exitcode*: int8
|
||||
cmd*: Command # raw command parsed as enum
|
||||
ideActive*: bool # serving IDE tooling (nimsuggest): collect suggestions and
|
||||
# keep going after errors. Decoupled from `cmd` so the IDE
|
||||
# server can run under any compilation mode (cmdCheck, cmdM).
|
||||
ideImportsFromNif*: bool # nimsuggest: load the unchanged import closure from
|
||||
# precompiled NIF (run under cmdM) instead of recompiling it
|
||||
# from source (cmdCheck). IC is opt-in: default off (cmdCheck);
|
||||
# `--ideImports:nif` opts in.
|
||||
cmdInput*: string # input command
|
||||
projectIsCmd*: bool # whether we're compiling from a command input
|
||||
implicitCmd*: bool # whether some flag triggered an implicit `command`
|
||||
@@ -685,6 +691,7 @@ proc newConfigRef*(): ConfigRef =
|
||||
command: "", # the main command (e.g. cc, check, scan, etc)
|
||||
commandArgs: @[], # any arguments after the main command
|
||||
commandLine: "",
|
||||
ideImportsFromNif: false, # IC opt-in; see `--ideImports`
|
||||
implicitImports: @[], # modules that are to be implicitly imported
|
||||
implicitIncludes: @[], # modules that are to be implicitly included
|
||||
docSeeSrcUrl: "",
|
||||
@@ -786,7 +793,7 @@ template quitOrRaise*(conf: ConfigRef, msg = "") =
|
||||
else:
|
||||
quit(msg) # quits with QuitFailure
|
||||
|
||||
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in cmdDocLike + {cmdIdeTools}
|
||||
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.ideActive or conf.cmd in cmdDocLike
|
||||
proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc
|
||||
proc usesSso*(conf: ConfigRef): bool {.inline.} = conf.selectedStrings == stringSso
|
||||
|
||||
@@ -912,7 +919,8 @@ proc getOsCacheDir(): string =
|
||||
|
||||
proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir =
|
||||
proc nimcacheSuffix(conf: ConfigRef): string =
|
||||
if conf.cmd == cmdCheck: "_check"
|
||||
if conf.ideActive: "_nimsuggest" # dedicated cache, never shared with `nim c`
|
||||
elif conf.cmd == cmdCheck: "_check"
|
||||
elif isDefined(conf, "release") or isDefined(conf, "danger"): "_r"
|
||||
else: "_d"
|
||||
|
||||
|
||||
@@ -246,11 +246,20 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
# (imported modules should be loaded from existing NIF files). Members of the
|
||||
# current strongly-connected import group (`--icGroup`) are the exception:
|
||||
# they are compiled from source here, so each must write its own NIF.
|
||||
let shouldWriteNif = (optCompress in graph.config.globalOptions) or
|
||||
(graph.config.cmd == cmdM and
|
||||
(sfMainModule in module.flags or
|
||||
(graph.config.icGroup.len > 0 and
|
||||
toFullPath(graph.config, module.position.FileIndex) in graph.config.icGroup)))
|
||||
let shouldWriteNif =
|
||||
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
|
||||
# module that failed to compile — that would poison the cache.
|
||||
graph.config.cmd == cmdM and graph.config.errorCounter == 0 and
|
||||
graph.config.m.fileInfos[module.position].dirtyFile.isEmpty
|
||||
else:
|
||||
(optCompress in graph.config.globalOptions) or
|
||||
(graph.config.cmd == cmdM and
|
||||
(sfMainModule in module.flags or
|
||||
(graph.config.icGroup.len > 0 and
|
||||
toFullPath(graph.config, module.position.FileIndex) in graph.config.icGroup)))
|
||||
if shouldWriteNif and not graph.config.isDefined("nimscript"):
|
||||
topLevelStmts.add finalNode
|
||||
# Collect replay actions from both pragma computations and VM state diff
|
||||
@@ -380,24 +389,31 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
toFullPath(graph.config, fileIdx) notin graph.config.icGroup):
|
||||
let precomp = moduleFromNifFile(graph, fileIdx)
|
||||
if precomp.module == nil:
|
||||
let nifPath = toNifFilename(graph.config, fileIdx)
|
||||
# Macro-generated imports (e.g. chronicles' parseStmt("import
|
||||
# chronicles/textlines") driven by the chronicles_sinks define) are
|
||||
# invisible to the static scanner, so this module's NIF was never
|
||||
# built. The importer already recorded this import via
|
||||
# addImportFileDep, so flush every module's `.s.deps`: `nim ic` reads
|
||||
# it, re-derives the graph with the missing node + edge, and reruns
|
||||
# the frontend. We still error — this process cannot finish sem
|
||||
# without the import — but the discovery is structured data now, not
|
||||
# a side-channel file.
|
||||
for importer, deps in graph.importDeps.pairs:
|
||||
var paths: seq[string] = @[]
|
||||
for f in deps: paths.add toFullPath(graph.config, f)
|
||||
writeSemDeps(graph.config, importer.int32, paths)
|
||||
globalError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for import: " & toFullPath(graph.config, fileIdx) &
|
||||
" (expected: " & nifPath & ")")
|
||||
return nil # Don't fall through to compile from source
|
||||
if graph.config.ideActive:
|
||||
# nimsuggest bootstrap: this import has no precompiled NIF yet (cold
|
||||
# cache, or it was invalidated). Don't error — fall through to the
|
||||
# source-compile path below; the pass-close emits a fresh NIF so the
|
||||
# next query loads it instead of recompiling.
|
||||
discard
|
||||
else:
|
||||
let nifPath = toNifFilename(graph.config, fileIdx)
|
||||
# Macro-generated imports (e.g. chronicles' parseStmt("import
|
||||
# chronicles/textlines") driven by the chronicles_sinks define) are
|
||||
# invisible to the static scanner, so this module's NIF was never
|
||||
# built. The importer already recorded this import via
|
||||
# addImportFileDep, so flush every module's `.s.deps`: `nim ic` reads
|
||||
# it, re-derives the graph with the missing node + edge, and reruns
|
||||
# the frontend. We still error — this process cannot finish sem
|
||||
# without the import — but the discovery is structured data now, not
|
||||
# a side-channel file.
|
||||
for importer, deps in graph.importDeps.pairs:
|
||||
var paths: seq[string] = @[]
|
||||
for f in deps: paths.add toFullPath(graph.config, f)
|
||||
writeSemDeps(graph.config, importer.int32, paths)
|
||||
globalError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for import: " & toFullPath(graph.config, fileIdx) &
|
||||
" (expected: " & nifPath & ")")
|
||||
return nil # Don't fall through to compile from source
|
||||
else:
|
||||
# Module successfully loaded from NIF file - use it and skip processing
|
||||
result = precomp.module
|
||||
@@ -520,13 +536,21 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
|
||||
graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
|
||||
graph.config.libpath / RelativeFile"system.nim")
|
||||
when not defined(nimKochBootstrap):
|
||||
let precomp = moduleFromNifFile(graph, graph.config.m.systemFileIdx)
|
||||
graph.systemModule = precomp.module
|
||||
# Don't clobber an already-compiled system: nimsuggest's NimScript config
|
||||
# evaluation compiles `system` into this same graph before we get here.
|
||||
if graph.systemModule == nil:
|
||||
let nifPath = toNifFilename(graph.config, graph.config.m.systemFileIdx)
|
||||
localError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for system module (expected: " & nifPath & ")")
|
||||
return
|
||||
let precomp = moduleFromNifFile(graph, graph.config.m.systemFileIdx)
|
||||
graph.systemModule = precomp.module
|
||||
if graph.systemModule == nil:
|
||||
if graph.config.ideActive:
|
||||
# nimsuggest bootstrap: no system NIF yet — compile it from source
|
||||
# (the pass-close emits it), then continue with the main module.
|
||||
graph.compilePipelineSystemModule()
|
||||
else:
|
||||
let nifPath = toNifFilename(graph.config, graph.config.m.systemFileIdx)
|
||||
localError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for system module (expected: " & nifPath & ")")
|
||||
return
|
||||
discard graph.compilePipelineModule(projectFile, {sfMainModule})
|
||||
else:
|
||||
graph.compilePipelineSystemModule()
|
||||
|
||||
@@ -77,7 +77,7 @@ template semIdeForTemplateOrGeneric(c: PContext; n: PNode;
|
||||
# templates perform some quick check whether the cursor is actually in
|
||||
# the generic or template.
|
||||
when defined(nimsuggest):
|
||||
if c.config.cmd == cmdIdeTools and requiresCheck:
|
||||
if c.config.ideActive and requiresCheck:
|
||||
#if optIdeDebug in gGlobalOptions:
|
||||
# echo "passing to safeSemExpr: ", renderTree(n)
|
||||
discard safeSemExpr(c, n)
|
||||
@@ -874,7 +874,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
|
||||
result = hloStmt(c, result)
|
||||
if c.config.cmd == cmdInteractive and not isEmptyType(result.typ):
|
||||
result = buildEchoStmt(c, result)
|
||||
if c.config.cmd == cmdIdeTools:
|
||||
if c.config.ideActive:
|
||||
appendToModule(c.module, result)
|
||||
trackStmt(c, c.module, result, isTopLevel = true)
|
||||
if optMultiMethods notin c.config.globalOptions and
|
||||
@@ -911,7 +911,7 @@ proc semWithPContext*(c: PContext, n: PNode): PNode =
|
||||
result = nil
|
||||
else:
|
||||
result = newNodeI(nkEmpty, n.info)
|
||||
#if c.config.cmd == cmdIdeTools: findSuggest(c, n)
|
||||
#if c.config.ideActive: findSuggest(c, n)
|
||||
|
||||
proc reportUnusedModules(c: PContext) =
|
||||
if c.config.cmd == cmdM: return
|
||||
@@ -920,7 +920,7 @@ proc reportUnusedModules(c: PContext) =
|
||||
message(c.config, info, warnUnusedImportX, s.name.s)
|
||||
|
||||
proc closePContext*(graph: ModuleGraph; c: PContext, n: PNode): PNode =
|
||||
if c.config.cmd == cmdIdeTools and not c.suggestionsMade:
|
||||
if c.config.ideActive and not c.suggestionsMade:
|
||||
suggestSentinel(c)
|
||||
closeScope(c) # close module's scope
|
||||
rawCloseScope(c) # imported symbols; don't check for unused ones!
|
||||
|
||||
@@ -1571,7 +1571,7 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
|
||||
# here at all!
|
||||
#if isSymChoice(n[1]): return
|
||||
when defined(nimsuggest):
|
||||
if c.config.cmd == cmdIdeTools:
|
||||
if c.config.ideActive:
|
||||
suggestExpr(c, n)
|
||||
if exactEquals(c.config.m.trackPos, n[1].info): suggestExprNoCheck(c, n)
|
||||
|
||||
@@ -3405,7 +3405,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
|
||||
c.config.expandNodeResult = $n
|
||||
suggestQuit()
|
||||
|
||||
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
|
||||
if c.config.ideActive: suggestExpr(c, n)
|
||||
if nfSem in n.flags: return
|
||||
case n.kind
|
||||
of nkIdent, nkAccQuoted:
|
||||
|
||||
@@ -273,7 +273,7 @@ proc semGenericStmt(c: PContext, n: PNode,
|
||||
when defined(nimsuggest):
|
||||
if withinTypeDesc in flags: inc c.inTypeContext
|
||||
|
||||
#if conf.cmd == cmdIdeTools: suggestStmt(c, n)
|
||||
#if conf.ideActive: suggestStmt(c, n)
|
||||
semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody)
|
||||
|
||||
case n.kind
|
||||
|
||||
@@ -531,7 +531,7 @@ proc semUsing(c: PContext; n: PNode): PNode =
|
||||
if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "using")
|
||||
for i in 0..<n.len:
|
||||
var a = n[i]
|
||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
||||
if c.config.ideActive: suggestStmt(c, a)
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
|
||||
checkMinSonsLen(a, 3, c.config)
|
||||
@@ -838,7 +838,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||
|
||||
for i in 0..<n.len:
|
||||
var a = n[i]
|
||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
||||
if c.config.ideActive: suggestStmt(c, a)
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config)
|
||||
checkMinSonsLen(a, 3, c.config)
|
||||
@@ -994,7 +994,7 @@ proc semConst(c: PContext, n: PNode): PNode =
|
||||
var b: PNode
|
||||
for i in 0..<n.len:
|
||||
var a = n[i]
|
||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
||||
if c.config.ideActive: suggestStmt(c, a)
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config)
|
||||
checkMinSonsLen(a, 3, c.config)
|
||||
@@ -1535,7 +1535,7 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) =
|
||||
while i < n.len: # n may grow due to type pragma macros
|
||||
var a = n[i]
|
||||
when defined(nimsuggest):
|
||||
if c.config.cmd == cmdIdeTools:
|
||||
if c.config.ideActive:
|
||||
inc c.inTypeContext
|
||||
suggestStmt(c, a)
|
||||
dec c.inTypeContext
|
||||
@@ -1812,7 +1812,7 @@ proc typeSectionFinalPass(c: PContext, n: PNode) =
|
||||
var remainingOwners = initIntSet()
|
||||
for (owner, _, _) in c.forwardTypeUpdates:
|
||||
remainingOwners.incl owner.id
|
||||
|
||||
|
||||
while c.forwardTypeUpdates.len > 0:
|
||||
let pending = move c.forwardTypeUpdates
|
||||
var madeProgress = false
|
||||
@@ -1829,7 +1829,7 @@ proc typeSectionFinalPass(c: PContext, n: PNode) =
|
||||
c.forwardTypeUpdates.add (owner, typ, typeNode)
|
||||
elif not remainingOwners.missingOrExcl(owner.id):
|
||||
madeProgress = true
|
||||
|
||||
|
||||
if not madeProgress:
|
||||
# can't error here unfortunately
|
||||
break
|
||||
@@ -2868,7 +2868,7 @@ 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:
|
||||
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:
|
||||
resolvedIncStmt = newNodeI(nkIncludeStmt, n.info)
|
||||
|
||||
@@ -2233,7 +2233,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = nil
|
||||
inc c.inTypeContext
|
||||
|
||||
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
|
||||
if c.config.ideActive: suggestExpr(c, n)
|
||||
case n.kind
|
||||
of nkEmpty: result = n.typ
|
||||
of nkTypeOfExpr:
|
||||
|
||||
@@ -911,7 +911,7 @@ proc suggestDecl*(c: PContext, n: PNode; s: PSym) =
|
||||
defer:
|
||||
if attached: dec(c.inTypeContext)
|
||||
# If user is typing out an enum field, then don't provide suggestions
|
||||
if s.kind == skEnumField and c.config.cmd == cmdIdeTools and exactEquals(c.config.m.trackPos, n.info):
|
||||
if s.kind == skEnumField and c.config.ideActive and exactEquals(c.config.m.trackPos, n.info):
|
||||
suggestQuit()
|
||||
suggestExpr(c, n)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp
|
||||
# suggestionResultHook, because suggest.nim is included by sigmatch.
|
||||
# So we import that one instead.
|
||||
import compiler / [options, commands, modules,
|
||||
passes, passaux, msgs,
|
||||
passes, passaux, msgs, pipelines,
|
||||
sigmatch, ast,
|
||||
idents, modulegraphs, prefixmatches, lineinfos, cmdlinehelper,
|
||||
pathutils, condsyms, syntaxes, suggestsymdb]
|
||||
@@ -261,6 +261,14 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int,
|
||||
var isKnownFile = true
|
||||
let dirtyIdx = fileInfoIdx(conf, file, isKnownFile)
|
||||
|
||||
# Cold-opened include file: nothing has been compiled/loaded yet, so its
|
||||
# includer is unknown. Scan the nimcache NIFs for the module that `include`s
|
||||
# this exact file and register that one edge; `parentModule(dirtyIdx)` then
|
||||
# resolves to the includer, which we (re)compile below.
|
||||
if conf.ideImportsFromNif and graph.needsIncludeScan(dirtyIdx):
|
||||
discard graph.registerIncluderFromNif(dirtyIdx)
|
||||
let isInclude = graph.inclToMod.hasKey(dirtyIdx)
|
||||
|
||||
if not dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile)
|
||||
else: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"")
|
||||
|
||||
@@ -269,9 +277,9 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int,
|
||||
conf.errorCounter = 0
|
||||
if conf.suggestVersion == 1:
|
||||
graph.usageSym = nil
|
||||
if not isKnownFile:
|
||||
if not isKnownFile and not isInclude:
|
||||
graph.clearInstCache(dirtyIdx)
|
||||
graph.compileProject(dirtyIdx)
|
||||
graph.compilePipelineProject(dirtyIdx)
|
||||
if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and
|
||||
dirtyfile.isEmpty:
|
||||
discard "no need to recompile anything"
|
||||
@@ -279,10 +287,19 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int,
|
||||
let modIdx = graph.parentModule(dirtyIdx)
|
||||
graph.markDirty dirtyIdx
|
||||
graph.markClientsDirty dirtyIdx
|
||||
# For an include-file query the includer must be re-sem'd so the include body
|
||||
# (where trackPos sits) is re-checked. An already-loaded includer is only
|
||||
# recompiled when dirty (pipelines.compilePipelineModule), and the include
|
||||
# edge isn't always in `g.deps` for markClientsDirty to catch (notably on the
|
||||
# EPC path), so mark the includer dirty explicitly.
|
||||
if isInclude:
|
||||
graph.markDirty modIdx
|
||||
if conf.ideCmd != ideMod:
|
||||
if isKnownFile:
|
||||
# `isInclude`: a freshly discovered include file is not "known" yet, but we
|
||||
# still must (source-)compile its includer to serve the query.
|
||||
if isKnownFile or isInclude:
|
||||
graph.clearInstCache(modIdx)
|
||||
graph.compileProject(modIdx)
|
||||
graph.compilePipelineProject(modIdx)
|
||||
if conf.ideCmd in {ideUse, ideDus}:
|
||||
let u = if conf.suggestVersion != 1: graph.symFromInfo(conf.m.trackPos) else: graph.usageSym
|
||||
if u != nil:
|
||||
@@ -577,7 +594,7 @@ proc recompileFullProject(graph: ModuleGraph) =
|
||||
graph.vm = nil
|
||||
graph.resetAllModules()
|
||||
GC_fullCollect()
|
||||
graph.compileProject()
|
||||
graph.compilePipelineProject()
|
||||
|
||||
proc mainThread(graph: ModuleGraph) =
|
||||
let conf = graph.config
|
||||
@@ -621,10 +638,14 @@ var
|
||||
|
||||
proc mainCommand(graph: ModuleGraph) =
|
||||
let conf = graph.config
|
||||
clearPasses(graph)
|
||||
registerPass graph, verbosePass
|
||||
registerPass graph, semPass
|
||||
conf.setCmd cmdIdeTools
|
||||
# Use the pipeline driver (same as `nim check`): it is where IC/NIF loading
|
||||
# and emission live. The legacy `passes.compileProject` path has no NIF support.
|
||||
setPipeLinePass(graph, SemPass)
|
||||
# cmdM loads the unchanged import closure from precompiled NIF; cmdCheck
|
||||
# recompiles everything from source. `ideActive` keeps suggestion collection
|
||||
# and error-resilience regardless of which mode we run under.
|
||||
conf.setCmd(if conf.ideImportsFromNif: cmdM else: cmdCheck)
|
||||
conf.ideActive = true
|
||||
defineSymbol(conf.symbols, $conf.backend)
|
||||
wantMainModule(conf)
|
||||
|
||||
@@ -646,7 +667,7 @@ proc mainCommand(graph: ModuleGraph) =
|
||||
# compile the project before showing any input so that we already
|
||||
# can answer questions right away:
|
||||
benchmark "Initial compilation":
|
||||
compileProject(graph)
|
||||
compilePipelineProject(graph)
|
||||
|
||||
open(requests)
|
||||
open(results)
|
||||
@@ -798,7 +819,7 @@ proc recompilePartially(graph: ModuleGraph, projectFileIdx = InvalidFileIdx) =
|
||||
|
||||
try:
|
||||
benchmark "Recompilation":
|
||||
graph.compileProject(projectFileIdx)
|
||||
graph.compilePipelineProject(projectFileIdx)
|
||||
except Exception as e:
|
||||
myLog fmt "Failed to recompile partially with the following error:\n {e.msg} \n\n {e.getStackTrace()}"
|
||||
try:
|
||||
@@ -1078,9 +1099,22 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
|
||||
myLog fmt "cmd: {cmd}, file: {file}[{line}:{col}], dirtyFile: {dirtyfile}, tag: {tag}"
|
||||
|
||||
var fileIndex: FileIndex = default(FileIndex)
|
||||
# The module to (re)compile for this query. For a normal file it is the file
|
||||
# itself; for an `include` file it is the module that includes it — the include
|
||||
# body is only sem'd as part of its includer, and the includer compiles as the
|
||||
# main module (source, never NIF-loaded), so the include statements at the
|
||||
# cursor get re-checked. The query position stays in the include file.
|
||||
var moduleToCompile: FileIndex = default(FileIndex)
|
||||
var isIncludeQuery = false
|
||||
|
||||
if not (cmd in {ideRecompile, ideGlobalSymbols}):
|
||||
fileIndex = fileInfoIdx(conf, file)
|
||||
# Discover an include file's includer from the NIF include graph (cold query)
|
||||
# so `parentModule` can map it; see registerIncluderFromNif.
|
||||
if conf.ideImportsFromNif and graph.needsIncludeScan(fileIndex):
|
||||
discard graph.registerIncluderFromNif(fileIndex)
|
||||
isIncludeQuery = graph.inclToMod.hasKey(fileIndex)
|
||||
moduleToCompile = if isIncludeQuery: graph.parentModule(fileIndex) else: fileIndex
|
||||
msgs.setDirtyFile(
|
||||
conf,
|
||||
fileIndex,
|
||||
@@ -1100,14 +1134,20 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
|
||||
|
||||
# these commands require partially compiled project
|
||||
elif cmd in {ideSug, ideCon, ideOutline, ideHighlight, ideDef, ideChkFile, ideType, ideDeclaration, ideExpand} and
|
||||
(graph.needsCompilation(fileIndex) or cmd in {ideSug, ideCon}):
|
||||
(graph.needsCompilation(fileIndex) or cmd in {ideSug, ideCon} or isIncludeQuery):
|
||||
# for ideSug use v2 implementation
|
||||
if cmd in {ideSug, ideCon}:
|
||||
conf.m.trackPos = newLineInfo(fileIndex, line, col)
|
||||
conf.m.trackPosAttached = false
|
||||
else:
|
||||
conf.m.trackPos = default(TLineInfo)
|
||||
graph.recompilePartially(fileIndex)
|
||||
# An include file's includer must be (re)compiled from source so the
|
||||
# include body is re-sem'd; force it dirty since the include file itself
|
||||
# is not a module the dirty machinery tracks.
|
||||
if isIncludeQuery:
|
||||
graph.markDirty moduleToCompile
|
||||
graph.markClientsDirty moduleToCompile
|
||||
graph.recompilePartially(moduleToCompile)
|
||||
|
||||
case cmd
|
||||
of ideDef:
|
||||
@@ -1149,11 +1189,15 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
|
||||
graph.markDirtyIfNeeded(file.string, fileIndex)
|
||||
of ideSug, ideCon:
|
||||
# ideSug/ideCon performs partial build of the file, thus mark it dirty for the
|
||||
# future calls.
|
||||
# future calls. For an include file, drive everything off its includer module
|
||||
# (the include file has no module of its own — getModule would be nil).
|
||||
graph.markDirtyIfNeeded(file.string, fileIndex)
|
||||
graph.recompilePartially(fileIndex)
|
||||
let m = graph.getModule fileIndex
|
||||
incl m, sfDirty
|
||||
if isIncludeQuery:
|
||||
graph.markClientsDirty fileIndex
|
||||
graph.recompilePartially(moduleToCompile)
|
||||
let m = graph.getModule moduleToCompile
|
||||
if m != nil:
|
||||
incl m, sfDirty
|
||||
of ideOutline:
|
||||
let n = parseFile(fileIndex, graph.cache, graph.config)
|
||||
graph.iterateOutlineNodes(n, graph.fileSymbols(fileIndex).deduplicateSymInfoPair(false))
|
||||
@@ -1311,11 +1355,10 @@ else:
|
||||
proc mockCommand(graph: ModuleGraph) =
|
||||
retval = graph
|
||||
let conf = graph.config
|
||||
conf.setCmd cmdIdeTools
|
||||
conf.setCmd(if conf.ideImportsFromNif: cmdM else: cmdCheck)
|
||||
conf.ideActive = true
|
||||
defineSymbol(conf.symbols, $conf.backend)
|
||||
clearPasses(graph)
|
||||
registerPass graph, verbosePass
|
||||
registerPass graph, semPass
|
||||
setPipeLinePass(graph, SemPass)
|
||||
|
||||
wantMainModule(conf)
|
||||
|
||||
@@ -1331,7 +1374,7 @@ else:
|
||||
|
||||
# compile the project before showing any input so that we already
|
||||
# can answer questions right away:
|
||||
compileProject(graph)
|
||||
compilePipelineProject(graph)
|
||||
|
||||
|
||||
proc mockCmdLine(pass: TCmdLinePass, cmd: string; conf: ConfigRef) =
|
||||
|
||||
@@ -22,6 +22,14 @@ const
|
||||
|
||||
import std/compilesettings
|
||||
|
||||
# nimsuggest's incremental (NIF/IC) mode is opt-in via `--ideImports:nif`. By default
|
||||
# nimsuggest recompiles the import closure from source (cmdCheck), which is fast and
|
||||
# stable, so the whole suite runs that path. Only the tests listed below exercise the
|
||||
# IC path; running every test under IC dominated the suite's wall-clock (each test
|
||||
# recompiles `system` cold into NIF, plus a few warm-cache-only ordering/highlight
|
||||
# quirks) and blew CI's job timeout.
|
||||
const icTests = ["tic.nim", "tv3_import.nim"]
|
||||
|
||||
proc parseTest(filename: string; epcMode=false): Test =
|
||||
const cursorMarker = "#[!]#"
|
||||
let nimsug = "bin" / addFileExt("nimsuggest_testing", ExeExt)
|
||||
@@ -74,6 +82,14 @@ proc parseTest(filename: string; epcMode=false): Test =
|
||||
# else: ignore empty lines for better readability of the specs
|
||||
inc i
|
||||
tmp.close()
|
||||
# The IC tests opt into the NIF path and get their own private cache. The stdio
|
||||
# variant (epcMode=false) starts cold and writes the NIFs; the EPC variant reuses
|
||||
# them warm, so a single test exercises both the NIF write and the NIF read path.
|
||||
if extractFilename(filename) in icTests:
|
||||
let nimcache = getTempDir() / ("nimsuggest_ic_" &
|
||||
extractFilename(result.dest).changeFileExt(""))
|
||||
if not epcMode: removeDir(nimcache)
|
||||
result.cmd.add " --ideImports:nif --nimcache:" & nimcache
|
||||
# now that we know the markers, substitute them:
|
||||
for a in mitems(result.script):
|
||||
a[0] = a[0] % markers
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
doAssert true#[!]#
|
||||
|
||||
discard """
|
||||
$nimsuggest --tester $1
|
||||
$nimsuggest --tester $file
|
||||
>highlight $1
|
||||
highlight;;skTemplate;;1;;0;;8
|
||||
highlight;;skTemplate;;1;;0;;8
|
||||
|
||||
Reference in New Issue
Block a user