summary refs log tree commit diff stats
path: root/nimsuggest
diff options
context:
space:
mode:
Diffstat (limited to 'nimsuggest')
-rw-r--r--nimsuggest/config.nims2
-rw-r--r--nimsuggest/nimsuggest.nim715
-rw-r--r--nimsuggest/nimsuggest.nimble12
-rw-r--r--nimsuggest/procmonitor.nim34
-rw-r--r--nimsuggest/sexp.nim19
-rw-r--r--nimsuggest/tester.nim72
-rw-r--r--nimsuggest/tests/module_20265.nim6
-rw-r--r--nimsuggest/tests/t20265_1.nim8
-rw-r--r--nimsuggest/tests/t20265_2.nim8
-rw-r--r--nimsuggest/tests/t20440.nim7
-rw-r--r--nimsuggest/tests/t20440.nims1
-rw-r--r--nimsuggest/tests/t21185.nim18
-rw-r--r--nimsuggest/tests/t22448.nim11
-rw-r--r--nimsuggest/tests/tarrowcrash.nim20
-rw-r--r--nimsuggest/tests/tchk1.nim4
-rw-r--r--nimsuggest/tests/tchk2.nim35
-rw-r--r--nimsuggest/tests/tchk_compiles.nim2
-rw-r--r--nimsuggest/tests/tconcept1.nim12
-rw-r--r--nimsuggest/tests/tconcept2.nim15
-rw-r--r--nimsuggest/tests/tdef1.nim10
-rw-r--r--nimsuggest/tests/tdef_let.nim7
-rw-r--r--nimsuggest/tests/tdot4.nim2
-rw-r--r--nimsuggest/tests/tenum_field.nim17
-rw-r--r--nimsuggest/tests/tfatal1.nim15
-rw-r--r--nimsuggest/tests/tgeneric_highlight.nim7
-rw-r--r--nimsuggest/tests/tgenerics.nim18
-rw-r--r--nimsuggest/tests/tic.nim20
-rw-r--r--nimsuggest/tests/timport_highlight.nim12
-rw-r--r--nimsuggest/tests/tinclude.nim2
-rw-r--r--nimsuggest/tests/tqualified_highlight.nim2
-rw-r--r--nimsuggest/tests/tsug_pragmas.nim40
-rw-r--r--nimsuggest/tests/tsug_recursive.nim8
-rw-r--r--nimsuggest/tests/tsug_template.nim2
-rw-r--r--nimsuggest/tests/tsug_typedecl.nim4
-rw-r--r--nimsuggest/tests/ttempl_inst.nim2
-rw-r--r--nimsuggest/tests/ttype_decl.nim4
-rw-r--r--nimsuggest/tests/tuse.nim8
-rw-r--r--nimsuggest/tests/tuse_enum.nim15
-rw-r--r--nimsuggest/tests/tuse_structure.nim15
-rw-r--r--nimsuggest/tests/tv3.nim27
-rw-r--r--nimsuggest/tests/tv3_con.nim13
-rw-r--r--nimsuggest/tests/tv3_definition.nim9
-rw-r--r--nimsuggest/tests/tv3_forward_definition.nim23
-rw-r--r--nimsuggest/tests/tv3_generics.nim18
-rw-r--r--nimsuggest/tests/tv3_globalSymbols.nim14
-rw-r--r--nimsuggest/tests/tv3_import.nim7
-rw-r--r--nimsuggest/tests/tv3_outline.nim45
-rw-r--r--nimsuggest/tests/tv3_typeDefinition.nim32
-rw-r--r--nimsuggest/tests/twithin_macro.nim4
-rw-r--r--nimsuggest/tests/twithin_macro_prefix.nim2
50 files changed, 1293 insertions, 112 deletions
diff --git a/nimsuggest/config.nims b/nimsuggest/config.nims
new file mode 100644
index 000000000..ee19f9893
--- /dev/null
+++ b/nimsuggest/config.nims
@@ -0,0 +1,2 @@
+# xxx not sure why this flag isn't needed: switch("processing", "filenames")
+switch("filenames", "canonical")
diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim
index c139b8b17..04bae08c1 100644
--- a/nimsuggest/nimsuggest.nim
+++ b/nimsuggest/nimsuggest.nim
@@ -7,6 +7,24 @@
 #    distribution, for details about the copyright.
 #
 
+import compiler/renderer
+import compiler/types
+import compiler/trees
+import compiler/wordrecg
+import compiler/sempass2
+import strformat
+import algorithm
+import tables
+import times
+import procmonitor
+
+template tryImport(module) = import module
+
+when compiles tryImport ../dist/checksums/src/checksums/sha1:
+  import ../dist/checksums/src/checksums/sha1
+else:
+  import checksums/sha1
+
 ## Nimsuggest is a tool that helps to give editors IDE like capabilities.
 
 when not defined(nimcore):
@@ -16,17 +34,21 @@ import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp
 # Do NOT import suggest. It will lead to weird bugs with
 # suggestionResultHook, because suggest.nim is included by sigmatch.
 # So we import that one instead.
-import compiler / [options, commands, modules, sem,
+import compiler / [options, commands, modules,
   passes, passaux, msgs,
   sigmatch, ast,
   idents, modulegraphs, prefixmatches, lineinfos, cmdlinehelper,
-  pathutils]
+  pathutils, condsyms, syntaxes, suggestsymdb]
+
+when defined(nimPreviewSlimSystem):
+  import std/typedthreads
 
 when defined(windows):
   import winlean
 else:
   import posix
 
+const HighestSuggestProtocolVersion = 4
 const DummyEof = "!EOF!"
 const Usage = """
 Nimsuggest - Tool to give every editor IDE like capabilities for Nim
@@ -39,15 +61,25 @@ Options:
   --address:HOST          binds to that address, by default ""
   --stdin                 read commands from stdin and write results to
                           stdout instead of using sockets
+  --clientProcessId:PID   shutdown nimsuggest in case this process dies
   --epc                   use emacs epc mode
   --debug                 enable debug output
   --log                   enable verbose logging to nimsuggest.log file
   --v1                    use version 1 of the protocol; for backwards compatibility
+  --v2                    use version 2(default) of the protocol
+  --v3                    use version 3 of the protocol
+  --v4                    use version 4 of the protocol
+  --info:X                information
+    --info:nimVer           return the Nim compiler version that nimsuggest uses internally
+    --info:protocolVer      return the newest protocol version that is supported
+    --info:capabilities     return the capabilities supported by nimsuggest
   --refresh               perform automatic refreshes to keep the analysis precise
   --maxresults:N          limit the number of suggestions to N
   --tester                implies --stdin and outputs a line
                           '""" & DummyEof & """' for the tester
   --find                  attempts to find the project file of the current project
+  --exceptionInlayHints:on|off
+                          globally turn exception inlay hints on|off
 
 The server then listens to the connection and takes line-based commands.
 
@@ -66,7 +98,7 @@ type
 
 var
   gPort = 6000.Port
-  gAddress = ""
+  gAddress = "127.0.0.1"
   gMode: Mode
   gEmitEof: bool # whether we write '!EOF!' dummy lines
   gLogging = defined(logging)
@@ -76,6 +108,9 @@ var
   requests: Channel[string]
   results: Channel[Suggest]
 
+proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string,
+  graph: ModuleGraph);
+
 proc writelnToChannel(line: string) =
   results.send(Suggest(section: ideMsg, doc: line))
 
@@ -96,6 +131,12 @@ const
          "type 'quit' to quit\n" &
          "type 'debug' to toggle debug mode on/off\n" &
          "type 'terse' to toggle terse mode on/off"
+  #List of currently supported capabilities. So lang servers/ides can iterate over and check for what's enabled
+  Capabilities = [
+    "con", #current NimSuggest supports the `con` commmand
+    "exceptionInlayHints",
+    "unknownFile", #current NimSuggest can handle unknown files
+  ]
 
 proc parseQuoted(cmd: string; outp: var string; start: int): int =
   var i = start
@@ -125,6 +166,9 @@ proc sexp(s: Suggest): SexpNode =
   ])
   if s.section == ideSug:
     result.add convertSexp(s.prefix)
+  if s.section in {ideOutline, ideExpand} and s.version == 3:
+    result.add convertSexp(s.endLine.int)
+    result.add convertSexp(s.endCol)
 
 proc sexp(s: seq[Suggest]): SexpNode =
   result = newSList()
@@ -137,7 +181,7 @@ proc listEpc(): SexpNode =
     argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
     docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
   result = newSList()
-  for command in ["sug", "con", "def", "use", "dus", "chk", "mod"]:
+  for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration", "inlayHints"]:
     let
       cmd = sexp(command)
       methodDesc = newSList()
@@ -160,9 +204,51 @@ proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym =
   if m != nil and m.ast != nil:
     result = findNode(m.ast, trackPos)
 
-proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
+template benchmark(benchmarkName: untyped, code: untyped) =
+  block:
+    myLog "Started [" & benchmarkName & "]..."
+    let t0 = epochTime()
+    code
+    let elapsed = epochTime() - t0
+    let elapsedStr = elapsed.formatFloat(format = ffDecimal, precision = 3)
+    myLog "CPU Time [" & benchmarkName & "] " & elapsedStr & "s"
+
+proc clearInstCache(graph: ModuleGraph, projectFileIdx: FileIndex) =
+  if projectFileIdx == InvalidFileIdx:
+    graph.typeInstCache.clear()
+    graph.procInstCache.clear()
+    return
+  var typeIdsToDelete = newSeq[ItemId]()
+  for id in graph.typeInstCache.keys:
+    if id.module == projectFileIdx.int:
+      typeIdsToDelete.add id
+  for id in typeIdsToDelete:
+    graph.typeInstCache.del id
+  var procIdsToDelete = newSeq[ItemId]()
+  for id in graph.procInstCache.keys:
+    if id.module == projectFileIdx.int:
+      procIdsToDelete.add id
+  for id in procIdsToDelete:
+    graph.procInstCache.del id
+
+  for tbl in mitems(graph.attachedOps):
+    var attachedOpsToDelete = newSeq[ItemId]()
+    for id in tbl.keys:
+      if id.module == projectFileIdx.int and sfOverridden in resolveAttachedOp(graph, tbl[id]).flags:
+        attachedOpsToDelete.add id
+    for id in attachedOpsToDelete:
+      tbl.del id
+
+proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, tag: string,
              graph: ModuleGraph) =
   let conf = graph.config
+
+  if conf.suggestVersion >= 3:
+    let command = fmt "cmd = {cmd} {file}:{line}:{col}"
+    benchmark command:
+      executeNoHooksV3(cmd, file, dirtyfile, line, col, tag, graph)
+    return
+
   myLog("cmd: " & $cmd & ", file: " & file.string &
         ", dirtyFile: " & dirtyfile.string &
         "[" & $line & ":" & $col & "]")
@@ -181,6 +267,7 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
   if conf.suggestVersion == 1:
     graph.usageSym = nil
   if not isKnownFile:
+    graph.clearInstCache(dirtyIdx)
     graph.compileProject(dirtyIdx)
   if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and
       dirtyfile.isEmpty:
@@ -191,6 +278,7 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
     graph.markClientsDirty dirtyIdx
     if conf.ideCmd != ideMod:
       if isKnownFile:
+        graph.clearInstCache(modIdx)
         graph.compileProject(modIdx)
   if conf.ideCmd in {ideUse, ideDus}:
     let u = if conf.suggestVersion != 1: graph.symFromInfo(conf.m.trackPos) else: graph.usageSym
@@ -199,7 +287,10 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
     else:
       localError(conf, conf.m.trackPos, "found no symbol at this position " & (conf $ conf.m.trackPos))
 
-proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
+proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, graph: ModuleGraph) =
+  executeNoHooks(cmd, file, dirtyfile, line, col, "", graph)
+
+proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; tag: string,
              graph: ModuleGraph) =
   if cmd == ideChk:
     graph.config.structuredErrorHook = errorHook
@@ -207,7 +298,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
   else:
     graph.config.structuredErrorHook = nil
     graph.config.writelnHook = myLog
-  executeNoHooks(cmd, file, dirtyfile, line, col, graph)
+  executeNoHooks(cmd, file, dirtyfile, line, col, tag, graph)
 
 proc executeEpc(cmd: IdeCmd, args: SexpNode;
                 graph: ModuleGraph) =
@@ -218,7 +309,7 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode;
   var dirtyfile = AbsoluteFile""
   if len(args) > 3:
     dirtyfile = AbsoluteFile args[3].getStr("")
-  execute(cmd, file, dirtyfile, int(line), int(column), graph)
+  execute(cmd, file, dirtyfile, int(line), int(column), args[3].getStr, graph)
 
 proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
                returnSymbol = "return") =
@@ -436,6 +527,18 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
   of "terse": toggle optIdeTerse
   of "known": conf.ideCmd = ideKnown
   of "project": conf.ideCmd = ideProject
+  of "changed": conf.ideCmd = ideChanged
+  of "globalsymbols": conf.ideCmd = ideGlobalSymbols
+  of "declaration": conf.ideCmd = ideDeclaration
+  of "expand": conf.ideCmd = ideExpand
+  of "chkfile": conf.ideCmd = ideChkFile
+  of "recompile": conf.ideCmd = ideRecompile
+  of "type": conf.ideCmd = ideType
+  of "inlayhints":
+    if conf.suggestVersion >= 4:
+      conf.ideCmd = ideInlayHints
+    else:
+      err()
   else: err()
   var dirtyfile = ""
   var orig = ""
@@ -452,6 +555,7 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
   i += parseInt(cmd, line, i)
   i += skipWhile(cmd, seps, i)
   i += parseInt(cmd, col, i)
+  let tag = substr(cmd, i)
 
   if conf.ideCmd == ideKnown:
     results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, AbsoluteFile orig))))
@@ -460,23 +564,23 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
   else:
     if conf.ideCmd == ideChk:
       for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev)
-    execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, graph)
+    execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, tag, graph)
   sentinel()
 
 proc recompileFullProject(graph: ModuleGraph) =
-  #echo "recompiling full project"
-  resetSystemArtifacts(graph)
-  graph.vm = nil
-  graph.resetAllModules()
-  GC_fullCollect()
-  compileProject(graph)
-  #echo GC_getStatistics()
+  benchmark "Recompilation(clean)":
+    graph.resetForBackend()
+    graph.resetSystemArtifacts()
+    graph.vm = nil
+    graph.resetAllModules()
+    GC_fullCollect()
+    graph.compileProject()
 
 proc mainThread(graph: ModuleGraph) =
   let conf = graph.config
-  if gLogging:
-    for it in conf.searchPaths:
-      log(it.string)
+  myLog "searchPaths: "
+  for it in conf.searchPaths:
+    myLog("  " & it.string)
 
   proc wrHook(line: string) {.closure.} =
     if gMode == mepc:
@@ -499,7 +603,7 @@ proc mainThread(graph: ModuleGraph) =
     else:
       os.sleep 250
       idle += 1
-    if idle == 20 and gRefresh:
+    if idle == 20 and gRefresh and conf.suggestVersion < 3:
       # we use some nimsuggest activity to enable a lazy recompile:
       conf.ideCmd = ideChk
       conf.writelnHook = proc (s: string) = discard
@@ -518,6 +622,7 @@ proc mainCommand(graph: ModuleGraph) =
   registerPass graph, verbosePass
   registerPass graph, semPass
   conf.setCmd cmdIdeTools
+  defineSymbol(conf.symbols, $conf.backend)
   wantMainModule(conf)
 
   if not fileExists(conf.projectFull):
@@ -527,16 +632,25 @@ proc mainCommand(graph: ModuleGraph) =
 
   conf.setErrorMaxHighMaybe # honor --errorMax even if it may not make sense here
   # do not print errors, but log them
-  conf.writelnHook = myLog
-  conf.structuredErrorHook = nil
+  conf.writelnHook = proc (msg: string) = discard
+
+  if graph.config.suggestVersion >= 3:
+    graph.config.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) =
+      let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info),
+        line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev)
+      graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest
 
   # compile the project before showing any input so that we already
   # can answer questions right away:
-  compileProject(graph)
+  benchmark "Initial compilation":
+    compileProject(graph)
 
   open(requests)
   open(results)
 
+  if graph.config.clientProcessId != 0:
+    hookProcMonitor(graph.config.clientProcessId)
+
   case gMode
   of mstdin: createThread(inputThread, replStdin, (gPort, gAddress))
   of mtcp: createThread(inputThread, replTcp, (gPort, gAddress))
@@ -584,8 +698,28 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) =
         gMode = mepc
         conf.verbosity = 0          # Port number gotta be first.
       of "debug": incl(conf.globalOptions, optIdeDebug)
-      of "v2": conf.suggestVersion = 0
       of "v1": conf.suggestVersion = 1
+      of "v2": conf.suggestVersion = 0
+      of "v3": conf.suggestVersion = 3
+      of "v4": conf.suggestVersion = 4
+      of "info":
+        case p.val.normalize
+        of "protocolver":
+          stdout.writeLine(HighestSuggestProtocolVersion)
+          quit 0
+        of "nimver":
+          stdout.writeLine(system.NimVersion)
+          quit 0
+        of "capabilities":
+          stdout.writeLine(Capabilities.toSeq.mapIt($it).join(" "))
+          quit 0
+        else:
+          processSwitch(pass, p, conf)
+      of "exceptioninlayhints":
+        case p.val.normalize
+        of "", "on": incl(conf.globalOptions, optIdeExceptionInlayHints)
+        of "off": excl(conf.globalOptions, optIdeExceptionInlayHints)
+        else: processSwitch(pass, p, conf)
       of "tester":
         gMode = mstdin
         gEmitEof = true
@@ -600,6 +734,8 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) =
         conf.suggestMaxResults = parseInt(p.val)
       of "find":
         findProject = true
+      of "clientprocessid":
+        conf.clientProcessId = parseInt(p.val)
       else: processSwitch(pass, p, conf)
     of cmdArgument:
       let a = unixToNativePath(p.key)
@@ -631,22 +767,520 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
 
   if gMode != mstdin:
     conf.writelnHook = proc (msg: string) = discard
-  # Find Nim's prefix dir.
-  let binaryPath = findExe("nim")
-  if binaryPath == "":
-    raise newException(IOError,
-        "Cannot find Nim standard library: Nim compiler not in PATH")
-  conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir()
-  if not dirExists(conf.prefixDir / RelativeDir"lib"):
-    conf.prefixDir = AbsoluteDir""
-
+  conf.prefixDir = conf.getPrefixDir()
   #msgs.writelnHook = proc (line: string) = log(line)
   myLog("START " & conf.projectFull.string)
 
   var graph = newModuleGraph(cache, conf)
   if self.loadConfigsAndProcessCmdLine(cache, conf, graph):
+
+    if conf.selectedGC == gcUnselected and
+          conf.backend != backendJs:
+      initOrcDefines(conf)
     mainCommand(graph)
 
+# v3 start
+
+proc recompilePartially(graph: ModuleGraph, projectFileIdx = InvalidFileIdx) =
+  if projectFileIdx == InvalidFileIdx:
+    myLog "Recompiling partially from root"
+  else:
+    myLog fmt "Recompiling partially starting from {graph.getModule(projectFileIdx)}"
+
+  # inst caches are breaking incremental compilation when the cache caches stuff
+  # from dirty buffer
+  graph.clearInstCache(projectFileIdx)
+
+  GC_fullCollect()
+
+  try:
+    benchmark "Recompilation":
+      graph.compileProject(projectFileIdx)
+  except Exception as e:
+    myLog fmt "Failed to recompile partially with the following error:\n {e.msg} \n\n {e.getStackTrace()}"
+    try:
+      graph.recompileFullProject()
+    except Exception as e:
+      myLog fmt "Failed clean recompilation:\n {e.msg} \n\n {e.getStackTrace()}"
+
+func deduplicateSymInfoPair[SymInfoPair](xs: seq[SymInfoPair]): seq[SymInfoPair] =
+  # xs contains duplicate items and we want to filter them by range because the
+  # sym may not match. This can happen when xs contains the same definition but
+  # with different signature because suggestSym might be called multiple times
+  # for the same symbol (e. g. including/excluding the pragma)
+  result = newSeqOfCap[SymInfoPair](xs.len)
+  for itm in xs.reversed:
+    var found = false
+    for res in result:
+      if res.info.exactEquals(itm.info):
+        found = true
+        break
+    if not found:
+      result.add(itm)
+  result.reverse()
+
+func deduplicateSymInfoPair(xs: SuggestFileSymbolDatabase): SuggestFileSymbolDatabase =
+  # xs contains duplicate items and we want to filter them by range because the
+  # sym may not match. This can happen when xs contains the same definition but
+  # with different signature because suggestSym might be called multiple times
+  # for the same symbol (e. g. including/excluding the pragma)
+  result = SuggestFileSymbolDatabase(
+    lineInfo: newSeqOfCap[TinyLineInfo](xs.lineInfo.len),
+    sym: newSeqOfCap[PSym](xs.sym.len),
+    isDecl: newPackedBoolArray(),
+    caughtExceptions: newSeqOfCap[seq[PType]](xs.caughtExceptions.len),
+    caughtExceptionsSet: newPackedBoolArray(),
+    fileIndex: xs.fileIndex,
+    trackCaughtExceptions: xs.trackCaughtExceptions,
+    isSorted: false
+  )
+  var i = xs.lineInfo.high
+  while i >= 0:
+    let itm = xs.lineInfo[i]
+    var found = false
+    for res in result.lineInfo:
+      if res.exactEquals(itm):
+        found = true
+        break
+    if not found:
+      result.add(xs.getSymInfoPair(i))
+    dec i
+  result.reverse()
+
+proc findSymData(graph: ModuleGraph, trackPos: TLineInfo):
+    ref SymInfoPair =
+  let db = graph.fileSymbols(trackPos.fileIndex).deduplicateSymInfoPair
+  doAssert(db.fileIndex == trackPos.fileIndex)
+  for i in db.lineInfo.low..db.lineInfo.high:
+    if isTracked(db.lineInfo[i], TinyLineInfo(line: trackPos.line, col: trackPos.col), db.sym[i].name.s.len):
+      var res = db.getSymInfoPair(i)
+      new(result)
+      result[] = res
+      break
+
+func isInRange*(current, startPos, endPos: TinyLineInfo, tokenLen: int): bool =
+  result =
+    (current.line > startPos.line or (current.line == startPos.line and current.col>=startPos.col)) and
+    (current.line < endPos.line or (current.line == endPos.line and current.col <= endPos.col))
+
+proc findSymDataInRange(graph: ModuleGraph, startPos, endPos: TLineInfo):
+    seq[SymInfoPair] =
+  result = newSeq[SymInfoPair]()
+  let db = graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair
+  for i in db.lineInfo.low..db.lineInfo.high:
+    if isInRange(db.lineInfo[i], TinyLineInfo(line: startPos.line, col: startPos.col), TinyLineInfo(line: endPos.line, col: endPos.col), db.sym[i].name.s.len):
+      result.add(db.getSymInfoPair(i))
+
+proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int):
+    ref SymInfoPair =
+  let
+    fileIdx = fileInfoIdx(graph.config, file)
+    trackPos = newLineInfo(fileIdx, line, col)
+  result = findSymData(graph, trackPos)
+
+proc findSymDataInRange(graph: ModuleGraph, file: AbsoluteFile; startLine, startCol, endLine, endCol: int):
+    seq[SymInfoPair] =
+  let
+    fileIdx = fileInfoIdx(graph.config, file)
+    startPos = newLineInfo(fileIdx, startLine, startCol)
+    endPos = newLineInfo(fileIdx, endLine, endCol)
+  result = findSymDataInRange(graph, startPos, endPos)
+
+proc markDirtyIfNeeded(graph: ModuleGraph, file: string, originalFileIdx: FileIndex) =
+  let sha = $sha1.secureHashFile(file)
+  if graph.config.m.fileInfos[originalFileIdx.int32].hash != sha or graph.config.ideCmd in {ideSug, ideCon}:
+    myLog fmt "{file} changed compared to last compilation"
+    graph.markDirty originalFileIdx
+    graph.markClientsDirty originalFileIdx
+  else:
+    myLog fmt "No changes in file {file} compared to last compilation"
+
+proc suggestResult(graph: ModuleGraph, sym: PSym, info: TLineInfo,
+                   defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) =
+  let section = if defaultSection != ideNone:
+                  defaultSection
+                elif sym.info.exactEquals(info):
+                  ideDef
+                else:
+                  ideUse
+  let suggest = symToSuggest(graph, sym, isLocal=false, section,
+                             info, 100, PrefixMatch.None, false, 0,
+                             endLine = endLine, endCol = endCol)
+  suggestResult(graph.config, suggest)
+
+proc suggestInlayHintResultType(graph: ModuleGraph, sym: PSym, info: TLineInfo,
+                   defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) =
+  let section = if defaultSection != ideNone:
+                  defaultSection
+                elif sym.info.exactEquals(info):
+                  ideDef
+                else:
+                  ideUse
+  var suggestDef = symToSuggest(graph, sym, isLocal=false, section,
+                                info, 100, PrefixMatch.None, false, 0, true,
+                                endLine = endLine, endCol = endCol)
+  suggestDef.inlayHintInfo = suggestToSuggestInlayTypeHint(suggestDef)
+  suggestDef.section = ideInlayHints
+  if sym.kind == skForVar:
+    suggestDef.inlayHintInfo.allowInsert = false
+  suggestResult(graph.config, suggestDef)
+
+proc suggestInlayHintResultException(graph: ModuleGraph, sym: PSym, info: TLineInfo,
+                   defaultSection = ideNone, caughtExceptions: seq[PType], caughtExceptionsSet: bool, endLine: uint16 = 0, endCol = 0) =
+  if not caughtExceptionsSet:
+    return
+
+  if sym.kind == skParam and sfEffectsDelayed in sym.flags:
+    return
+
+  var raisesList: seq[PType] = @[getEbase(graph, info)]
+
+  let t = sym.typ
+  if not isNil(t) and not isNil(t.n) and t.n.len > 0 and t.n[0].len > exceptionEffects:
+    let effects = t.n[0]
+    if effects.kind == nkEffectList and effects.len == effectListLen:
+      let effs = effects[exceptionEffects]
+      if not isNil(effs):
+        raisesList = @[]
+        for eff in items(effs):
+          if not isNil(eff):
+            raisesList.add(eff.typ)
+
+  var propagatedExceptionList: seq[PType] = @[]
+  for re in raisesList:
+    var exceptionIsPropagated = true
+    for ce in caughtExceptions:
+      if isNil(ce) or safeInheritanceDiff(re, ce) <= 0:
+        exceptionIsPropagated = false
+        break
+    if exceptionIsPropagated:
+      propagatedExceptionList.add(re)
+
+  if propagatedExceptionList.len == 0:
+    return
+
+  let section = if defaultSection != ideNone:
+                  defaultSection
+                elif sym.info.exactEquals(info):
+                  ideDef
+                else:
+                  ideUse
+  var suggestDef = symToSuggest(graph, sym, isLocal=false, section,
+                                info, 100, PrefixMatch.None, false, 0, true,
+                                endLine = endLine, endCol = endCol)
+  suggestDef.inlayHintInfo = suggestToSuggestInlayExceptionHintLeft(suggestDef, propagatedExceptionList)
+  suggestDef.section = ideInlayHints
+  suggestResult(graph.config, suggestDef)
+  suggestDef.inlayHintInfo = suggestToSuggestInlayExceptionHintRight(suggestDef, propagatedExceptionList)
+  suggestResult(graph.config, suggestDef)
+
+const
+  # kinds for ideOutline and ideGlobalSymbols
+  searchableSymKinds = {skField, skEnumField, skIterator, skMethod, skFunc, skProc, skConverter, skTemplate}
+
+proc symbolEqual(left, right: PSym): bool =
+  # More relaxed symbol comparison
+  return left.info.exactEquals(right.info) and left.name == right.name
+
+proc findDef(n: PNode, line: uint16, col: int16): PNode =
+  if n.kind in {nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkMacroDef}:
+    if n.info.line == line:
+      return n
+  else:
+    for i in 0 ..< safeLen(n):
+      let res = findDef(n[i], line, col)
+      if res != nil: return res
+
+proc findByTLineInfo(trackPos: TLineInfo, infoPairs: SuggestFileSymbolDatabase):
+    ref SymInfoPair =
+  result = nil
+  if infoPairs.fileIndex == trackPos.fileIndex:
+    for i in infoPairs.lineInfo.low..infoPairs.lineInfo.high:
+      let s = infoPairs.getSymInfoPair(i)
+      if s.info.exactEquals trackPos:
+        new(result)
+        result[] = s
+        break
+
+proc outlineNode(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: SuggestFileSymbolDatabase): bool =
+  proc checkSymbol(sym: PSym, info: TLineInfo): bool =
+    result = (sym.owner.kind in {skModule, skType} or sym.kind in {skProc, skMethod, skIterator, skTemplate, skType})
+
+  if n.kind == nkSym and n.sym.checkSymbol(n.info):
+    graph.suggestResult(n.sym, n.sym.info, ideOutline, endInfo.line, endInfo.col)
+    return true
+  elif n.kind == nkIdent:
+    let symData = findByTLineInfo(n.info, infoPairs)
+    if symData != nil and symData.sym.checkSymbol(symData.info):
+       let sym = symData.sym
+       graph.suggestResult(sym, sym.info, ideOutline, endInfo.line, endInfo.col)
+       return true
+
+proc handleIdentOrSym(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: SuggestFileSymbolDatabase): bool =
+  for child in n:
+    if child.kind in {nkIdent, nkSym}:
+      if graph.outlineNode(child, endInfo, infoPairs):
+        return true
+    elif child.kind == nkPostfix:
+      if graph.handleIdentOrSym(child, endInfo, infoPairs):
+        return true
+
+proc iterateOutlineNodes(graph: ModuleGraph, n: PNode, infoPairs: SuggestFileSymbolDatabase) =
+  var matched = true
+  if n.kind == nkIdent:
+    let symData = findByTLineInfo(n.info, infoPairs)
+    if symData != nil and symData.sym.kind == skEnumField and symData.info.exactEquals(symData.sym.info):
+       let sym = symData.sym
+       graph.suggestResult(sym, sym.info, ideOutline, n.endInfo.line, n.endInfo.col)
+  elif (n.kind in {nkFuncDef, nkProcDef, nkTypeDef, nkMacroDef, nkTemplateDef, nkConverterDef, nkEnumFieldDef, nkConstDef}):
+    matched = handleIdentOrSym(graph, n, n.endInfo, infoPairs)
+  else:
+    matched = false
+
+  if n.kind != nkFormalParams:
+    for child in n:
+      graph.iterateOutlineNodes(child, infoPairs)
+
+proc calculateExpandRange(n: PNode, info: TLineInfo): TLineInfo =
+  if ((n.kind in {nkFuncDef, nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkConverterDef} and
+          n.info.exactEquals(info)) or
+         (n.kind in {nkCall, nkCommand} and n[0].info.exactEquals(info))):
+    result = n.endInfo
+  else:
+    for child in n:
+      result = child.calculateExpandRange(info)
+      if result != unknownLineInfo:
+        return result
+    result = unknownLineInfo
+
+proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string,
+    graph: ModuleGraph) =
+  let conf = graph.config
+  conf.writelnHook = proc (s: string) = discard
+  conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo;
+                                   msg: string; sev: Severity) =
+    let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info),
+      line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev)
+    graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest
+
+  conf.ideCmd = cmd
+
+  myLog fmt "cmd: {cmd}, file: {file}[{line}:{col}], dirtyFile: {dirtyfile}, tag: {tag}"
+
+  var fileIndex: FileIndex
+
+  if not (cmd in {ideRecompile, ideGlobalSymbols}):
+    fileIndex = fileInfoIdx(conf, file)
+    msgs.setDirtyFile(
+      conf,
+      fileIndex,
+      if dirtyfile.isEmpty: AbsoluteFile"" else: dirtyfile)
+
+    if not dirtyfile.isEmpty:
+      graph.markDirtyIfNeeded(dirtyFile.string, fileInfoIdx(conf, file))
+
+  # these commands require fully compiled project
+  if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk, ideInlayHints} and graph.needsCompilation():
+    graph.recompilePartially()
+    # when doing incremental build for the project root we should make sure that
+    # everything is unmarked as no longer beeing dirty in case there is no
+    # longer reference to a particular module. E. g. A depends on B, B is marked
+    # as dirty and A loses B import.
+    graph.unmarkAllDirty()
+
+  # 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}):
+    # 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)
+
+  case cmd
+  of ideDef:
+    let s = graph.findSymData(file, line, col)
+    if not s.isNil:
+      graph.suggestResult(s.sym, s.sym.info)
+  of ideType:
+    let s = graph.findSymData(file, line, col)
+    if not s.isNil:
+      let typeSym = s.sym.typ.sym
+      if typeSym != nil:
+        graph.suggestResult(typeSym, typeSym.info, ideType)
+      elif s.sym.typ.len != 0:
+        let genericType = s.sym.typ[0].sym
+        graph.suggestResult(genericType, genericType.info, ideType)
+  of ideUse, ideDus:
+    let symbol = graph.findSymData(file, line, col)
+    if not symbol.isNil:
+      var res: seq[SymInfoPair] = @[]
+      for s in graph.suggestSymbolsIter:
+        if s.sym.symbolEqual(symbol.sym):
+          res.add(s)
+      for s in res.deduplicateSymInfoPair():
+        graph.suggestResult(s.sym, s.info)
+  of ideHighlight:
+    let sym = graph.findSymData(file, line, col)
+    if not sym.isNil:
+      let fs = graph.fileSymbols(fileIndex)
+      var usages: seq[SymInfoPair] = @[]
+      for i in fs.lineInfo.low..fs.lineInfo.high:
+        if fs.sym[i] == sym.sym:
+          usages.add(fs.getSymInfoPair(i))
+      myLog fmt "Found {usages.len} usages in {file.string}"
+      for s in usages:
+        graph.suggestResult(s.sym, s.info)
+  of ideRecompile:
+    graph.recompileFullProject()
+  of ideChanged:
+    graph.markDirtyIfNeeded(file.string, fileIndex)
+  of ideSug, ideCon:
+    # ideSug/ideCon performs partial build of the file, thus mark it dirty for the
+    # future calls.
+    graph.markDirtyIfNeeded(file.string, fileIndex)
+    graph.recompilePartially(fileIndex) 
+    let m = graph.getModule fileIndex
+    incl m.flags, sfDirty 
+  of ideOutline:
+    let n = parseFile(fileIndex, graph.cache, graph.config)
+    graph.iterateOutlineNodes(n, graph.fileSymbols(fileIndex).deduplicateSymInfoPair)
+  of ideChk:
+    myLog fmt "Reporting errors for {graph.suggestErrors.len} file(s)"
+    for sug in graph.suggestErrorsIter:
+      suggestResult(graph.config, sug)
+  of ideChkFile:
+    let errors = graph.suggestErrors.getOrDefault(fileIndex, @[])
+    myLog fmt "Reporting {errors.len} error(s) for {file.string}"
+    for error in errors:
+      suggestResult(graph.config, error)
+  of ideGlobalSymbols:
+    var
+      counter = 0
+      res: seq[SymInfoPair] = @[]
+
+    for s in graph.suggestSymbolsIter:
+      if (sfGlobal in s.sym.flags or s.sym.kind in searchableSymKinds) and
+          s.sym.info == s.info:
+        if contains(s.sym.name.s, file.string):
+          inc counter
+          res = res.filterIt(not it.info.exactEquals(s.info))
+          res.add s
+          # stop after first 1000 matches...
+          if counter > 1000:
+            break
+
+    # ... then sort them by weight ...
+    res.sort() do (left, right: SymInfoPair) -> int:
+      let
+        leftString = left.sym.name.s
+        rightString = right.sym.name.s
+        leftIndex = leftString.find(file.string)
+        rightIndex = rightString.find(file.string)
+
+      if leftIndex == rightIndex:
+        result = cmp(toLowerAscii(leftString),
+                     toLowerAscii(rightString))
+      else:
+        result = cmp(leftIndex, rightIndex)
+
+    # ... and send first 100 results
+    if res.len > 0:
+      for i in 0 .. min(100, res.len - 1):
+        let s = res[i]
+        graph.suggestResult(s.sym, s.info)
+
+  of ideDeclaration:
+    let s = graph.findSymData(file, line, col)
+    if not s.isNil:
+      # find first mention of the symbol in the file containing the definition.
+      # It is either the definition or the declaration.
+      var first: SymInfoPair
+      let db = graph.fileSymbols(s.sym.info.fileIndex).deduplicateSymInfoPair
+      for i in db.lineInfo.low..db.lineInfo.high:
+        if s.sym.symbolEqual(db.sym[i]):
+          first = db.getSymInfoPair(i)
+          break
+
+      if s.info.exactEquals(first.info):
+        # we are on declaration, go to definition
+        graph.suggestResult(first.sym, first.sym.info, ideDeclaration)
+      else:
+        # we are on definition or usage, look for declaration
+        graph.suggestResult(first.sym, first.info, ideDeclaration)
+  of ideExpand:
+    var level: int = high(int)
+    let index = skipWhitespace(tag, 0);
+    let trimmed = substr(tag, index)
+    if not (trimmed == "" or trimmed == "all"):
+      discard parseInt(trimmed, level, 0)
+
+    conf.expandPosition = newLineInfo(fileIndex, line, col)
+    conf.expandLevels = level
+    conf.expandProgress = false
+    conf.expandNodeResult = ""
+
+    graph.markDirty fileIndex
+    graph.markClientsDirty fileIndex
+    graph.recompilePartially()
+    var suggest = Suggest()
+    suggest.section = ideExpand
+    suggest.version = 3
+    suggest.line = line
+    suggest.column = col
+    suggest.doc = graph.config.expandNodeResult
+    if suggest.doc != "":
+      let
+        n = parseFile(fileIndex, graph.cache, graph.config)
+        endInfo = n.calculateExpandRange(conf.expandPosition)
+
+      suggest.endLine = endInfo.line
+      suggest.endCol = endInfo.col
+
+    suggestResult(graph.config, suggest)
+
+    graph.markDirty fileIndex
+    graph.markClientsDirty fileIndex
+  of ideInlayHints:
+    myLog fmt "Executing inlayHints"
+    var endLine = 0
+    var endCol = -1
+    var i = 0
+    i += skipWhile(tag, seps, i)
+    i += parseInt(tag, endLine, i)
+    i += skipWhile(tag, seps, i)
+    i += parseInt(tag, endCol, i)
+    i += skipWhile(tag, seps, i)
+    var typeHints = true
+    var exceptionHints = false
+    while i <= tag.high:
+      var token: string
+      i += parseUntil(tag, token, seps, i)
+      i += skipWhile(tag, seps, i)
+      case token:
+      of "+typeHints":
+        typeHints = true
+      of "-typeHints":
+        typeHints = false
+      of "+exceptionHints":
+        exceptionHints = true
+      of "-exceptionHints":
+        exceptionHints = false
+      else:
+        myLog fmt "Discarding unknown inlay hint parameter {token}"
+
+    let s = graph.findSymDataInRange(file, line, col, endLine, endCol)
+    for q in s:
+      if typeHints and q.sym.kind in {skLet, skVar, skForVar, skConst} and q.isDecl and not q.sym.hasUserSpecifiedType:
+        graph.suggestInlayHintResultType(q.sym, q.info, ideInlayHints)
+      if exceptionHints and q.sym.kind in {skProc, skFunc, skMethod, skVar, skLet, skParam} and not q.isDecl:
+        graph.suggestInlayHintResultException(q.sym, q.info, ideInlayHints, caughtExceptions = q.caughtExceptions, caughtExceptionsSet = q.caughtExceptionsSet)
+  else:
+    myLog fmt "Discarding {cmd}"
+
+# v3 end
 when isMainModule:
   handleCmdLine(newIdentCache(), newConfigRef())
 else:
@@ -663,10 +1297,12 @@ else:
     proc mockCommand(graph: ModuleGraph) =
       retval = graph
       let conf = graph.config
+      conf.setCmd cmdIdeTools
+      defineSymbol(conf.symbols, $conf.backend)
       clearPasses(graph)
       registerPass graph, verbosePass
       registerPass graph, semPass
-      conf.setCmd cmdIdeTools
+
       wantMainModule(conf)
 
       if not fileExists(conf.projectFull):
@@ -709,13 +1345,7 @@ else:
       conf.writelnHook = proc (msg: string) = discard
     # Find Nim's prefix dir.
     if nimPath == "":
-      let binaryPath = findExe("nim")
-      if binaryPath == "":
-        raise newException(IOError,
-            "Cannot find Nim standard library: Nim compiler not in PATH")
-      conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir()
-      if not dirExists(conf.prefixDir / RelativeDir"lib"):
-        conf.prefixDir = AbsoluteDir""
+      conf.prefixDir = conf.getPrefixDir()
     else:
       conf.prefixDir = AbsoluteDir nimPath
 
@@ -726,8 +1356,9 @@ else:
     if self.loadConfigsAndProcessCmdLine(cache, conf, graph):
       mockCommand(graph)
     if gLogging:
+      myLog("Search paths:")
       for it in conf.searchPaths:
-        log(it.string)
+        myLog(" " & it.string)
 
     retval.doStopCompile = proc (): bool = false
     return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[])
diff --git a/nimsuggest/nimsuggest.nimble b/nimsuggest/nimsuggest.nimble
index 53b5d1d6f..b3790e116 100644
--- a/nimsuggest/nimsuggest.nimble
+++ b/nimsuggest/nimsuggest.nimble
@@ -1,8 +1,8 @@
-version       = "0.1.0"
-author        = "Andreas Rumpf"
-description   = "Tool for providing auto completion data for Nim source code."
-license       = "MIT"
-
+include "../lib/system/compilation.nim"
+version = $NimMajor & "." & $NimMinor & "." & $NimPatch
+author = "Andreas Rumpf"
+description = "Tool for providing auto completion data for Nim source code."
+license = "MIT"
 bin = @["nimsuggest"]
 
-requires "nim >= 1.1.1"
+requires "compiler >= 1.9.0" , "checksums"
diff --git a/nimsuggest/procmonitor.nim b/nimsuggest/procmonitor.nim
new file mode 100644
index 000000000..0f1ba1e0d
--- /dev/null
+++ b/nimsuggest/procmonitor.nim
@@ -0,0 +1,34 @@
+# Monitor a client process and shutdown the current process, if the client
+# process is found to be dead
+
+import os
+
+when defined(posix):
+  import posix_utils
+  import posix
+
+when defined(windows):
+  import winlean
+
+when defined(posix):
+  proc monitorClientProcessIdThreadProc(pid: int) {.thread.} =
+    while true:
+      sleep(1000)
+      try:
+        sendSignal(Pid(pid), 0)
+      except:
+        discard kill(Pid(getCurrentProcessId()), cint(SIGTERM))
+
+when defined(windows):
+  proc monitorClientProcessIdThreadProc(pid: int) {.thread.} =
+    var process = openProcess(SYNCHRONIZE, 0, DWORD(pid))
+    if process != 0:
+      discard waitForSingleObject(process, INFINITE)
+      discard closeHandle(process)
+    quit(0)
+
+var tid: Thread[int]
+
+proc hookProcMonitor*(pid: int) =
+  when defined(posix) or defined(windows):
+    createThread(tid, monitorClientProcessIdThreadProc, pid)
diff --git a/nimsuggest/sexp.nim b/nimsuggest/sexp.nim
index cee538b6e..03369ccb7 100644
--- a/nimsuggest/sexp.nim
+++ b/nimsuggest/sexp.nim
@@ -14,6 +14,9 @@ import
 
 import std/private/decode_helpers
 
+when defined(nimPreviewSlimSystem):
+  import std/[assertions, formatfloat]
+
 type
   SexpEventKind* = enum  ## enumeration of all events that may occur when parsing
     sexpError,           ## an error occurred during parsing
@@ -288,10 +291,6 @@ proc newSString*(s: string): SexpNode =
   ## Creates a new `SString SexpNode`.
   result = SexpNode(kind: SString, str: s)
 
-proc newSStringMove(s: string): SexpNode =
-  result = SexpNode(kind: SString)
-  shallowCopy(result.str, s)
-
 proc newSInt*(n: BiggestInt): SexpNode =
   ## Creates a new `SInt SexpNode`.
   result = SexpNode(kind: SInt, num: n)
@@ -315,10 +314,6 @@ proc newSList*(): SexpNode =
 proc newSSymbol*(s: string): SexpNode =
   result = SexpNode(kind: SSymbol, symbol: s)
 
-proc newSSymbolMove(s: string): SexpNode =
-  result = SexpNode(kind: SSymbol)
-  shallowCopy(result.symbol, s)
-
 proc getStr*(n: SexpNode, default: string = ""): string =
   ## Retrieves the string value of a `SString SexpNode`.
   ##
@@ -409,7 +404,7 @@ macro convertSexp*(x: untyped): untyped =
   ## `%` for every element.
   result = toSexp(x)
 
-proc `==`* (a,b: SexpNode): bool =
+func `==`* (a, b: SexpNode): bool =
   ## Check two nodes for equality
   if a.isNil:
     if b.isNil: return true
@@ -596,8 +591,7 @@ proc parseSexp(p: var SexpParser): SexpNode =
   case p.tok
   of tkString:
     # we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
-    result = newSStringMove(p.a)
-    p.a = ""
+    result = SexpNode(kind: SString, str: move p.a)
     discard getTok(p)
   of tkInt:
     result = newSInt(parseBiggestInt(p.a))
@@ -609,8 +603,7 @@ proc parseSexp(p: var SexpParser): SexpNode =
     result = newSNil()
     discard getTok(p)
   of tkSymbol:
-    result = newSSymbolMove(p.a)
-    p.a = ""
+    result = SexpNode(kind: SSymbol, symbol: move p.a)
     discard getTok(p)
   of tkParensLe:
     result = newSList()
diff --git a/nimsuggest/tester.nim b/nimsuggest/tester.nim
index 425430ede..9b9488348 100644
--- a/nimsuggest/tester.nim
+++ b/nimsuggest/tester.nim
@@ -2,8 +2,11 @@
 # Every test file can have a #[!]# comment that is deleted from the input
 # before 'nimsuggest' is invoked to ensure this token doesn't make a
 # crucial difference for Nim's parser.
+# When debugging, to run a single test, use for e.g.:
+# `nim r nimsuggest/tester.nim nimsuggest/tests/tsug_accquote.nim`
 
 import os, osproc, strutils, streams, re, sexp, net
+from sequtils import toSeq
 
 type
   Test = object
@@ -13,16 +16,16 @@ type
     disabled: bool
 
 const
-  curDir = when defined(windows): "" else: ""
   DummyEof = "!EOF!"
-
-template tpath(): untyped = getAppDir() / "tests"
+  tpath = "nimsuggest/tests"
+  # we could also use `stdtest/specialpaths`
 
 import std/compilesettings
 
 proc parseTest(filename: string; epcMode=false): Test =
   const cursorMarker = "#[!]#"
-  let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
+  let nimsug = "bin" / addFileExt("nimsuggest_testing", ExeExt)
+  doAssert nimsug.fileExists, nimsug
   const libpath = querySetting(libPath)
   result.filename = filename
   result.dest = getTempDir() / extractFilename(filename)
@@ -63,7 +66,7 @@ proc parseTest(filename: string; epcMode=false): Test =
       elif x.startsWith(">"):
         # since 'markers' here are not complete yet, we do the $substitutions
         # afterwards
-        result.script.add((x.substr(1).replaceWord("$path", tpath()), ""))
+        result.script.add((x.substr(1).replaceWord("$path", tpath).replaceWord("$file", filename), ""))
       elif x.len > 0:
         # expected output line:
         let x = x % ["file", filename, "lib", libpath]
@@ -104,7 +107,7 @@ proc parseCmd(c: string): seq[string] =
 proc edit(tmpfile: string; x: seq[string]) =
   if x.len != 3 and x.len != 4:
     quit "!edit takes two or three arguments"
-  let f = if x.len >= 4: tpath() / x[3] else: tmpfile
+  let f = if x.len >= 4: tpath / x[3] else: tmpfile
   try:
     let content = readFile(f)
     let newcontent = content.replace(x[1], x[2])
@@ -121,12 +124,12 @@ proc exec(x: seq[string]) =
 
 proc copy(x: seq[string]) =
   if x.len != 3: quit "!copy takes two arguments"
-  let rel = tpath()
+  let rel = tpath
   copyFile(rel / x[1], rel / x[2])
 
 proc del(x: seq[string]) =
   if x.len != 2: quit "!del takes one argument"
-  removeFile(tpath() / x[1])
+  removeFile(tpath / x[1])
 
 proc runCmd(cmd, dest: string): bool =
   result = cmd[0] == '!'
@@ -215,7 +218,12 @@ proc sexpToAnswer(s: SexpNode): string =
       result.add doc
       result.add '\t'
       result.addInt a[8].getNum
-      if a.len >= 10:
+      if a.len >= 11:
+        result.add '\t'
+        result.addInt a[9].getNum
+        result.add '\t'
+        result.addInt a[10].getNum
+      elif a.len >= 10:
         result.add '\t'
         result.add a[9].getStr
     result.add '\L'
@@ -226,8 +234,8 @@ proc doReport(filename, answer, resp: string; report: var string) =
     var hasDiff = false
     for i in 0..min(resp.len-1, answer.len-1):
       if resp[i] != answer[i]:
-        report.add "\n  Expected:  " & resp.substr(i, i+200)
-        report.add "\n  But got:   " & answer.substr(i, i+200)
+        report.add "\n  Expected:\n" & resp
+        report.add "\n  But got:\n" & answer
         hasDiff = true
         break
     if not hasDiff:
@@ -249,13 +257,15 @@ proc runEpcTest(filename: string): int =
   for cmd in s.startup:
     if not runCmd(cmd, s.dest):
       quit "invalid command: " & cmd
-  let epccmd = s.cmd.replace("--tester", "--epc --v2 --log")
+  let epccmd = if s.cmd.contains("--v3"):
+    s.cmd.replace("--tester", "--epc --log")
+  else:
+    s.cmd.replace("--tester", "--epc --v2 --log")
   let cl = parseCmdLine(epccmd)
   var p = startProcess(command=cl[0], args=cl[1 .. ^1],
                        options={poStdErrToStdOut, poUsePath,
                        poInteractive, poDaemon})
   let outp = p.outputStream
-  let inp = p.inputStream
   var report = ""
   var socket = newSocket()
   try:
@@ -269,17 +279,28 @@ proc runEpcTest(filename: string): int =
         os.sleep(50)
         inc i
       let a = outp.readAll().strip()
-    let port = parseInt(a)
+    var port: int
+    try:
+      port = parseInt(a)
+    except ValueError:
+      echo "Error parsing port number: " & a
+      echo outp.readAll()
+      quit 1
     socket.connect("localhost", Port(port))
+
     for req, resp in items(s.script):
       if not runCmd(req, s.dest):
         socket.sendEpcStr(req)
         let sx = parseSexp(socket.recvEpc())
         if not req.startsWith("mod "):
-          let answer = sexpToAnswer(sx)
+          let answer = if sx[2].kind == SNil: "" else: sexpToAnswer(sx)
           doReport(filename, answer, resp, report)
-  finally:
+
     socket.sendEpcStr "return arg"
+      # bugfix: this was in `finally` block, causing the original error to be
+      # potentially masked by another one in case `socket.sendEpcStr` raises
+      # (e.g. if socket couldn't connect in the 1st place)
+  finally:
     close(p)
   if report.len > 0:
     echo "==== EPC ========================================"
@@ -315,8 +336,12 @@ proc runTest(filename: string): int =
           answer.add '\L'
         doReport(filename, answer, resp, report)
   finally:
-    inp.writeLine("quit")
-    inp.flush()
+    try:
+      inp.writeLine("quit")
+      inp.flush()
+    except IOError, OSError:
+      # assume it's SIGPIPE, ie, the child already died
+      discard
     close(p)
   if report.len > 0:
     echo "==== STDIN ======================================"
@@ -328,11 +353,16 @@ proc main() =
   if os.paramCount() > 0:
     let x = os.paramStr(1)
     let xx = expandFilename x
+    # run only stdio when running single test
     failures += runTest(xx)
-    failures += runEpcTest(xx)
   else:
-    for x in walkFiles(tpath() / "t*.nim"):
-      echo "Test ", x
+    let files = toSeq(walkFiles(tpath / "t*.nim"))
+    for i, x in files:
+      echo "$#/$# test: $#" % [$i, $files.len, x]
+      when defined(i386):
+        if x == "nimsuggest/tests/tmacro_highlight.nim":
+          echo "skipping" # workaround bug #17945
+          continue
       let xx = expandFilename x
       when not defined(windows):
         # XXX Windows IO redirection seems bonkers:
diff --git a/nimsuggest/tests/module_20265.nim b/nimsuggest/tests/module_20265.nim
new file mode 100644
index 000000000..24b7d10c9
--- /dev/null
+++ b/nimsuggest/tests/module_20265.nim
@@ -0,0 +1,6 @@
+type A* = tuple
+  a: int
+  b: int
+
+var x*: A = (a: 2, b: 10)
+var y* = (a: 2, b: 10)
diff --git a/nimsuggest/tests/t20265_1.nim b/nimsuggest/tests/t20265_1.nim
new file mode 100644
index 000000000..553b3d545
--- /dev/null
+++ b/nimsuggest/tests/t20265_1.nim
@@ -0,0 +1,8 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;a;;int;;*module_20265.nim;;6;;10;;"";;100;;None
+sug;;skField;;b;;int;;*module_20265.nim;;6;;16;;"";;100;;None
+"""
+import module_20265
+y.#[!]#
diff --git a/nimsuggest/tests/t20265_2.nim b/nimsuggest/tests/t20265_2.nim
new file mode 100644
index 000000000..33edf2d9a
--- /dev/null
+++ b/nimsuggest/tests/t20265_2.nim
@@ -0,0 +1,8 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;a;;int;;*module_20265.nim;;2;;2;;"";;100;;None
+sug;;skField;;b;;int;;*module_20265.nim;;3;;2;;"";;100;;None
+"""
+import module_20265
+x.#[!]#
diff --git a/nimsuggest/tests/t20440.nim b/nimsuggest/tests/t20440.nim
new file mode 100644
index 000000000..0456aa074
--- /dev/null
+++ b/nimsuggest/tests/t20440.nim
@@ -0,0 +1,7 @@
+when not defined(js):
+  {.fatal: "Crash".}
+echo 4
+
+discard """
+$nimsuggest --v3 --tester $file
+"""
diff --git a/nimsuggest/tests/t20440.nims b/nimsuggest/tests/t20440.nims
new file mode 100644
index 000000000..1336be3d4
--- /dev/null
+++ b/nimsuggest/tests/t20440.nims
@@ -0,0 +1 @@
+switch("backend", "js")
diff --git a/nimsuggest/tests/t21185.nim b/nimsuggest/tests/t21185.nim
new file mode 100644
index 000000000..bf5a0e3cc
--- /dev/null
+++ b/nimsuggest/tests/t21185.nim
@@ -0,0 +1,18 @@
+
+# Reduced case of 21185. Issue was first parameter being static
+proc foo(x: static[int]) = discard
+
+type
+  Person = object
+    name: string
+    age: int
+
+let p = Person()
+p.#[!]#
+
+discard """
+$nimsuggest --tester --v3 --maxresults:2 $file
+>sug $1
+sug;;skField;;age;;int;;$file;;8;;4;;"";;100;;None
+sug;;skField;;name;;string;;$file;;7;;4;;"";;100;;None
+"""
diff --git a/nimsuggest/tests/t22448.nim b/nimsuggest/tests/t22448.nim
new file mode 100644
index 000000000..8664bbbc3
--- /dev/null
+++ b/nimsuggest/tests/t22448.nim
@@ -0,0 +1,11 @@
+proc fn(a: static float) = discard
+proc fn(a: int) = discard
+
+let x = 1
+fn(x)
+
+discard """
+$nimsuggest --tester --v3 $file
+>chk $file
+chk;;skUnknown;;;;Hint;;*
+"""
diff --git a/nimsuggest/tests/tarrowcrash.nim b/nimsuggest/tests/tarrowcrash.nim
new file mode 100644
index 000000000..a303e88f5
--- /dev/null
+++ b/nimsuggest/tests/tarrowcrash.nim
@@ -0,0 +1,20 @@
+# issue #24179
+
+import sugar
+
+type
+    Parser[T] = object
+    
+proc eatWhile[T](p: Parser[T], predicate: T -> bool): seq[T] =
+    return @[]
+
+proc skipWs(p: Parser[char]) =
+    discard p.eatWhile((c: char) => c == 'a')
+#[!]#
+    
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tarrowcrash.nim [Processing]";;0
+chk;;skUnknown;;;;Hint;;$file;;11;;5;;"\'skipWs\' is declared but not used [XDeclaredButNotUsed]";;0
+"""
diff --git a/nimsuggest/tests/tchk1.nim b/nimsuggest/tests/tchk1.nim
index c28b88b9b..be6115c1c 100644
--- a/nimsuggest/tests/tchk1.nim
+++ b/nimsuggest/tests/tchk1.nim
@@ -17,11 +17,11 @@ proc main =
 discard """
 $nimsuggest --tester $file
 >chk $1
-chk;;skUnknown;;;;Hint;;???;;0;;-1;;"tchk1 [Processing]";;0
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk1.nim [Processing]";;0
 chk;;skUnknown;;;;Error;;$file;;12;;0;;"identifier expected, but got \'keyword template\'";;0
 chk;;skUnknown;;;;Error;;$file;;14;;0;;"nestable statement requires indentation";;0
-chk;;skUnknown;;;;Error;;$file;;12;;0;;"implementation of \'foo\' expected";;0
 chk;;skUnknown;;;;Error;;$file;;17;;0;;"invalid indentation";;0
+chk;;skUnknown;;;;Error;;$file;;12;;0;;"implementation of \'foo\' expected";;0
 chk;;skUnknown;;;;Hint;;$file;;12;;9;;"\'foo\' is declared but not used [XDeclaredButNotUsed]";;0
 chk;;skUnknown;;;;Hint;;$file;;14;;5;;"\'main\' is declared but not used [XDeclaredButNotUsed]";;0
 """
diff --git a/nimsuggest/tests/tchk2.nim b/nimsuggest/tests/tchk2.nim
new file mode 100644
index 000000000..f5404368d
--- /dev/null
+++ b/nimsuggest/tests/tchk2.nim
@@ -0,0 +1,35 @@
+# bug #22794
+type O = object
+
+proc `=destroy`(x: O) = discard
+proc `=trace`(x: var O; env: pointer) = discard
+proc `=copy`(a: var O; b: O) = discard
+proc `=dup`(a: O): O {.nodestroy.} = a
+proc `=sink`(a: var O; b: O) = discard
+
+
+# bug #23316
+type SomeSturct = object
+
+proc `=destroy`(x: SomeSturct) =
+  echo "SomeSturct destroyed"
+
+# bug #23867
+type ObjStr = object
+  s: string
+
+let ostr = ObjStr() # <-- nimsuggest crashes
+discard ostr
+
+type ObjSeq = object
+  s: seq[int]
+
+let oseq = ObjSeq() # <-- nimsuggest crashes
+discard oseq
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk2.nim [Processing]";;0
+"""
diff --git a/nimsuggest/tests/tchk_compiles.nim b/nimsuggest/tests/tchk_compiles.nim
index 887a947b5..c8a3daac4 100644
--- a/nimsuggest/tests/tchk_compiles.nim
+++ b/nimsuggest/tests/tchk_compiles.nim
@@ -4,5 +4,5 @@ discard compiles(2 + "hello")
 discard """
 $nimsuggest --tester $file
 >chk $1
-chk;;skUnknown;;;;Hint;;???;;0;;-1;;"tchk_compiles [Processing]";;0
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk_compiles.nim [Processing]";;0
 """
diff --git a/nimsuggest/tests/tconcept1.nim b/nimsuggest/tests/tconcept1.nim
new file mode 100644
index 000000000..d81cd8120
--- /dev/null
+++ b/nimsuggest/tests/tconcept1.nim
@@ -0,0 +1,12 @@
+SomeNumber = concept
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tconcept1.nim [Processing]";;0
+chk;;skUnknown;;;;Error;;$file;;1;;13;;"the \'concept\' keyword is only valid in \'type\' sections";;0
+chk;;skUnknown;;;;Error;;$file;;1;;13;;"invalid indentation";;0
+chk;;skUnknown;;;;Error;;$file;;1;;13;;"expression expected, but found \'keyword concept\'";;0
+chk;;skUnknown;;;;Error;;$file;;1;;0;;"\'SomeNumber\' cannot be assigned to";;0
+"""
diff --git a/nimsuggest/tests/tconcept2.nim b/nimsuggest/tests/tconcept2.nim
new file mode 100644
index 000000000..7f7d147f5
--- /dev/null
+++ b/nimsuggest/tests/tconcept2.nim
@@ -0,0 +1,15 @@
+  SomeNumber = concept a, type T
+    a.int is int
+    int.to(T) is type(a)
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tconcept2.nim [Processing]";;0
+chk;;skUnknown;;;;Error;;$file;;1;;2;;"invalid indentation";;0
+chk;;skUnknown;;;;Error;;$file;;1;;15;;"the \'concept\' keyword is only valid in \'type\' sections";;0
+chk;;skUnknown;;;;Error;;$file;;1;;15;;"invalid indentation";;0
+chk;;skUnknown;;;;Error;;$file;;1;;15;;"expression expected, but found \'keyword concept\'";;0
+chk;;skUnknown;;;;Error;;$file;;1;;2;;"\'SomeNumber\' cannot be assigned to";;0
+"""
diff --git a/nimsuggest/tests/tdef1.nim b/nimsuggest/tests/tdef1.nim
index 2cd040ea1..49265bbc1 100644
--- a/nimsuggest/tests/tdef1.nim
+++ b/nimsuggest/tests/tdef1.nim
@@ -1,12 +1,14 @@
 discard """
 $nimsuggest --tester $file
 >def $1
-def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"Return hello";;100
->def $1
-def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"Return hello";;100
+def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100
+>def $2
+def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100
+>def $2
+def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100
 """
 
-proc hello(): string =
+proc hel#[!]#lo(): string =
   ## Return hello
   "Hello"
 
diff --git a/nimsuggest/tests/tdef_let.nim b/nimsuggest/tests/tdef_let.nim
new file mode 100644
index 000000000..3e9456d2f
--- /dev/null
+++ b/nimsuggest/tests/tdef_let.nim
@@ -0,0 +1,7 @@
+discard """
+$nimsuggest --tester $file
+>def $1
+def;;skLet;;tdef_let.intVar;;int;;$file;;7;;4;;"";;100
+"""
+
+let int#[!]#Var = 10
diff --git a/nimsuggest/tests/tdot4.nim b/nimsuggest/tests/tdot4.nim
index e1ff96553..f2c6c765f 100644
--- a/nimsuggest/tests/tdot4.nim
+++ b/nimsuggest/tests/tdot4.nim
@@ -15,7 +15,7 @@ discard """
 $nimsuggest --tester --maxresults:2 $file
 >sug $1
 sug;;skProc;;tdot4.main;;proc (inp: string): string;;$file;;6;;5;;"";;100;;None
-sug;;skFunc;;mstrutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, locks: 0.};;*fixtures/mstrutils.nim;;9;;5;;"this is a test version of strutils.replace, it simply returns `by`";;100;;None
+sug;;skFunc;;mstrutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, raises: <inferred> [].};;*fixtures/mstrutils.nim;;9;;5;;"this is a test version of strutils.replace, it simply returns `by`";;100;;None
 """
 
 # TODO - determine appropriate behaviour for further suggest output and test it
diff --git a/nimsuggest/tests/tenum_field.nim b/nimsuggest/tests/tenum_field.nim
new file mode 100644
index 000000000..4ceb3e021
--- /dev/null
+++ b/nimsuggest/tests/tenum_field.nim
@@ -0,0 +1,17 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+>sug $2
+sug;;skConst;;tenum_field.BarFoo;;int literal(1);;$file;;10;;6;;"";;100;;Prefix
+"""
+
+proc something() = discard
+
+const BarFoo = 1
+
+type
+  Foo = enum
+    # Test that typing the name doesn't give suggestions
+    somethi#[!]#
+    # Test that the right hand side still gets suggestions
+    another = BarFo#[!]#
diff --git a/nimsuggest/tests/tfatal1.nim b/nimsuggest/tests/tfatal1.nim
new file mode 100644
index 000000000..19778f22e
--- /dev/null
+++ b/nimsuggest/tests/tfatal1.nim
@@ -0,0 +1,15 @@
+{.warning: "I'm a warning!".}
+{.error: "I'm an error!".}
+{.fatal: "I'm a fatal error!".}
+{.error: "I'm an error after fatal error!".}
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tfatal1.nim [Processing]";;0
+chk;;skUnknown;;;;Warning;;$file;;1;;9;;"I\'m a warning! [User]";;0
+chk;;skUnknown;;;;Error;;$file;;2;;7;;"I\'m an error!";;0
+chk;;skUnknown;;;;Error;;$file;;3;;7;;"fatal error: I\'m a fatal error!";;0
+chk;;skUnknown;;;;Error;;$file;;4;;7;;"I\'m an error after fatal error!";;0
+"""
diff --git a/nimsuggest/tests/tgeneric_highlight.nim b/nimsuggest/tests/tgeneric_highlight.nim
index 334323613..c7291d08b 100644
--- a/nimsuggest/tests/tgeneric_highlight.nim
+++ b/nimsuggest/tests/tgeneric_highlight.nim
@@ -7,14 +7,7 @@ $nimsuggest --tester $file
 >highlight $1
 highlight;;skType;;1;;7;;3
 highlight;;skProc;;1;;0;;6
-highlight;;skProc;;1;;0;;6
-highlight;;skType;;1;;7;;3
-highlight;;skProc;;1;;0;;6
-highlight;;skType;;2;;14;;3
-highlight;;skProc;;2;;7;;6
-highlight;;skProc;;2;;7;;6
 highlight;;skType;;2;;14;;3
 highlight;;skProc;;2;;7;;6
-highlight;;skTemplate;;3;;0;;8
 highlight;;skType;;3;;9;;3
 """
diff --git a/nimsuggest/tests/tgenerics.nim b/nimsuggest/tests/tgenerics.nim
new file mode 100644
index 000000000..7f490321c
--- /dev/null
+++ b/nimsuggest/tests/tgenerics.nim
@@ -0,0 +1,18 @@
+type
+  Hello[T] = object
+    value: T
+
+proc printHelloValue[T](hello: Hello[T]) =
+  echo hello.value
+
+proc main() =
+  let a = Hello[float]()
+  p#[!]#rintHelloValue(a)
+
+main()
+
+discard """
+$nimsuggest --tester $file
+>def $1
+def;;skProc;;tgenerics.printHelloValue;;proc (hello: Hello[printHelloValue.T]);;$file;;5;;5;;"";;100
+"""
diff --git a/nimsuggest/tests/tic.nim b/nimsuggest/tests/tic.nim
new file mode 100644
index 000000000..26e644f83
--- /dev/null
+++ b/nimsuggest/tests/tic.nim
@@ -0,0 +1,20 @@
+import std/[appdirs, assertions, cmdline, compilesettings, decls, 
+  dirs, editdistance, effecttraits, enumerate, enumutils, envvars, 
+  exitprocs, files, formatfloat, genasts, importutils, 
+  isolation, jsonutils, logic, monotimes, objectdollar, 
+  oserrors, outparams, packedsets, paths, private, setutils, sha1, 
+  socketstreams, stackframes, staticos, strbasics, symlinks, syncio, 
+  sysatomics, sysrand, tasks, tempfiles, time_t, typedthreads, varints, 
+  vmutils, widestrs, with, wordwrap, wrapnils]
+
+proc test(a: string, b:string) = discard
+proc test(a: int) = discard
+
+test(#[!]#
+
+discard """
+$nimsuggest --v3 --ic:off --tester $file 
+>con $1
+con;;skProc;;tic.test;;proc (a: string, b: string);;$file;;10;;5;;"";;100
+con;;skProc;;tic.test;;proc (a: int);;$file;;11;;5;;"";;100
+"""
\ No newline at end of file
diff --git a/nimsuggest/tests/timport_highlight.nim b/nimsuggest/tests/timport_highlight.nim
new file mode 100644
index 000000000..043f87d98
--- /dev/null
+++ b/nimsuggest/tests/timport_highlight.nim
@@ -0,0 +1,12 @@
+import std/paths
+import json as J
+import std/[os,streams]#[!]#
+
+discard """
+$nimsuggest --tester $file
+>highlight $1
+highlight;;skModule;;1;;11;;5
+highlight;;skModule;;2;;7;;4
+highlight;;skModule;;3;;12;;2
+highlight;;skModule;;3;;15;;7
+"""
diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim
index b67440b9e..f5cbabf05 100644
--- a/nimsuggest/tests/tinclude.nim
+++ b/nimsuggest/tests/tinclude.nim
@@ -11,7 +11,7 @@ go()
 discard """
 $nimsuggest --tester $file
 >def $path/tinclude.nim:7:14
-def;;skProc;;minclude_import.create;;proc (greeting: string, subject: string): Greet{.noSideEffect, gcsafe, locks: 0.};;*fixtures/minclude_include.nim;;3;;5;;"";;100
+def;;skProc;;minclude_import.create;;proc (greeting: string, subject: string): Greet{.noSideEffect, gcsafe, raises: <inferred> [].};;*fixtures/minclude_include.nim;;3;;5;;"";;100
 >def $path/fixtures/minclude_include.nim:3:71
 def;;skType;;minclude_types.Greet;;Greet;;*fixtures/minclude_types.nim;;4;;2;;"";;100
 >def $path/fixtures/minclude_include.nim:3:71
diff --git a/nimsuggest/tests/tqualified_highlight.nim b/nimsuggest/tests/tqualified_highlight.nim
index 3b521ecc1..b83669e72 100644
--- a/nimsuggest/tests/tqualified_highlight.nim
+++ b/nimsuggest/tests/tqualified_highlight.nim
@@ -10,5 +10,5 @@ highlight;;skProc;;1;;7;;4
 highlight;;skTemplate;;2;;7;;4
 highlight;;skTemplate;;2;;7;;4
 highlight;;skTemplate;;2;;7;;4
-highlight;;skProc;;3;;8;;1
+highlight;;skFunc;;3;;8;;1
 """
diff --git a/nimsuggest/tests/tsug_pragmas.nim b/nimsuggest/tests/tsug_pragmas.nim
new file mode 100644
index 000000000..ce9c4e8f8
--- /dev/null
+++ b/nimsuggest/tests/tsug_pragmas.nim
@@ -0,0 +1,40 @@
+template fooBar1() {.pragma.}
+proc fooBar2() = discard
+macro fooBar3(x: untyped) = discard
+{.pragma: fooBar4 fooBar3.}
+
+proc test1() {.fooBar#[!]#.} = discard
+
+var test2 {.fooBar#[!]#.} = 9
+
+type
+  Person {.fooBar#[!]#.} = object
+    hello {.fooBar#[!]#.}: string
+  Callback = proc () {.fooBar#[!]#.}
+
+# Check only macros/templates/pragmas are suggested
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+>sug $2
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+>sug $3
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+>sug $4
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+>sug $5
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+"""
+
+
diff --git a/nimsuggest/tests/tsug_recursive.nim b/nimsuggest/tests/tsug_recursive.nim
new file mode 100644
index 000000000..97ee5ca01
--- /dev/null
+++ b/nimsuggest/tests/tsug_recursive.nim
@@ -0,0 +1,8 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skProc;;tsug_recursive.fooBar;;proc ();;$file;;7;;5;;"";;100;;Prefix
+"""
+
+proc fooBar() =
+  fooBa#[!]#
diff --git a/nimsuggest/tests/tsug_template.nim b/nimsuggest/tests/tsug_template.nim
index 15615f0af..da494d279 100644
--- a/nimsuggest/tests/tsug_template.nim
+++ b/nimsuggest/tests/tsug_template.nim
@@ -6,7 +6,7 @@ tmp#[!]#
 discard """
 $nimsuggest --tester $file
 >sug $1
-sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, locks: 0.};;$file;;2;;6;;"";;100;;Prefix
+sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;2;;6;;"";;100;;Prefix
 sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;100;;Prefix
 sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;100;;Prefix
 """
diff --git a/nimsuggest/tests/tsug_typedecl.nim b/nimsuggest/tests/tsug_typedecl.nim
index 833043d31..2a510929d 100644
--- a/nimsuggest/tests/tsug_typedecl.nim
+++ b/nimsuggest/tests/tsug_typedecl.nim
@@ -21,6 +21,6 @@ $nimsuggest --tester $file
 >sug $1
 sug;;skType;;tsug_typedecl.someType;;someType;;*nimsuggest/tests/tsug_typedecl.nim;;7;;2;;"";;100;;Prefix
 sug;;skType;;tsug_typedecl.super;;super;;*nimsuggest/tests/tsug_typedecl.nim;;6;;2;;"";;100;;Prefix
-sug;;skType;;system.string;;string;;*lib/system.nim;;*;;*;;*;;100;;Prefix
+sug;;skType;;system.string;;string;;*lib/system/basic_types.nim;;*;;*;;*;;100;;Prefix
 sug;;skType;;system.seq;;seq;;*lib/system.nim;;*;;*;;*;;100;;Prefix
-"""
\ No newline at end of file
+"""
diff --git a/nimsuggest/tests/ttempl_inst.nim b/nimsuggest/tests/ttempl_inst.nim
index ed04a67ce..5f5b10fe9 100644
--- a/nimsuggest/tests/ttempl_inst.nim
+++ b/nimsuggest/tests/ttempl_inst.nim
@@ -7,7 +7,7 @@ foo()
 discard """
 $nimsuggest --tester $file
 >chk $1
-chk;;skUnknown;;;;Hint;;???;;0;;-1;;"ttempl_inst [Processing]";;0
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/ttempl_inst.nim [Processing]";;0
 chk;;skUnknown;;;;Hint;;$file;;4;;3;;"template/generic instantiation from here";;0
 chk;;skUnknown;;;;Warning;;$file;;2;;11;;"foo [User]";;0
 """
diff --git a/nimsuggest/tests/ttype_decl.nim b/nimsuggest/tests/ttype_decl.nim
index 6022392d0..61d8c26cd 100644
--- a/nimsuggest/tests/ttype_decl.nim
+++ b/nimsuggest/tests/ttype_decl.nim
@@ -2,8 +2,8 @@ discard """
 $nimsuggest --tester --maxresults:3 $file
 >sug $1
 sug;;skType;;ttype_decl.Other;;Other;;$file;;10;;2;;"";;100;;None
-sug;;skType;;system.int;;int;;*/lib/system/basic_types.nim;;2;;2;;"";;100;;None
-sug;;skType;;system.string;;string;;*/lib/system.nim;;34;;2;;"";;100;;None
+sug;;skType;;system.int;;int;;*lib/system/basic_types.nim;;2;;2;;"";;100;;None
+sug;;skType;;system.string;;string;;*lib/system/basic_types.nim;;23;;2;;"";;100;;None
 """
 import strutils
 type
diff --git a/nimsuggest/tests/tuse.nim b/nimsuggest/tests/tuse.nim
index 89a9c151a..7c1d1ad0c 100644
--- a/nimsuggest/tests/tuse.nim
+++ b/nimsuggest/tests/tuse.nim
@@ -14,9 +14,9 @@ proc #[!]#someProc*() =
 discard """
 $nimsuggest --tester $file
 >use $1
-def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"";;100
-use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, locks: 0.};;$file;;12;;0;;"";;100
+def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;9;;5;;"";;100
+use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;12;;0;;"";;100
 >use $2
-def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"";;100
-use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, locks: 0.};;$file;;12;;0;;"";;100
+def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;9;;5;;"";;100
+use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;12;;0;;"";;100
 """
diff --git a/nimsuggest/tests/tuse_enum.nim b/nimsuggest/tests/tuse_enum.nim
new file mode 100644
index 000000000..8a40a8348
--- /dev/null
+++ b/nimsuggest/tests/tuse_enum.nim
@@ -0,0 +1,15 @@
+discard """
+$nimsuggest --tester $file
+>use $1
+def;;skEnumField;;tuse_enum.Colour.Red;;Colour;;$file;;10;;4;;"";;100
+use;;skEnumField;;tuse_enum.Colour.Red;;Colour;;$file;;14;;8;;"";;100
+"""
+
+type
+  Colour = enum
+    Red
+    Green
+    Blue
+
+discard #[!]#Red
+
diff --git a/nimsuggest/tests/tuse_structure.nim b/nimsuggest/tests/tuse_structure.nim
new file mode 100644
index 000000000..f65ab9060
--- /dev/null
+++ b/nimsuggest/tests/tuse_structure.nim
@@ -0,0 +1,15 @@
+# tests for use and structures
+
+type
+  Foo* = ref object of RootObj
+    bar*: string
+
+proc test(f: Foo) =
+  echo f.#[!]#bar
+
+discard """
+$nimsuggest --tester $file
+>use $1
+def	skField	tuse_structure.Foo.bar	string	$file	5	4	""	100
+use	skField	tuse_structure.Foo.bar	string	$file	8	9	""	100
+"""
diff --git a/nimsuggest/tests/tv3.nim b/nimsuggest/tests/tv3.nim
new file mode 100644
index 000000000..80e51e364
--- /dev/null
+++ b/nimsuggest/tests/tv3.nim
@@ -0,0 +1,27 @@
+# tests v3
+
+type
+  Foo* = ref object of RootObj
+    bar*: string
+
+proc test(f: Foo) =
+  echo f.ba#[!]#r
+
+#[!]#
+
+discard """
+$nimsuggest --v3 --tester $file
+>use $1
+def	skField	tv3.Foo.bar	string	$file	5	4	""	100
+use	skField	tv3.Foo.bar	string	$file	8	9	""	100
+>def $1
+def	skField	tv3.Foo.bar	string	$file	5	4	""	100
+>sug $1
+sug	skField	bar	string	$file	5	4	""	100	Prefix
+>globalSymbols test
+def	skProc	tv3.test	proc (f: Foo){.gcsafe, raises: <inferred> [].}	$file	7	5	""	100
+>globalSymbols Foo
+def	skType	tv3.Foo	Foo	$file	4	2	""	100
+>def $2
+>use $2
+"""
diff --git a/nimsuggest/tests/tv3_con.nim b/nimsuggest/tests/tv3_con.nim
new file mode 100644
index 000000000..4714c366b
--- /dev/null
+++ b/nimsuggest/tests/tv3_con.nim
@@ -0,0 +1,13 @@
+# tests v3
+
+proc test(a: string, b:string) = discard
+proc test(a: int) = discard
+
+test(#[!]#
+
+discard """
+$nimsuggest --v3 --tester $file
+>con $1
+con;;skProc;;tv3_con.test;;proc (a: string, b: string);;$file;;3;;5;;"";;100
+con;;skProc;;tv3_con.test;;proc (a: int);;$file;;4;;5;;"";;100
+"""
diff --git a/nimsuggest/tests/tv3_definition.nim b/nimsuggest/tests/tv3_definition.nim
new file mode 100644
index 000000000..03684b7cd
--- /dev/null
+++ b/nimsuggest/tests/tv3_definition.nim
@@ -0,0 +1,9 @@
+
+let foo = 30
+let bar = foo + fo#[!]#o + foo
+
+discard """
+$nimsuggest --v3 --tester $file
+>def $1
+def	skLet	tv3_definition.foo	int	$file	2	4	""	100
+"""
diff --git a/nimsuggest/tests/tv3_forward_definition.nim b/nimsuggest/tests/tv3_forward_definition.nim
new file mode 100644
index 000000000..7a16ea331
--- /dev/null
+++ b/nimsuggest/tests/tv3_forward_definition.nim
@@ -0,0 +1,23 @@
+proc de#[!]#mo(): int
+
+proc de#[!]#mo(): int = 5
+
+let a = de#[!]#mo()
+
+discard """
+$nimsuggest --v3 --tester $file
+>use $1
+use	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	1	5	""	100
+def	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	3	5	""	100
+use	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	5	8	""	100
+>use $2
+use	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	1	5	""	100
+def	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	3	5	""	100
+use	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	5	8	""	100
+>declaration $1
+declaration	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	3	5	""	100
+>declaration $2
+declaration	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	1	5	""	100
+>declaration $3
+declaration	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	1	5	""	100
+"""
diff --git a/nimsuggest/tests/tv3_generics.nim b/nimsuggest/tests/tv3_generics.nim
new file mode 100644
index 000000000..2bfb2ca1d
--- /dev/null
+++ b/nimsuggest/tests/tv3_generics.nim
@@ -0,0 +1,18 @@
+type
+  Hello[T] = object
+    value: T
+
+proc printHelloValue[T](hello: Hello[T]) =
+  echo hello.value
+
+proc main() =
+  let a = Hello[float]()
+  p#[!]#rintHelloValue(a)
+
+main()
+
+discard """
+$nimsuggest --v3 --tester $file
+>def $1
+def;;skProc;;tv3_generics.printHelloValue;;proc (hello: Hello[printHelloValue.T]);;$file;;5;;5;;"";;100
+"""
diff --git a/nimsuggest/tests/tv3_globalSymbols.nim b/nimsuggest/tests/tv3_globalSymbols.nim
new file mode 100644
index 000000000..c3bb9933b
--- /dev/null
+++ b/nimsuggest/tests/tv3_globalSymbols.nim
@@ -0,0 +1,14 @@
+# Tests the order of the matches
+proc Btoken(): int = 5
+proc tokenA(): int = 5
+proc token(): int = 5
+proc BBtokenA(): int = 5
+
+discard """
+$nimsuggest --v3 --tester $file
+>globalSymbols token
+def	skProc	tv3_globalSymbols.token	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	4	5	""	100
+def	skProc	tv3_globalSymbols.tokenA	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	3	5	""	100
+def	skProc	tv3_globalSymbols.Btoken	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	2	5	""	100
+def	skProc	tv3_globalSymbols.BBtokenA	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	5	5	""	100
+"""
diff --git a/nimsuggest/tests/tv3_import.nim b/nimsuggest/tests/tv3_import.nim
new file mode 100644
index 000000000..3c128f85b
--- /dev/null
+++ b/nimsuggest/tests/tv3_import.nim
@@ -0,0 +1,7 @@
+import tv#[!]#3
+
+discard """
+$nimsuggest --v3 --tester $file
+>def $1
+def	skModule	tv3		*/tv3.nim	1	0	""	100
+"""
diff --git a/nimsuggest/tests/tv3_outline.nim b/nimsuggest/tests/tv3_outline.nim
new file mode 100644
index 000000000..518620c87
--- /dev/null
+++ b/nimsuggest/tests/tv3_outline.nim
@@ -0,0 +1,45 @@
+# tests v3 outline
+
+type
+  Foo* = ref object of RootObj
+    bar*: string
+  FooEnum = enum value1, value2
+  FooPrivate = ref object of RootObj
+    barPrivate: string
+
+macro m(arg: untyped): untyped = discard
+template t(arg: untyped): untyped = discard
+proc p(): void = discard
+iterator i(): int = discard
+converter c(s: string): int = discard
+method m(f: Foo): void = discard
+func f(): void = discard
+
+let a = 1
+var b = 2
+const con = 2
+
+proc outer(): void =
+  proc inner() = discard
+
+proc procWithLocal(): void =
+  let local = 10
+
+discard """
+$nimsuggest --v3 --tester $file
+>outline $file
+outline	skType	tv3_outline.Foo	Foo	$file	4	2	""	100	5	16
+outline	skType	tv3_outline.FooEnum	FooEnum	$file	6	2	""	100	6	31
+outline	skEnumField	tv3_outline.FooEnum.value1	FooEnum	$file	6	17	""	100	6	23
+outline	skEnumField	tv3_outline.FooEnum.value2	FooEnum	$file	6	25	""	100	6	31
+outline	skType	tv3_outline.FooPrivate	FooPrivate	$file	7	2	""	100	8	22
+outline	skMacro	tv3_outline.m	macro (arg: untyped): untyped{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	10	6	""	100	10	40
+outline	skTemplate	tv3_outline.t	template (arg: untyped): untyped	$file	11	9	""	100	11	43
+outline	skProc	tv3_outline.p	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	12	5	""	100	12	24
+outline	skConverter	tv3_outline.c	converter (s: string): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	14	10	""	100	14	37
+outline	skFunc	tv3_outline.f	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	16	5	""	100	16	24
+outline	skConst	tv3_outline.con	int literal(2)	$file	20	6	""	100	20	13
+outline	skProc	tv3_outline.outer	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	22	5	""	100	23	24
+outline	skProc	tv3_outline.outer.inner	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	23	7	""	100	23	24
+outline	skProc	tv3_outline.procWithLocal	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	25	5	""	100	26	16
+"""
diff --git a/nimsuggest/tests/tv3_typeDefinition.nim b/nimsuggest/tests/tv3_typeDefinition.nim
new file mode 100644
index 000000000..f86d12cc6
--- /dev/null
+++ b/nimsuggest/tests/tv3_typeDefinition.nim
@@ -0,0 +1,32 @@
+# tests v3
+
+type
+  Foo* = ref object of RootObj
+    bar*: string
+
+proc test(ff: Foo) =
+  echo f#[!]#f.bar
+
+type
+  Fo#[!]#o2* = ref object of RootObj
+
+type
+  FooGeneric[T] = ref object of RootObj
+    bar*: T
+
+let fooGeneric = FooGeneric[string]()
+echo fo#[!]#oGeneric.bar
+
+# bad type
+echo unde#[!]#fined
+
+discard """
+$nimsuggest --v3 --tester $file
+>type $1
+type	skType	tv3_typeDefinition.Foo	Foo	$file	4	2	""	100
+>type $2
+type	skType	tv3_typeDefinition.Foo2	Foo2	$file	11	2	""	100
+>type $3
+type	skType	tv3_typeDefinition.FooGeneric	FooGeneric	$file	14	2	""	100
+>type $4
+"""
diff --git a/nimsuggest/tests/twithin_macro.nim b/nimsuggest/tests/twithin_macro.nim
index d79dae1be..98d58381f 100644
--- a/nimsuggest/tests/twithin_macro.nim
+++ b/nimsuggest/tests/twithin_macro.nim
@@ -45,7 +45,7 @@ $nimsuggest --tester --maxresults:5 $file
 >sug $1
 sug;;skField;;age;;int;;$file;;6;;6;;"";;100;;None
 sug;;skField;;name;;string;;$file;;5;;6;;"";;100;;None
-sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;8;;9;;"";;100;;None
-sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;7;;9;;"";;100;;None
+sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int{.raises: <inferred> [].};;$file;;8;;9;;"";;100;;None
+sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string{.raises: <inferred> [].};;$file;;7;;9;;"";;100;;None
 sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;23;;9;;"";;100;;None
 """
diff --git a/nimsuggest/tests/twithin_macro_prefix.nim b/nimsuggest/tests/twithin_macro_prefix.nim
index dd3810818..e89c8b942 100644
--- a/nimsuggest/tests/twithin_macro_prefix.nim
+++ b/nimsuggest/tests/twithin_macro_prefix.nim
@@ -44,5 +44,5 @@ discard """
 $nimsuggest --tester $file
 >sug $1
 sug;;skField;;age;;int;;$file;;6;;6;;"";;100;;Prefix
-sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int;;$file;;8;;9;;"";;100;;Prefix
+sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int{.raises: <inferred> [].};;$file;;8;;9;;"";;100;;Prefix
 """