Skip to content

Commit b412260

Browse files
authored
Initial implementation of nimsuggest v3 (#19826)
* Initial implementation of nimsuggest v3 Rework `nimsuggest` to use caching to make usage of ide commands more efficient. Previously, all commands no matter what the state of the process is were causing clean build. In the context of Language Server Protocol(LSP) and lsp clients this was causing perf issues and overall instability. Overall, the goal of v3 is to fit to LSP Server needs - added two new commands: - `recompile` to do clean compilation - `changed` which can be used by the IDEs to notify that a particular file has been changed. The later can be utilized when using LSP file watches. - `globalSymbols` - searching global references - added `segfaults` dependency to allow fallback to clean build when incremental fails. I wish the error to be propagated to the client so we can work on fixing the incremental build failures (typically hitting pointer) - more efficient rebuild flow. ATM incremental rebuild is triggered when the command needs that(i. e. it is global) while the commands that work on the current source rebuild only it Things missing in this PR: - Documentation - Extensive unit testing. Although functional I still see this more as a POC that this approach can work. Next steps: - Implement `sug` request. - Rework/extend the protocol to allow better client/server communication. Ideally we will need push events, diagnostics should be restructored to allow per file notifications, etc. - implement v3 test suite. - better logging * Add tests for v3 and implement ideSug * Remove typeInstCache/procInstCache cleanup * Add ideChkFile command * Avoid contains call when adding symbol info * Remove log * Remove segfaults
1 parent a4fdaa8 commit b412260

File tree

7 files changed

+348
-36
lines changed

7 files changed

+348
-36
lines changed

compiler/modulegraphs.nim

+67-13
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
## represents a complete Nim project. Single modules can either be kept in RAM
1212
## or stored in a rod-file.
1313

14-
import intsets, tables, hashes, md5_old
14+
import intsets, tables, hashes, md5_old, sequtils
1515
import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages
1616
import ic / [packed_ast, ic]
1717

@@ -83,6 +83,8 @@ type
8383
doStopCompile*: proc(): bool {.closure.}
8484
usageSym*: PSym # for nimsuggest
8585
owners*: seq[PSym]
86+
suggestSymbols*: Table[FileIndex, seq[tuple[sym: PSym, info: TLineInfo]]]
87+
suggestErrors*: Table[FileIndex, seq[Suggest]]
8688
methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization!
8789
systemModule*: PSym
8890
sysTypes*: array[TTypeKind, PType]
@@ -385,9 +387,19 @@ when defined(nimfind):
385387
c.graph.onDefinitionResolveForward(c.graph, s, info)
386388

387389
else:
388-
template onUse*(info: TLineInfo; s: PSym) = discard
389-
template onDef*(info: TLineInfo; s: PSym) = discard
390-
template onDefResolveForward*(info: TLineInfo; s: PSym) = discard
390+
when defined(nimsuggest):
391+
template onUse*(info: TLineInfo; s: PSym) = discard
392+
393+
template onDef*(info: TLineInfo; s: PSym) =
394+
let c = getPContext()
395+
if c.graph.config.suggestVersion == 3:
396+
suggestSym(c.graph, info, s, c.graph.usageSym)
397+
398+
template onDefResolveForward*(info: TLineInfo; s: PSym) = discard
399+
else:
400+
template onUse*(info: TLineInfo; s: PSym) = discard
401+
template onDef*(info: TLineInfo; s: PSym) = discard
402+
template onDefResolveForward*(info: TLineInfo; s: PSym) = discard
391403

392404
proc stopCompile*(g: ModuleGraph): bool {.inline.} =
393405
result = g.doStopCompile != nil and g.doStopCompile()
@@ -434,8 +446,7 @@ proc initOperators*(g: ModuleGraph): Operators =
434446
result.opNot = createMagic(g, "not", mNot)
435447
result.opContains = createMagic(g, "contains", mInSet)
436448

437-
proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
438-
result = ModuleGraph()
449+
proc initModuleGraphFields(result: ModuleGraph) =
439450
# A module ID of -1 means that the symbol is not attached to a module at all,
440451
# but to the module graph:
441452
result.idgen = IdGenerator(module: -1'i32, symId: 0'i32, typeId: 0'i32)
@@ -445,9 +456,9 @@ proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
445456
result.ifaces = @[]
446457
result.importStack = @[]
447458
result.inclToMod = initTable[FileIndex, FileIndex]()
448-
result.config = config
449-
result.cache = cache
450459
result.owners = @[]
460+
result.suggestSymbols = initTable[FileIndex, seq[tuple[sym: PSym, info: TLineInfo]]]()
461+
result.suggestErrors = initTable[FileIndex, seq[Suggest]]()
451462
result.methods = @[]
452463
initStrTable(result.compilerprocs)
453464
initStrTable(result.exposed)
@@ -461,6 +472,12 @@ proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
461472
result.operators = initOperators(result)
462473
result.emittedTypeInfo = initTable[string, FileIndex]()
463474

475+
proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
476+
result = ModuleGraph()
477+
result.config = config
478+
result.cache = cache
479+
initModuleGraphFields(result)
480+
464481
proc resetAllModules*(g: ModuleGraph) =
465482
initStrTable(g.packageSyms)
466483
g.deps = initIntSet()
@@ -472,6 +489,7 @@ proc resetAllModules*(g: ModuleGraph) =
472489
g.methods = @[]
473490
initStrTable(g.compilerprocs)
474491
initStrTable(g.exposed)
492+
initModuleGraphFields(g)
475493

476494
proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
477495
if fileIdx.int32 >= 0:
@@ -550,7 +568,19 @@ proc transitiveClosure(g: var IntSet; n: int) =
550568

551569
proc markDirty*(g: ModuleGraph; fileIdx: FileIndex) =
552570
let m = g.getModule fileIdx
553-
if m != nil: incl m.flags, sfDirty
571+
if m != nil:
572+
g.suggestSymbols.del(fileIdx)
573+
g.suggestErrors.del(fileIdx)
574+
incl m.flags, sfDirty
575+
576+
proc unmarkAllDirty*(g: ModuleGraph) =
577+
for i in 0i32..<g.ifaces.len.int32:
578+
let m = g.ifaces[i].module
579+
if m != nil:
580+
m.flags.excl sfDirty
581+
582+
proc isDirty*(g: ModuleGraph; m: PSym): bool =
583+
result = g.suggestMode and sfDirty in m.flags
554584

555585
proc markClientsDirty*(g: ModuleGraph; fileIdx: FileIndex) =
556586
# we need to mark its dependent modules D as dirty right away because after
@@ -560,14 +590,28 @@ proc markClientsDirty*(g: ModuleGraph; fileIdx: FileIndex) =
560590
g.invalidTransitiveClosure = false
561591
transitiveClosure(g.deps, g.ifaces.len)
562592

593+
# every module that *depends* on this file is also dirty:
594+
for i in 0i32..<g.ifaces.len.int32:
595+
if g.deps.contains(i.dependsOn(fileIdx.int)):
596+
g.markDirty(FileIndex(i))
597+
598+
proc needsCompilation*(g: ModuleGraph): bool =
563599
# every module that *depends* on this file is also dirty:
564600
for i in 0i32..<g.ifaces.len.int32:
565601
let m = g.ifaces[i].module
566-
if m != nil and g.deps.contains(i.dependsOn(fileIdx.int)):
567-
incl m.flags, sfDirty
602+
if m != nil:
603+
if sfDirty in m.flags:
604+
return true
568605

569-
proc isDirty*(g: ModuleGraph; m: PSym): bool =
570-
result = g.suggestMode and sfDirty in m.flags
606+
proc needsCompilation*(g: ModuleGraph, fileIdx: FileIndex): bool =
607+
let module = g.getModule(fileIdx)
608+
if module != nil and g.isDirty(module):
609+
return true
610+
611+
for i in 0i32..<g.ifaces.len.int32:
612+
let m = g.ifaces[i].module
613+
if m != nil and g.isDirty(m) and g.deps.contains(fileIdx.int32.dependsOn(i)):
614+
return true
571615

572616
proc getBody*(g: ModuleGraph; s: PSym): PNode {.inline.} =
573617
result = s.ast[bodyPos]
@@ -611,3 +655,13 @@ proc getPackage*(graph: ModuleGraph; fileIdx: FileIndex): PSym =
611655
func belongsToStdlib*(graph: ModuleGraph, sym: PSym): bool =
612656
## Check if symbol belongs to the 'stdlib' package.
613657
sym.getPackageSymbol.getPackageId == graph.systemModule.getPackageId
658+
659+
iterator suggestSymbolsIter*(g: ModuleGraph): tuple[sym: PSym, info: TLineInfo] =
660+
for xs in g.suggestSymbols.values:
661+
for x in xs.deduplicate:
662+
yield x
663+
664+
iterator suggestErrorsIter*(g: ModuleGraph): Suggest =
665+
for xs in g.suggestErrors.values:
666+
for x in xs:
667+
yield x

compiler/options.nim

+11-2
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,9 @@ type
188188
# as far as usesWriteBarrier() is concerned
189189

190190
IdeCmd* = enum
191-
ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod,
192-
ideHighlight, ideOutline, ideKnown, ideMsg, ideProject
191+
ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideChkFile, ideMod,
192+
ideHighlight, ideOutline, ideKnown, ideMsg, ideProject, ideGlobalSymbols,
193+
ideRecompile, ideChanged
193194

194195
Feature* = enum ## experimental features; DO NOT RENAME THESE!
195196
implicitDeref,
@@ -993,12 +994,16 @@ proc parseIdeCmd*(s: string): IdeCmd =
993994
of "use": ideUse
994995
of "dus": ideDus
995996
of "chk": ideChk
997+
of "chkFile": ideChkFile
996998
of "mod": ideMod
997999
of "highlight": ideHighlight
9981000
of "outline": ideOutline
9991001
of "known": ideKnown
10001002
of "msg": ideMsg
10011003
of "project": ideProject
1004+
of "globalSymbols": ideGlobalSymbols
1005+
of "recompile": ideRecompile
1006+
of "changed": ideChanged
10021007
else: ideNone
10031008

10041009
proc `$`*(c: IdeCmd): string =
@@ -1009,13 +1014,17 @@ proc `$`*(c: IdeCmd): string =
10091014
of ideUse: "use"
10101015
of ideDus: "dus"
10111016
of ideChk: "chk"
1017+
of ideChkFile: "chkFile"
10121018
of ideMod: "mod"
10131019
of ideNone: "none"
10141020
of ideHighlight: "highlight"
10151021
of ideOutline: "outline"
10161022
of ideKnown: "known"
10171023
of ideMsg: "msg"
10181024
of ideProject: "project"
1025+
of ideGlobalSymbols: "globalSymbols"
1026+
of ideRecompile: "recompile"
1027+
of ideChanged: "changed"
10191028

10201029
proc floatInt64Align*(conf: ConfigRef): int16 =
10211030
## Returns either 4 or 8 depending on reasons.

compiler/passes.nim

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import
1414
options, ast, llstream, msgs,
1515
idents,
1616
syntaxes, modulegraphs, reorder,
17-
lineinfos, pathutils, packages
17+
lineinfos, pathutils, std/sha1, packages
1818

1919
when defined(nimPreviewSlimSystem):
2020
import std/syncio
@@ -132,6 +132,11 @@ proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
132132
return false
133133
else:
134134
s = stream
135+
136+
when defined(nimsuggest):
137+
let filename = toFullPathConsiderDirty(graph.config, fileIdx).string
138+
msgs.setHash(graph.config, fileIdx, $sha1.secureHashFile(filename))
139+
135140
while true:
136141
openParser(p, fileIdx, s, graph.cache, graph.config)
137142

compiler/suggest.nim

+7-5
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo): int
117117
elif sourceIdent != ident:
118118
result = 0
119119

120-
proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo;
120+
proc symToSuggest*(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo;
121121
quality: range[0..100]; prefix: PrefixMatch;
122122
inTypeContext: bool; scope: int;
123123
useSuppliedInfo = false): Suggest =
@@ -203,14 +203,14 @@ proc `$`*(suggest: Suggest): string =
203203
result.add(sep)
204204
when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
205205
result.add(suggest.doc.escape)
206-
if suggest.version == 0:
206+
if suggest.version in {0, 3}:
207207
result.add(sep)
208208
result.add($suggest.quality)
209209
if suggest.section == ideSug:
210210
result.add(sep)
211211
result.add($suggest.prefix)
212212

213-
proc suggestResult(conf: ConfigRef; s: Suggest) =
213+
proc suggestResult*(conf: ConfigRef; s: Suggest) =
214214
if not isNil(conf.suggestionResultHook):
215215
conf.suggestionResultHook(s)
216216
else:
@@ -424,7 +424,7 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions)
424424
t = skipTypes(t[0], skipPtrs)
425425
elif typ.kind == tyTuple and typ.n != nil:
426426
suggestSymList(c, typ.n, field, n.info, outputs)
427-
427+
428428
suggestOperations(c, n, field, orig, outputs)
429429
if typ != orig:
430430
suggestOperations(c, n, field, typ, outputs)
@@ -482,7 +482,7 @@ proc findDefinition(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym
482482
if s.isNil: return
483483
if isTracked(info, g.config.m.trackPos, s.name.s.len) or (s == usageSym and sfForward notin s.flags):
484484
suggestResult(g.config, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0, useSuppliedInfo = s == usageSym))
485-
if sfForward notin s.flags:
485+
if sfForward notin s.flags and g.config.suggestVersion != 3:
486486
suggestQuit()
487487
else:
488488
usageSym = s
@@ -497,6 +497,8 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i
497497
## misnamed: should be 'symDeclared'
498498
let conf = g.config
499499
when defined(nimsuggest):
500+
g.suggestSymbols.mgetOrPut(info.fileIndex, @[]).add (s, info)
501+
500502
if conf.suggestVersion == 0:
501503
if s.allUsages.len == 0:
502504
s.allUsages = @[info]

0 commit comments

Comments
 (0)