Skip to content

Commit 9a0edcd

Browse files
yyonchoEmilIvanichkovv
authored andcommitted
Initial implementation of nimsuggest v3 (nim-lang#19826) [backport] (nim-lang#19892)
* Initial implementation of nimsuggest v3 (nim-lang#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 * Fixed bad cherry-pick resolve * modulegraphs.dependsOn does not work on transitive modules - make sure transitive deps are marked as dirty
1 parent 4a5cf4d commit 9a0edcd

File tree

7 files changed

+354
-36
lines changed

7 files changed

+354
-36
lines changed

compiler/modulegraphs.nim

+72-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
14+
import intsets, tables, hashes, md5, sequtils
1515
import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils
1616
import ic / [packed_ast, ic]
1717

@@ -81,6 +81,8 @@ type
8181
doStopCompile*: proc(): bool {.closure.}
8282
usageSym*: PSym # for nimsuggest
8383
owners*: seq[PSym]
84+
suggestSymbols*: Table[FileIndex, seq[tuple[sym: PSym, info: TLineInfo]]]
85+
suggestErrors*: Table[FileIndex, seq[Suggest]]
8486
methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization!
8587
systemModule*: PSym
8688
sysTypes*: array[TTypeKind, PType]
@@ -383,9 +385,19 @@ when defined(nimfind):
383385
c.graph.onDefinitionResolveForward(c.graph, s, info)
384386

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

390402
proc stopCompile*(g: ModuleGraph): bool {.inline.} =
391403
result = g.doStopCompile != nil and g.doStopCompile()
@@ -432,8 +444,7 @@ proc initOperators*(g: ModuleGraph): Operators =
432444
result.opNot = createMagic(g, "not", mNot)
433445
result.opContains = createMagic(g, "contains", mInSet)
434446

435-
proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
436-
result = ModuleGraph()
447+
proc initModuleGraphFields(result: ModuleGraph) =
437448
# A module ID of -1 means that the symbol is not attached to a module at all,
438449
# but to the module graph:
439450
result.idgen = IdGenerator(module: -1'i32, symId: 0'i32, typeId: 0'i32)
@@ -443,9 +454,9 @@ proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
443454
result.ifaces = @[]
444455
result.importStack = @[]
445456
result.inclToMod = initTable[FileIndex, FileIndex]()
446-
result.config = config
447-
result.cache = cache
448457
result.owners = @[]
458+
result.suggestSymbols = initTable[FileIndex, seq[tuple[sym: PSym, info: TLineInfo]]]()
459+
result.suggestErrors = initTable[FileIndex, seq[Suggest]]()
449460
result.methods = @[]
450461
initStrTable(result.compilerprocs)
451462
initStrTable(result.exposed)
@@ -459,6 +470,12 @@ proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
459470
result.operators = initOperators(result)
460471
result.emittedTypeInfo = initTable[string, FileIndex]()
461472

473+
proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
474+
result = ModuleGraph()
475+
result.config = config
476+
result.cache = cache
477+
initModuleGraphFields(result)
478+
462479
proc resetAllModules*(g: ModuleGraph) =
463480
initStrTable(g.packageSyms)
464481
g.deps = initIntSet()
@@ -470,6 +487,7 @@ proc resetAllModules*(g: ModuleGraph) =
470487
g.methods = @[]
471488
initStrTable(g.compilerprocs)
472489
initStrTable(g.exposed)
490+
initModuleGraphFields(g)
473491

474492
proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
475493
if fileIdx.int32 >= 0:
@@ -548,7 +566,19 @@ proc transitiveClosure(g: var IntSet; n: int) =
548566

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

553583
proc markClientsDirty*(g: ModuleGraph; fileIdx: FileIndex) =
554584
# we need to mark its dependent modules D as dirty right away because after
@@ -558,14 +588,33 @@ proc markClientsDirty*(g: ModuleGraph; fileIdx: FileIndex) =
558588
g.invalidTransitiveClosure = false
559589
transitiveClosure(g.deps, g.ifaces.len)
560590

591+
# every module that *depends* on this file is also dirty:
592+
for i in 0i32..<g.ifaces.len.int32:
593+
if g.deps.contains(i.dependsOn(fileIdx.int)):
594+
let
595+
fi = FileIndex(i)
596+
module = g.getModule(fi)
597+
if module != nil and not g.isDirty(module):
598+
g.markDirty(fi)
599+
g.markClientsDirty(fi)
600+
601+
proc needsCompilation*(g: ModuleGraph): bool =
561602
# every module that *depends* on this file is also dirty:
562603
for i in 0i32..<g.ifaces.len.int32:
563604
let m = g.ifaces[i].module
564-
if m != nil and g.deps.contains(i.dependsOn(fileIdx.int)):
565-
incl m.flags, sfDirty
605+
if m != nil:
606+
if sfDirty in m.flags:
607+
return true
566608

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

570619
proc getBody*(g: ModuleGraph; s: PSym): PNode {.inline.} =
571620
result = s.ast[bodyPos]
@@ -594,3 +643,13 @@ proc onProcessing*(graph: ModuleGraph, fileIdx: FileIndex, moduleStatus: string,
594643
let fromModule2 = if fromModule != nil: $fromModule.name.s else: "(toplevel)"
595644
let mode = if isNimscript: "(nims) " else: ""
596645
rawMessage(conf, hintProcessing, "$#$# $#: $#: $#" % [mode, indent, fromModule2, moduleStatus, path])
646+
647+
iterator suggestSymbolsIter*(g: ModuleGraph): tuple[sym: PSym, info: TLineInfo] =
648+
for xs in g.suggestSymbols.values:
649+
for x in xs.deduplicate:
650+
yield x
651+
652+
iterator suggestErrorsIter*(g: ModuleGraph): Suggest =
653+
for xs in g.suggestErrors.values:
654+
for x in xs:
655+
yield x

compiler/options.nim

+11-2
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,9 @@ type
183183
# as far as usesWriteBarrier() is concerned
184184

185185
IdeCmd* = enum
186-
ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod,
187-
ideHighlight, ideOutline, ideKnown, ideMsg, ideProject
186+
ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideChkFile, ideMod,
187+
ideHighlight, ideOutline, ideKnown, ideMsg, ideProject, ideGlobalSymbols,
188+
ideRecompile, ideChanged
188189

189190
Feature* = enum ## experimental features; DO NOT RENAME THESE!
190191
implicitDeref,
@@ -991,12 +992,16 @@ proc parseIdeCmd*(s: string): IdeCmd =
991992
of "use": ideUse
992993
of "dus": ideDus
993994
of "chk": ideChk
995+
of "chkFile": ideChkFile
994996
of "mod": ideMod
995997
of "highlight": ideHighlight
996998
of "outline": ideOutline
997999
of "known": ideKnown
9981000
of "msg": ideMsg
9991001
of "project": ideProject
1002+
of "globalSymbols": ideGlobalSymbols
1003+
of "recompile": ideRecompile
1004+
of "changed": ideChanged
10001005
else: ideNone
10011006

10021007
proc `$`*(c: IdeCmd): string =
@@ -1007,13 +1012,17 @@ proc `$`*(c: IdeCmd): string =
10071012
of ideUse: "use"
10081013
of ideDus: "dus"
10091014
of ideChk: "chk"
1015+
of ideChkFile: "chkFile"
10101016
of ideMod: "mod"
10111017
of ideNone: "none"
10121018
of ideHighlight: "highlight"
10131019
of ideOutline: "outline"
10141020
of ideKnown: "known"
10151021
of ideMsg: "msg"
10161022
of ideProject: "project"
1023+
of ideGlobalSymbols: "globalSymbols"
1024+
of ideRecompile: "recompile"
1025+
of ideChanged: "changed"
10171026

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

compiler/passes.nim

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

1920
type
2021
TPassData* = tuple[input: PNode, closeOutput: PNode]
@@ -135,6 +136,11 @@ proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
135136
return false
136137
else:
137138
s = stream
139+
140+
when defined(nimsuggest):
141+
let filename = toFullPathConsiderDirty(graph.config, fileIdx).string
142+
msgs.setHash(graph.config, fileIdx, $sha1.secureHashFile(filename))
143+
138144
while true:
139145
openParser(p, fileIdx, s, graph.cache, graph.config)
140146

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)