summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/cgen.nim4
-rw-r--r--compiler/cgmeth.nim53
-rw-r--r--compiler/compilerlog.nim9
-rw-r--r--compiler/docgen2.nim4
-rw-r--r--compiler/jsgen.nim8
-rw-r--r--compiler/lookups.nim2
-rw-r--r--compiler/modulegraphs.nim14
-rw-r--r--compiler/msgs.nim9
-rw-r--r--compiler/options.nim4
-rw-r--r--compiler/passaux.nim18
-rw-r--r--compiler/passes.nim22
-rw-r--r--compiler/patterns.nim2
-rw-r--r--compiler/plugins/locals/locals.nim2
-rw-r--r--compiler/rodwrite.nim2
-rw-r--r--compiler/sem.nim18
-rw-r--r--compiler/semasgn.nim7
-rw-r--r--compiler/semcall.nim4
-rw-r--r--compiler/semdata.nim25
-rw-r--r--compiler/semdestruct.nim13
-rw-r--r--compiler/semexprs.nim45
-rw-r--r--compiler/semfields.nim2
-rw-r--r--compiler/seminst.nim6
-rw-r--r--compiler/semmagic.nim2
-rw-r--r--compiler/semstmts.nim193
-rw-r--r--compiler/semtempl.nim8
-rw-r--r--compiler/semtypes.nim16
-rw-r--r--compiler/sigmatch.nim8
-rw-r--r--compiler/suggest.nim28
-rw-r--r--compiler/transf.nim7
-rw-r--r--compiler/types.nim51
-rw-r--r--compiler/vm.nim5
-rw-r--r--lib/system/alloc.nim5
-rw-r--r--tests/metatype/tautonotgeneric.nim13
-rw-r--r--tests/misc/parsecomb.nim10
-rw-r--r--tools/nimsuggest/nimsuggest.nim314
-rw-r--r--tools/nimsuggest/nimsuggest.nim.cfg9
-rw-r--r--tools/nimsuggest/sexp.nim6
-rw-r--r--tools/nimsuggest/tester.nim156
-rw-r--r--tools/nimsuggest/tests/twithin_macro.nim6
39 files changed, 777 insertions, 333 deletions
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 04f62d66f..7fef34a27 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -1366,7 +1366,7 @@ proc updateCachedModule(m: BModule) =
     cf.flags = {CfileFlag.Cached}
   addFileToCompile(cf)
 
-proc myClose(b: PPassContext, n: PNode): PNode =
+proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
   result = n
   if b == nil or passes.skipCodegen(n): return
   var m = BModule(b)
@@ -1378,7 +1378,7 @@ proc myClose(b: PPassContext, n: PNode): PNode =
 
   if sfMainModule in m.module.flags:
     incl m.flags, objHasKidsValid
-    var disp = generateMethodDispatchers()
+    var disp = generateMethodDispatchers(graph)
     for i in 0..sonsLen(disp)-1: genProcAux(m, disp.sons[i].sym)
     genMainProc(m)
 
diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim
index eda80be30..3a945ae0a 100644
--- a/compiler/cgmeth.nim
+++ b/compiler/cgmeth.nim
@@ -11,7 +11,7 @@
 
 import
   intsets, options, ast, astalgo, msgs, idents, renderer, types, magicsys,
-  sempass2, strutils
+  sempass2, strutils, modulegraphs
 
 proc genConv(n: PNode, d: PType, downcast: bool): PNode =
   var dest = skipTypes(d, abstractPtrs)
@@ -35,19 +35,24 @@ proc genConv(n: PNode, d: PType, downcast: bool): PNode =
   else:
     result = n
 
+proc getDispatcher*(s: PSym): PSym =
+  ## can return nil if is has no dispatcher.
+  let dispn = lastSon(s.ast)
+  if dispn.kind == nkSym:
+    let disp = dispn.sym
+    if sfDispatcher in disp.flags: result = disp
+
 proc methodCall*(n: PNode): PNode =
   result = n
   # replace ordinary method by dispatcher method:
-  var disp = lastSon(result.sons[0].sym.ast).sym
-  assert sfDispatcher in disp.flags
-  result.sons[0].sym = disp
-  # change the arguments to up/downcasts to fit the dispatcher's parameters:
-  for i in countup(1, sonsLen(result)-1):
-    result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true)
-
-# save for incremental compilation:
-var
-  gMethods: seq[tuple[methods: TSymSeq, dispatcher: PSym]] = @[]
+  let disp = getDispatcher(result.sons[0].sym)
+  if disp != nil:
+    result.sons[0].sym = disp
+    # change the arguments to up/downcasts to fit the dispatcher's parameters:
+    for i in countup(1, sonsLen(result)-1):
+      result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true)
+  else:
+    localError(n.info, "'" & $result.sons[0] & "' lacks a dispatcher")
 
 type
   MethodResult = enum No, Invalid, Yes
@@ -144,27 +149,27 @@ proc fixupDispatcher(meth, disp: PSym) =
       if disp.typ.lockLevel < meth.typ.lockLevel:
         disp.typ.lockLevel = meth.typ.lockLevel
 
-proc methodDef*(s: PSym, fromCache: bool) =
-  let L = len(gMethods)
+proc methodDef*(g: ModuleGraph; s: PSym, fromCache: bool) =
+  let L = len(g.methods)
   var witness: PSym
   for i in countup(0, L - 1):
-    let disp = gMethods[i].dispatcher
+    let disp = g.methods[i].dispatcher
     case sameMethodBucket(disp, s)
     of Yes:
-      add(gMethods[i].methods, s)
+      add(g.methods[i].methods, s)
       attachDispatcher(s, lastSon(disp.ast))
       fixupDispatcher(s, disp)
       #echo "fixup ", disp.name.s, " ", disp.id
       when useEffectSystem: checkMethodEffects(disp, s)
-      if sfBase in s.flags and gMethods[i].methods[0] != s:
+      if sfBase in s.flags and g.methods[i].methods[0] != s:
         # already exists due to forwarding definition?
         localError(s.info, "method is not a base")
       return
     of No: discard
     of Invalid:
-      if witness.isNil: witness = gMethods[i].methods[0]
+      if witness.isNil: witness = g.methods[i].methods[0]
   # create a new dispatcher:
-  add(gMethods, (methods: @[s], dispatcher: createDispatcher(s)))
+  add(g.methods, (methods: @[s], dispatcher: createDispatcher(s)))
   #echo "adding ", s.info
   #if fromCache:
   #  internalError(s.info, "no method dispatcher found")
@@ -258,12 +263,12 @@ proc genDispatcher(methods: TSymSeq, relevantCols: IntSet): PSym =
       disp = ret
   result.ast.sons[bodyPos] = disp
 
-proc generateMethodDispatchers*(): PNode =
+proc generateMethodDispatchers*(g: ModuleGraph): PNode =
   result = newNode(nkStmtList)
-  for bucket in countup(0, len(gMethods) - 1):
+  for bucket in countup(0, len(g.methods) - 1):
     var relevantCols = initIntSet()
-    for col in countup(1, sonsLen(gMethods[bucket].methods[0].typ) - 1):
-      if relevantCol(gMethods[bucket].methods, col): incl(relevantCols, col)
-    sortBucket(gMethods[bucket].methods, relevantCols)
+    for col in countup(1, sonsLen(g.methods[bucket].methods[0].typ) - 1):
+      if relevantCol(g.methods[bucket].methods, col): incl(relevantCols, col)
+    sortBucket(g.methods[bucket].methods, relevantCols)
     addSon(result,
-           newSymNode(genDispatcher(gMethods[bucket].methods, relevantCols)))
+           newSymNode(genDispatcher(g.methods[bucket].methods, relevantCols)))
diff --git a/compiler/compilerlog.nim b/compiler/compilerlog.nim
new file mode 100644
index 000000000..fbf8cd9aa
--- /dev/null
+++ b/compiler/compilerlog.nim
@@ -0,0 +1,9 @@
+
+from os import getHomeDir, `/`
+
+proc logStr*(line: string) =
+  var f: File
+  if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
+    f.writeLine(line)
+    f.close()
+
diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim
index 9504ab52f..118f1c7c5 100644
--- a/compiler/docgen2.nim
+++ b/compiler/docgen2.nim
@@ -33,11 +33,11 @@ template closeImpl(body: untyped) {.dirty.} =
     except IOError:
       discard
 
-proc close(p: PPassContext, n: PNode): PNode =
+proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
   closeImpl:
     writeOutput(g.doc, g.module.filename, HtmlExt, useWarning)
 
-proc closeJson(p: PPassContext, n: PNode): PNode =
+proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
   closeImpl:
     writeOutputJson(g.doc, g.module.filename, ".json", useWarning)
 
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 7d40fe44a..8f819686d 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -2237,13 +2237,13 @@ proc myProcess(b: PPassContext, n: PNode): PNode =
   add(p.g.code, p.body)
   globals.unique = p.unique
 
-proc wholeCode*(m: BModule): Rope =
+proc wholeCode(graph: ModuleGraph; m: BModule): Rope =
   for prc in globals.forwarded:
     if not globals.generatedSyms.containsOrIncl(prc.id):
       var p = newProc(globals, m, nil, m.module.options)
       attachProc(p, prc)
 
-  var disp = generateMethodDispatchers()
+  var disp = generateMethodDispatchers(graph)
   for i in 0..sonsLen(disp)-1:
     let prc = disp.sons[i].sym
     if not globals.generatedSyms.containsOrIncl(prc.id):
@@ -2277,7 +2277,7 @@ proc genClass(obj: PType; content: Rope; ext: string) =
   let outfile = changeFileExt(completeCFilePath($cls), ext)
   discard writeRopeIfNotEqual(result, outfile)
 
-proc myClose(b: PPassContext, n: PNode): PNode =
+proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
   if passes.skipCodegen(n): return n
   result = myProcess(b, n)
   var m = BModule(b)
@@ -2285,7 +2285,7 @@ proc myClose(b: PPassContext, n: PNode): PNode =
     let ext = if m.target == targetJS: "js" else: "php"
     let f = if globals.classes.len == 0: m.module.filename
             else: "nimsystem"
-    let code = wholeCode(m)
+    let code = wholeCode(graph, m)
     let outfile =
       if options.outFile.len > 0:
         if options.outFile.isAbsolute: options.outFile
diff --git a/compiler/lookups.nim b/compiler/lookups.nim
index 19a4da07b..089e69ff9 100644
--- a/compiler/lookups.nim
+++ b/compiler/lookups.nim
@@ -115,7 +115,7 @@ proc errorSym*(c: PContext, n: PNode): PSym =
       considerQuotedIdent(m)
     else:
       getIdent("err:" & renderTree(m))
-  result = newSym(skError, ident, getCurrOwner(), n.info)
+  result = newSym(skError, ident, getCurrOwner(c), n.info)
   result.typ = errorType(c)
   incl(result.flags, sfDiscardable)
   # pretend it's imported from some unknown module to prevent cascading errors:
diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim
index 87a35b290..c081a099a 100644
--- a/compiler/modulegraphs.nim
+++ b/compiler/modulegraphs.nim
@@ -40,9 +40,16 @@ type
                               # module dependencies.
     backend*: RootRef # minor hack so that a backend can extend this easily
     config*: ConfigRef
+    doStopCompile*: proc(): bool {.closure.}
+    usageSym*: PSym # for nimsuggest
+    owners*: seq[PSym]
+    methods*: seq[tuple[methods: TSymSeq, dispatcher: PSym]]
 
 {.this: g.}
 
+proc stopCompile*(g: ModuleGraph): bool {.inline.} =
+  result = doStopCompile != nil and doStopCompile()
+
 proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph =
   result = ModuleGraph()
   initStrTable(result.packageSyms)
@@ -54,6 +61,8 @@ proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph =
     result.config = newConfigRef()
   else:
     result.config = config
+  result.owners = @[]
+  result.methods = @[]
 
 proc resetAllModules*(g: ModuleGraph) =
   initStrTable(packageSyms)
@@ -61,6 +70,9 @@ proc resetAllModules*(g: ModuleGraph) =
   modules = @[]
   importStack = @[]
   inclToMod = initTable[int32, int32]()
+  usageSym = nil
+  owners = @[]
+  methods = @[]
 
 proc getModule*(g: ModuleGraph; fileIdx: int32): PSym =
   if fileIdx >= 0 and fileIdx < modules.len:
@@ -73,7 +85,7 @@ proc addDep*(g: ModuleGraph; m: PSym, dep: int32) =
     deps.incl m.position.dependsOn(dep)
     # we compute the transitive closure later when quering the graph lazily.
     # this improve efficiency quite a lot:
-    invalidTransitiveClosure = true
+    #invalidTransitiveClosure = true
 
 proc addIncludeDep*(g: ModuleGraph; module, includeFile: int32) =
   discard hasKeyOrPut(inclToMod, includeFile, module)
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index 49e4fa184..a1ba82263 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -567,6 +567,15 @@ proc newFileInfo(fullPath, projPath: string): TFileInfo =
   if optEmbedOrigSrc in gGlobalOptions or true:
     result.lines = @[]
 
+proc fileInfoKnown*(filename: string): bool =
+  var
+    canon: string
+  try:
+    canon = canonicalizePath(filename)
+  except:
+    canon = filename
+  result = filenameToIndexTbl.hasKey(canon)
+
 proc fileInfoIdx*(filename: string; isKnownFile: var bool): int32 =
   var
     canon: string
diff --git a/compiler/options.nim b/compiler/options.nim
index 7fd7129e5..6281980ff 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -100,7 +100,7 @@ type
 
   IdeCmd* = enum
     ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod,
-    ideHighlight, ideOutline
+    ideHighlight, ideOutline, ideKnown
 
   ConfigRef* = ref object ## eventually all global configuration should be moved here
     cppDefines*: HashSet[string]
@@ -436,6 +436,7 @@ proc parseIdeCmd*(s: string): IdeCmd =
   of "mod": ideMod
   of "highlight": ideHighlight
   of "outline": ideOutline
+  of "known": ideKnown
   else: ideNone
 
 proc `$`*(c: IdeCmd): string =
@@ -450,3 +451,4 @@ proc `$`*(c: IdeCmd): string =
   of ideNone: "none"
   of ideHighlight: "highlight"
   of ideOutline: "outline"
+  of ideKnown: "known"
diff --git a/compiler/passaux.nim b/compiler/passaux.nim
index eeaf12953..2065d5893 100644
--- a/compiler/passaux.nim
+++ b/compiler/passaux.nim
@@ -29,21 +29,3 @@ proc verboseProcess(context: PPassContext, n: PNode): PNode =
     message(n.info, hintProcessing, $idgen.gFrontendId)
 
 const verbosePass* = makePass(open = verboseOpen, process = verboseProcess)
-
-proc cleanUp(c: PPassContext, n: PNode): PNode =
-  result = n
-  # we cannot clean up if dead code elimination is activated
-  if optDeadCodeElim in gGlobalOptions or n == nil: return
-  case n.kind
-  of nkStmtList:
-    for i in countup(0, sonsLen(n) - 1): discard cleanUp(c, n.sons[i])
-  of nkProcDef, nkMethodDef:
-    if n.sons[namePos].kind == nkSym:
-      var s = n.sons[namePos].sym
-      if sfDeadCodeElim notin getModule(s).flags and not astNeeded(s):
-        s.ast.sons[bodyPos] = ast.emptyNode # free the memory
-  else:
-    discard
-
-const cleanupPass* = makePass(process = cleanUp, close = cleanUp)
-
diff --git a/compiler/passes.nim b/compiler/passes.nim
index 328826f82..7966ee88d 100644
--- a/compiler/passes.nim
+++ b/compiler/passes.nim
@@ -24,7 +24,7 @@ type
   TPassOpen* = proc (graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext {.nimcall.}
   TPassOpenCached* =
     proc (graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext {.nimcall.}
-  TPassClose* = proc (p: PPassContext, n: PNode): PNode {.nimcall.}
+  TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.}
   TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.}
 
   TPass* = tuple[open: TPassOpen, openCached: TPassOpenCached,
@@ -94,7 +94,7 @@ proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; cache: IdentCache;
                 m: TPassData): TPassData =
   var c = p.open(g, module, cache)
   result.input = p.process(c, m.input)
-  result.closeOutput = if p.close != nil: p.close(c, m.closeOutput)
+  result.closeOutput = if p.close != nil: p.close(g, c, m.closeOutput)
                        else: m.closeOutput
 
 proc carryPasses*(g: ModuleGraph; nodes: PNode, module: PSym;
@@ -121,10 +121,10 @@ proc openPassesCached(g: ModuleGraph; a: var TPassContextArray, module: PSym,
     else:
       a[i] = nil
 
-proc closePasses(a: var TPassContextArray) =
+proc closePasses(graph: ModuleGraph; a: var TPassContextArray) =
   var m: PNode = nil
   for i in countup(0, gPassesLen - 1):
-    if not isNil(gPasses[i].close): m = gPasses[i].close(a[i], m)
+    if not isNil(gPasses[i].close): m = gPasses[i].close(graph, a[i], m)
     a[i] = nil                # free the memory here
 
 proc processTopLevelStmt(n: PNode, a: var TPassContextArray): bool =
@@ -142,11 +142,11 @@ proc processTopLevelStmtCached(n: PNode, a: var TPassContextArray) =
   for i in countup(0, gPassesLen - 1):
     if not isNil(gPasses[i].openCached): m = gPasses[i].process(a[i], m)
 
-proc closePassesCached(a: var TPassContextArray) =
+proc closePassesCached(graph: ModuleGraph; a: var TPassContextArray) =
   var m: PNode = nil
   for i in countup(0, gPassesLen - 1):
     if not isNil(gPasses[i].openCached) and not isNil(gPasses[i].close):
-      m = gPasses[i].close(a[i], m)
+      m = gPasses[i].close(graph, a[i], m)
     a[i] = nil                # free the memory here
 
 proc resolveMod(module, relativeTo: string): int32 =
@@ -171,6 +171,7 @@ proc processImplicits(implicits: seq[string], nodeKind: TNodeKind,
 
 proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
                     rd: PRodReader; cache: IdentCache): bool {.discardable.} =
+  if graph.stopCompile(): return true
   var
     p: TParsers
     a: TPassContextArray
@@ -198,6 +199,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
         processImplicits implicitIncludes, nkIncludeStmt, a, module
 
       while true:
+        if graph.stopCompile(): break
         var n = parseTopLevelStmt(p)
         if n.kind == nkEmpty: break
         if sfNoForward in module.flags:
@@ -213,12 +215,14 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
         elif not processTopLevelStmt(n, a): break
       closeParsers(p)
       if s.kind != llsStdIn: break
-    closePasses(a)
+    closePasses(graph, a)
     # id synchronization point for more consistent code generation:
     idSynchronizationPoint(1000)
   else:
     openPassesCached(graph, a, module, rd)
     var n = loadInitSection(rd)
-    for i in countup(0, sonsLen(n) - 1): processTopLevelStmtCached(n.sons[i], a)
-    closePassesCached(a)
+    for i in countup(0, sonsLen(n) - 1):
+      if graph.stopCompile(): break
+      processTopLevelStmtCached(n.sons[i], a)
+    closePassesCached(graph, a)
   result = true
diff --git a/compiler/patterns.nim b/compiler/patterns.nim
index 09b8d3305..859fe2a81 100644
--- a/compiler/patterns.nim
+++ b/compiler/patterns.nim
@@ -289,7 +289,7 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
         # constraint not fulfilled:
         if not ok: return nil
 
-  markUsed(n.info, s)
+  markUsed(n.info, s, c.graph.usageSym)
   if ctx.subMatch:
     assert m.len == 3
     m.sons[1] = result
diff --git a/compiler/plugins/locals/locals.nim b/compiler/plugins/locals/locals.nim
index 8a3f67dd4..338e7bcac 100644
--- a/compiler/plugins/locals/locals.nim
+++ b/compiler/plugins/locals/locals.nim
@@ -28,7 +28,7 @@ proc semLocals(c: PContext, n: PNode): PNode =
           it.typ.skipTypes({tyGenericInst, tyVar}).kind notin
             {tyVarargs, tyOpenArray, tyTypeDesc, tyStatic, tyExpr, tyStmt, tyEmpty}:
 
-        var field = newSym(skField, it.name, getCurrOwner(), n.info)
+        var field = newSym(skField, it.name, getCurrOwner(c), n.info)
         field.typ = it.typ.skipTypes({tyGenericInst, tyVar})
         field.position = counter
         inc(counter)
diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim
index 4ce9e616a..d61d817dd 100644
--- a/compiler/rodwrite.nim
+++ b/compiler/rodwrite.nim
@@ -641,7 +641,7 @@ proc myOpen(g: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   rawAddInterfaceSym(w, module)
   result = w
 
-proc myClose(c: PPassContext, n: PNode): PNode =
+proc myClose(graph: ModuleGraph; c: PPassContext, n: PNode): PNode =
   result = process(c, n)
   var w = PRodWriter(c)
   writeRod(w)
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 59d726b43..682e9d0ca 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -166,7 +166,7 @@ proc commonType*(x, y: PType): PType =
         result.addSonSkipIntLit(r)
 
 proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym =
-  result = newSym(kind, considerQuotedIdent(n), getCurrOwner(), n.info)
+  result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info)
 
 proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
   proc `$`(kind: TSymKind): string = substr(system.`$`(kind), 2).toLowerAscii
@@ -186,9 +186,9 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
     # when there is a nested proc inside a template, semtmpl
     # will assign a wrong owner during the first pass over the
     # template; we must fix it here: see #909
-    result.owner = getCurrOwner()
+    result.owner = getCurrOwner(c)
   else:
-    result = newSym(kind, considerQuotedIdent(n), getCurrOwner(), n.info)
+    result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info)
   #if kind in {skForVar, skLet, skVar} and result.owner.kind == skModule:
   #  incl(result.flags, sfGlobal)
 
@@ -376,7 +376,7 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
                   flags: TExprFlags = {}): PNode =
   pushInfoContext(nOrig.info)
 
-  markUsed(n.info, sym)
+  markUsed(n.info, sym, c.graph.usageSym)
   styleCheckUse(n.info, sym)
   if sym == c.p.owner:
     globalError(n.info, errRecursiveDependencyX, sym.name.s)
@@ -434,7 +434,7 @@ proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   c.instTypeBoundOp = sigmatch.instTypeBoundOp
 
   pushProcCon(c, module)
-  pushOwner(c.module)
+  pushOwner(c, c.module)
   c.importTable = openScope(c)
   c.importTable.addSym(module) # a module knows itself
   if sfSystemModule in module.flags:
@@ -450,7 +450,7 @@ proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
 
 proc myOpenCached(graph: ModuleGraph; module: PSym; rd: PRodReader): PPassContext =
   result = myOpen(graph, module, rd.cache)
-  for m in items(rd.methods): methodDef(m, true)
+  for m in items(rd.methods): methodDef(graph, m, true)
 
 proc isImportSystemStmt(n: PNode): bool =
   if magicsys.systemModule == nil: return false
@@ -502,7 +502,7 @@ proc recoverContext(c: PContext) =
   # faster than wrapping every stack operation in a 'try finally' block and
   # requires far less code.
   c.currentScope = c.topLevelScope
-  while getCurrOwner().kind != skModule: popOwner()
+  while getCurrOwner(c).kind != skModule: popOwner(c)
   while c.p != nil and c.p.owner.kind != skModule: c.p = c.p.next
 
 proc myProcess(context: PPassContext, n: PNode): PNode =
@@ -523,7 +523,7 @@ proc myProcess(context: PPassContext, n: PNode): PNode =
       else: result = ast.emptyNode
       #if gCmd == cmdIdeTools: findSuggest(c, n)
 
-proc myClose(context: PPassContext, n: PNode): PNode =
+proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode =
   var c = PContext(context)
   closeScope(c)         # close module's scope
   rawCloseScope(c)      # imported symbols; don't check for unused ones!
@@ -533,7 +533,7 @@ proc myClose(context: PPassContext, n: PNode): PNode =
   addCodeForGenerics(c, result)
   if c.module.ast != nil:
     result.add(c.module.ast)
-  popOwner()
+  popOwner(c)
   popProcCon(c)
 
 const semPass* = makePass(myOpen, myOpenCached, myProcess, myClose)
diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim
index c4dc13624..70765d087 100644
--- a/compiler/semasgn.nim
+++ b/compiler/semasgn.nim
@@ -105,7 +105,7 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
   of attachedDestructor:
     let op = t.destructor
     if op != nil:
-      markUsed(c.info, op)
+      markUsed(c.info, op, c.c.graph.usageSym)
       styleCheckUse(c.info, op)
       body.add newDestructorCall(op, x)
       result = true
@@ -123,14 +123,14 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
         op = t.assignment
         if op == nil:
           op = liftBody(c.c, t, c.info)
-      markUsed(c.info, op)
+      markUsed(c.info, op, c.c.graph.usageSym)
       styleCheckUse(c.info, op)
       body.add newAsgnCall(c.c, op, x, y)
       result = true
   of attachedDeepCopy:
     let op = t.deepCopy
     if op != nil:
-      markUsed(c.info, op)
+      markUsed(c.info, op, c.c.graph.usageSym)
       styleCheckUse(c.info, op)
       body.add newDeepCopyCall(op, x, y)
       result = true
@@ -248,6 +248,7 @@ proc addParam(procType: PType; param: PSym) =
 proc liftBody(c: PContext; typ: PType; info: TLineInfo): PSym =
   var a: TLiftCtx
   a.info = info
+  a.c = c
   let body = newNodeI(nkStmtList, info)
   result = newSym(skProc, getIdent":lifted=", typ.owner, info)
   a.fn = result
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index ae7bab89d..fe73d60e6 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -335,7 +335,7 @@ proc inferWithMetatype(c: PContext, formal: PType,
 proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode =
   assert x.state == csMatch
   var finalCallee = x.calleeSym
-  markUsed(n.sons[0].info, finalCallee)
+  markUsed(n.sons[0].info, finalCallee, c.graph.usageSym)
   styleCheckUse(n.sons[0].info, finalCallee)
   assert finalCallee.ast != nil
   if x.hasFauxMatch:
@@ -411,7 +411,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
     let tm = typeRel(m, formal, arg, true)
     if tm in {isNone, isConvertible}: return nil
   var newInst = generateInstance(c, s, m.bindings, n.info)
-  markUsed(n.info, s)
+  markUsed(n.info, s, c.graph.usageSym)
   styleCheckUse(n.info, s)
   result = newSymNode(newInst, n.info)
 
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index b501ec440..77d461007 100644
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -124,22 +124,19 @@ proc scopeDepth*(c: PContext): int {.inline.} =
   result = if c.currentScope != nil: c.currentScope.depthLevel
            else: 0
 
-var gOwners*: seq[PSym] = @[]
-
-proc getCurrOwner*(): PSym =
+proc getCurrOwner*(c: PContext): PSym =
   # owner stack (used for initializing the
   # owner field of syms)
   # the documentation comment always gets
   # assigned to the current owner
-  # BUGFIX: global array is needed!
-  result = gOwners[^1]
+  result = c.graph.owners[^1]
 
-proc pushOwner*(owner: PSym) =
-  add(gOwners, owner)
+proc pushOwner*(c: PContext; owner: PSym) =
+  add(c.graph.owners, owner)
 
-proc popOwner*() =
-  var length = len(gOwners)
-  if length > 0: setLen(gOwners, length - 1)
+proc popOwner*(c: PContext) =
+  var length = len(c.graph.owners)
+  if length > 0: setLen(c.graph.owners, length - 1)
   else: internalError("popOwner")
 
 proc lastOptionEntry*(c: PContext): POptionEntry =
@@ -225,7 +222,7 @@ proc addToLib*(lib: PLib, sym: PSym) =
   sym.annex = lib
 
 proc newTypeS*(kind: TTypeKind, c: PContext): PType =
-  result = newType(kind, getCurrOwner())
+  result = newType(kind, getCurrOwner(c))
 
 proc makePtrType*(c: PContext, baseType: PType): PType =
   result = newTypeS(tyPtr, c)
@@ -244,7 +241,7 @@ proc makeTypeDesc*(c: PContext, typ: PType): PType =
 
 proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
   let typedesc = makeTypeDesc(c, typ)
-  let sym = newSym(skType, c.cache.idAnon, getCurrOwner(), info).linkTo(typedesc)
+  let sym = newSym(skType, c.cache.idAnon, getCurrOwner(c), info).linkTo(typedesc)
   return newSymNode(sym, info)
 
 proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
@@ -254,7 +251,7 @@ proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
 
 proc newTypeWithSons*(c: PContext, kind: TTypeKind,
                       sons: seq[PType]): PType =
-  result = newType(kind, getCurrOwner())
+  result = newType(kind, getCurrOwner(c))
   result.sons = sons
 
 proc makeStaticExpr*(c: PContext, n: PNode): PNode =
@@ -325,7 +322,7 @@ proc errorNode*(c: PContext, n: PNode): PNode =
 
 proc fillTypeS*(dest: PType, kind: TTypeKind, c: PContext) =
   dest.kind = kind
-  dest.owner = getCurrOwner()
+  dest.owner = getCurrOwner(c)
   dest.size = - 1
 
 proc makeRangeType*(c: PContext; first, last: BiggestInt;
diff --git a/compiler/semdestruct.nim b/compiler/semdestruct.nim
index a8873bbe2..b09404b39 100644
--- a/compiler/semdestruct.nim
+++ b/compiler/semdestruct.nim
@@ -51,7 +51,7 @@ proc doDestructorStuff(c: PContext, s: PSym, n: PNode) =
       let destructableT = instantiateDestructor(c, t.sons[i])
       if destructableT != nil:
         n.sons[bodyPos].addSon(newNode(nkCall, t.sym.info, @[
-            useSym(destructableT.destructor),
+            useSym(destructableT.destructor, c.graph.usageSym),
             n.sons[paramsPos][1][0]]))
 
 proc destroyFieldOrFields(c: PContext, field: PNode, holder: PNode): PNode
@@ -60,8 +60,8 @@ proc destroySym(c: PContext, field: PSym, holder: PNode): PNode =
   let destructableT = instantiateDestructor(c, field.typ)
   if destructableT != nil:
     result = newNode(nkCall, field.info, @[
-      useSym(destructableT.destructor),
-      newNode(nkDotExpr, field.info, @[holder, useSym(field)])])
+      useSym(destructableT.destructor, c.graph.usageSym),
+      newNode(nkDotExpr, field.info, @[holder, useSym(field, c.graph.usageSym)])])
 
 proc destroyCase(c: PContext, n: PNode, holder: PNode): PNode =
   var nonTrivialFields = 0
@@ -181,7 +181,8 @@ proc createDestructorCall(c: PContext, s: PSym): PNode =
   let destructableT = instantiateDestructor(c, varTyp)
   if destructableT != nil:
     let call = semStmt(c, newNode(nkCall, s.info, @[
-      useSym(destructableT.destructor), useSym(s)]))
+      useSym(destructableT.destructor, c.graph.usageSym),
+      useSym(s, c.graph.usageSym)]))
     result = newNode(nkDefer, s.info, @[call])
 
 proc insertDestructors(c: PContext,
@@ -233,8 +234,8 @@ proc insertDestructors(c: PContext,
       tryStmt.addSon(
         newNode(nkFinally, info, @[
           semStmt(c, newNode(nkCall, info, @[
-            useSym(destructableT.destructor),
-            useSym(varId.sym)]))]))
+            useSym(destructableT.destructor, c.graph.usageSym),
+            useSym(varId.sym, c.graph.usageSym)]))]))
 
       result.outer = newNodeI(nkStmtList, info)
       varSection.sons.setLen(j+1)
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index d9a5f6c50..6db7724f9 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -12,10 +12,10 @@
 
 proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
                      flags: TExprFlags = {}): PNode =
-  markUsed(n.info, s)
+  markUsed(n.info, s, c.graph.usageSym)
   styleCheckUse(n.info, s)
   pushInfoContext(n.info)
-  result = evalTemplate(n, s, getCurrOwner(), efFromHlo in flags)
+  result = evalTemplate(n, s, getCurrOwner(c), efFromHlo in flags)
   if efNoSemCheck notin flags: result = semAfterMacroCall(c, result, s, flags)
   popInfoContext()
 
@@ -209,7 +209,7 @@ proc semConv(c: PContext, n: PNode): PNode =
       let it = op.sons[i]
       let status = checkConvertible(c, result.typ, it.typ)
       if status in {convOK, convNotNeedeed}:
-        markUsed(n.info, it.sym)
+        markUsed(n.info, it.sym, c.graph.usageSym)
         styleCheckUse(n.info, it.sym)
         markIndirect(c, it.sym)
         return it
@@ -926,7 +926,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
   let s = getGenSym(c, sym)
   case s.kind
   of skConst:
-    markUsed(n.info, s)
+    markUsed(n.info, s, c.graph.usageSym)
     styleCheckUse(n.info, s)
     case skipTypes(s.typ, abstractInst-{tyTypeDesc}).kind
     of  tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128,
@@ -950,20 +950,20 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
       result = newSymNode(s, n.info)
   of skMacro:
     if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0:
-      markUsed(n.info, s)
+      markUsed(n.info, s, c.graph.usageSym)
       styleCheckUse(n.info, s)
       result = newSymNode(s, n.info)
     else:
       result = semMacroExpr(c, n, n, s, flags)
   of skTemplate:
     if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0:
-      markUsed(n.info, s)
+      markUsed(n.info, s, c.graph.usageSym)
       styleCheckUse(n.info, s)
       result = newSymNode(s, n.info)
     else:
       result = semTemplateExpr(c, n, s, flags)
   of skParam:
-    markUsed(n.info, s)
+    markUsed(n.info, s, c.graph.usageSym)
     styleCheckUse(n.info, s)
     if s.typ.kind == tyStatic and s.typ.n != nil:
       # XXX see the hack in sigmatch.nim ...
@@ -985,7 +985,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
     if s.magic == mNimvm:
       localError(n.info, "illegal context for 'nimvm' magic")
 
-    markUsed(n.info, s)
+    markUsed(n.info, s, c.graph.usageSym)
     styleCheckUse(n.info, s)
     result = newSymNode(s, n.info)
     # We cannot check for access to outer vars for example because it's still
@@ -1003,7 +1003,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
       n.typ = s.typ
       return n
   of skType:
-    markUsed(n.info, s)
+    markUsed(n.info, s, c.graph.usageSym)
     styleCheckUse(n.info, s)
     if s.typ.kind == tyStatic and s.typ.n != nil:
       return s.typ.n
@@ -1025,7 +1025,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
           if f != nil and fieldVisible(c, f):
             # is the access to a public field or in the same module or in a friend?
             doAssert f == s
-            markUsed(n.info, f)
+            markUsed(n.info, f, c.graph.usageSym)
             styleCheckUse(n.info, f)
             result = newNodeIT(nkDotExpr, n.info, f.typ)
             result.add makeDeref(newSymNode(p.selfSym))
@@ -1038,11 +1038,11 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
           if ty.sons[0] == nil: break
           ty = skipTypes(ty.sons[0], skipPtrs)
     # old code, not sure if it's live code:
-    markUsed(n.info, s)
+    markUsed(n.info, s, c.graph.usageSym)
     styleCheckUse(n.info, s)
     result = newSymNode(s, n.info)
   else:
-    markUsed(n.info, s)
+    markUsed(n.info, s, c.graph.usageSym)
     styleCheckUse(n.info, s)
     result = newSymNode(s, n.info)
 
@@ -1062,7 +1062,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
       result = symChoice(c, n, s, scClosed)
       if result.kind == nkSym: result = semSym(c, n, s, flags)
     else:
-      markUsed(n.sons[1].info, s)
+      markUsed(n.sons[1].info, s, c.graph.usageSym)
       result = semSym(c, n, s, flags)
     styleCheckUse(n.sons[1].info, s)
     return
@@ -1087,7 +1087,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
         result = newSymNode(f)
         result.info = n.info
         result.typ = ty
-        markUsed(n.info, f)
+        markUsed(n.info, f, c.graph.usageSym)
         styleCheckUse(n.info, f)
         return
     of tyTypeParamsHolders:
@@ -1120,7 +1120,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
     if f != nil:
       if fieldVisible(c, f):
         # is the access to a public field or in the same module or in a friend?
-        markUsed(n.sons[1].info, f)
+        markUsed(n.sons[1].info, f, c.graph.usageSym)
         styleCheckUse(n.sons[1].info, f)
         n.sons[0] = makeDeref(n.sons[0])
         n.sons[1] = newSymNode(f) # we now have the correct field
@@ -1134,7 +1134,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
   elif ty.kind == tyTuple and ty.n != nil:
     f = getSymFromList(ty.n, i)
     if f != nil:
-      markUsed(n.sons[1].info, f)
+      markUsed(n.sons[1].info, f, c.graph.usageSym)
       styleCheckUse(n.sons[1].info, f)
       n.sons[0] = makeDeref(n.sons[0])
       n.sons[1] = newSymNode(f)
@@ -1581,9 +1581,8 @@ proc getMagicSym(magic: TMagic): PSym =
   result = newSym(skProc, getIdent($magic), systemModule, gCodegenLineInfo)
   result.magic = magic
 
-proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo,
-                owner = getCurrOwner()): PSym =
-  result = newSym(kind, c.cache.idAnon, owner, info)
+proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym =
+  result = newSym(kind, c.cache.idAnon, getCurrOwner(c), info)
   result.flags = {sfGenSym}
 
 proc semExpandToAst(c: PContext, n: PNode): PNode =
@@ -1592,7 +1591,7 @@ proc semExpandToAst(c: PContext, n: PNode): PNode =
   if expandedSym.kind == skError: return n
 
   macroCall.sons[0] = newSymNode(expandedSym, macroCall.info)
-  markUsed(n.info, expandedSym)
+  markUsed(n.info, expandedSym, c.graph.usageSym)
   styleCheckUse(n.info, expandedSym)
 
   for i in countup(1, macroCall.len-1):
@@ -1683,7 +1682,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   # open a scope for temporary symbol inclusions:
   let oldScope = c.currentScope
   openScope(c)
-  let oldOwnerLen = len(gOwners)
+  let oldOwnerLen = len(c.graph.owners)
   let oldGenerics = c.generics
   let oldErrorOutputs = errorOutputs
   errorOutputs = {}
@@ -1709,7 +1708,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   c.inGenericInst = oldInGenericInst
   c.p = oldProcCon
   msgs.setInfoContextLen(oldContextLen)
-  setLen(gOwners, oldOwnerLen)
+  setLen(c.graph.owners, oldOwnerLen)
   c.currentScope = oldScope
   errorOutputs = oldErrorOutputs
   msgs.gErrorCounter = oldErrorCount
@@ -2108,7 +2107,7 @@ proc semBlock(c: PContext, n: PNode): PNode =
     if sfGenSym notin labl.flags:
       addDecl(c, labl)
     n.sons[0] = newSymNode(labl, n.sons[0].info)
-    suggestSym(n.sons[0].info, labl)
+    suggestSym(n.sons[0].info, labl, c.graph.usageSym)
     styleCheckDef(labl)
   n.sons[1] = semExpr(c, n.sons[1])
   n.typ = n.sons[1].typ
diff --git a/compiler/semfields.nim b/compiler/semfields.nim
index 9d8cea862..5ac1ad11a 100644
--- a/compiler/semfields.nim
+++ b/compiler/semfields.nim
@@ -108,7 +108,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
   var trueSymbol = strTableGet(magicsys.systemModule.tab, getIdent"true")
   if trueSymbol == nil:
     localError(n.info, errSystemNeeds, "true")
-    trueSymbol = newSym(skUnknown, getIdent"true", getCurrOwner(), n.info)
+    trueSymbol = newSym(skUnknown, getIdent"true", getCurrOwner(c), n.info)
     trueSymbol.typ = getSysType(tyBool)
 
   result.sons[0] = newSymNode(trueSymbol, n.info)
diff --git a/compiler/seminst.nim b/compiler/seminst.nim
index acaade494..82d27cf10 100644
--- a/compiler/seminst.nim
+++ b/compiler/seminst.nim
@@ -61,7 +61,7 @@ iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym
     if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic}+tyTypeClasses:
       continue
     let symKind = if q.typ.kind == tyStatic: skConst else: skType
-    var s = newSym(symKind, q.name, getCurrOwner(), q.info)
+    var s = newSym(symKind, q.name, getCurrOwner(c), q.info)
     s.flags = s.flags + {sfUsed, sfFromGeneric}
     var t = PType(idTableGet(pt, q.typ))
     if t == nil:
@@ -255,7 +255,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
   incl(result.flags, sfFromGeneric)
   result.owner = fn
   result.ast = n
-  pushOwner(result)
+  pushOwner(c, result)
 
   openScope(c)
   let gp = n.sons[genericParamsPos]
@@ -304,7 +304,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
   popProcCon(c)
   popInfoContext()
   closeScope(c)           # close scope for parameters
-  popOwner()
+  popOwner(c)
   c.currentScope = oldScope
   discard c.friendModules.pop()
   dec(c.instCounter)
diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim
index e72172c81..5eed1e702 100644
--- a/compiler/semmagic.nim
+++ b/compiler/semmagic.nim
@@ -107,7 +107,7 @@ proc semTypeTraits(c: PContext, n: PNode): PNode =
   if t.sonsLen > 0:
     # This is either a type known to sem or a typedesc
     # param to a regular proc (again, known at instantiation)
-    result = evalTypeTrait(n[0], t, getCurrOwner())
+    result = evalTypeTrait(n[0], t, getCurrOwner(c))
   else:
     # a typedesc variable, pass unmodified to evals
     result = n
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 282d4da88..c28dbf82f 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -36,7 +36,7 @@ proc semBreakOrContinue(c: PContext, n: PNode): PNode =
         x.info = n.info
         incl(s.flags, sfUsed)
         n.sons[0] = x
-        suggestSym(x.info, s)
+        suggestSym(x.info, s, c.graph.usageSym)
         styleCheckUse(x.info, s)
       else:
         localError(n.info, errInvalidControlFlowX, s.name.s)
@@ -367,11 +367,13 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
   if isTopLevel(c):
     result = semIdentWithPragma(c, kind, n, {sfExported})
     incl(result.flags, sfGlobal)
+    #if kind in {skVar, skLet}:
+    #  echo "global variable here ", n.info, " ", result.name.s
   else:
     result = semIdentWithPragma(c, kind, n, {})
     if result.owner.kind == skModule:
       incl(result.flags, sfGlobal)
-  suggestSym(n.info, result)
+  suggestSym(n.info, result, c.graph.usageSym)
   styleCheckDef(result)
 
 proc checkNilable(v: PSym) =
@@ -622,7 +624,7 @@ proc semForVars(c: PContext, n: PNode): PNode =
   if iter.kind != tyTuple or length == 3:
     if length == 3:
       var v = symForVar(c, n.sons[0])
-      if getCurrOwner().kind == skModule: incl(v.flags, sfGlobal)
+      if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
       # BUGFIX: don't use `iter` here as that would strip away
       # the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
       # for an example:
@@ -636,7 +638,7 @@ proc semForVars(c: PContext, n: PNode): PNode =
   else:
     for i in countup(0, length - 3):
       var v = symForVar(c, n.sons[i])
-      if getCurrOwner().kind == skModule: incl(v.flags, sfGlobal)
+      if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
       v.typ = iter.sons[i]
       n.sons[i] = newSymNode(v)
       if sfGenSym notin v.flags and not isDiscardUnderscore(v):
@@ -746,7 +748,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
       # We have a generic type declaration here. In generic types,
       # symbol lookup needs to be done here.
       openScope(c)
-      pushOwner(s)
+      pushOwner(c, s)
       if s.magic == mNone: s.typ.kind = tyGenericBody
       # XXX for generic type aliases this is not correct! We need the
       # underlying Id really:
@@ -770,11 +772,11 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
         body.sym = s
         body.size = -1 # could not be computed properly
         s.typ.sons[sonsLen(s.typ) - 1] = body
-      popOwner()
+      popOwner(c)
       closeScope(c)
     elif a.sons[2].kind != nkEmpty:
       # process the type's body:
-      pushOwner(s)
+      pushOwner(c, s)
       var t = semTypeNode(c, a.sons[2], s.typ)
       if s.typ == nil:
         s.typ = t
@@ -783,7 +785,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
         assignType(s.typ, t)
         #debug s.typ
       s.ast = a
-      popOwner()
+      popOwner(c)
     let aa = a.sons[2]
     if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and
        aa.sons[0].kind == nkObjectTy:
@@ -794,7 +796,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
       internalAssert st.lastSon.sym == nil
       incl st.flags, tfRefsAnonObj
       let obj = newSym(skType, getIdent(s.name.s & ":ObjectType"),
-                              getCurrOwner(), s.info)
+                              getCurrOwner(c), s.info)
       obj.typ = st.lastSon
       st.lastSon.sym = obj
 
@@ -928,7 +930,7 @@ proc semBorrow(c: PContext, n: PNode, s: PSym) =
 
 proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) =
   if t != nil:
-    var s = newSym(skResult, getIdent"result", getCurrOwner(), info)
+    var s = newSym(skResult, getIdent"result", getCurrOwner(c), info)
     s.typ = t
     incl(s.flags, sfUsed)
     addParamOrResult(c, s, owner)
@@ -1003,12 +1005,12 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
   checkSonsLen(n, bodyPos + 1)
   var s: PSym
   if n[namePos].kind != nkSym:
-    s = newSym(skProc, c.cache.idAnon, getCurrOwner(), n.info)
+    s = newSym(skProc, c.cache.idAnon, getCurrOwner(c), n.info)
     s.ast = n
     n.sons[namePos] = newSymNode(s)
   else:
     s = n[namePos].sym
-  pushOwner(s)
+  pushOwner(c, s)
   openScope(c)
   var gp: PNode
   if n.sons[genericParamsPos].kind != nkEmpty:
@@ -1048,7 +1050,7 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
   else:
     localError(n.info, errImplOfXexpected, s.name.s)
   closeScope(c)           # close scope for parameters
-  popOwner()
+  popOwner(c)
   result.typ = s.typ
 
 proc semDo(c: PContext, n: PNode, flags: TExprFlags): PNode =
@@ -1082,7 +1084,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
                  params[i].sym.name.s)
     #params[i].sym.owner = s
   openScope(c)
-  pushOwner(s)
+  pushOwner(c, s)
   addParams(c, params, skProc)
   pushProcCon(c, s)
   addResult(c, n.typ.sons[0], n.info, skProc)
@@ -1090,7 +1092,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
   let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
   n.sons[bodyPos] = transformBody(c.module, semBody, s)
   popProcCon(c)
-  popOwner()
+  popOwner(c)
   closeScope(c)
 
   # alternative variant (not quite working):
@@ -1178,11 +1180,58 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
       localError(n.info, errGenerated,
                  "'destroy' or 'deepCopy' expected for 'override'")
 
+proc cursorInProcAux(n: PNode): bool =
+  if inCheckpoint(n.info) != cpNone: return true
+  for i in 0..<n.safeLen:
+    if cursorInProcAux(n[i]): return true
+
+proc cursorInProc(n: PNode): bool =
+  if n.info.fileIndex == gTrackPos.fileIndex:
+    result = cursorInProcAux(n)
+
 type
   TProcCompilationSteps = enum
     stepRegisterSymbol,
     stepDetermineType,
 
+import compilerlog
+
+proc hasObjParam(s: PSym): bool =
+  var t = s.typ
+  for col in countup(1, sonsLen(t)-1):
+    if skipTypes(t.sons[col], skipPtrs).kind == tyObject:
+      return true
+
+proc finishMethod(c: PContext, s: PSym) =
+  if hasObjParam(s):
+    methodDef(c.graph, s, false)
+
+proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
+  if isGenericRoutine(s):
+    let tt = s.typ
+    var foundObj = false
+    # we start at 1 for now so that tparsecombnum continues to compile.
+    # XXX Revisit this problem later.
+    for col in countup(1, sonsLen(tt)-1):
+      let t = tt.sons[col]
+      if t != nil and t.kind == tyGenericInvocation:
+        var x = skipTypes(t.sons[0], {tyVar, tyPtr, tyRef, tyGenericInst,
+                                      tyGenericInvocation, tyGenericBody,
+                                      tyAlias})
+        if x.kind == tyObject and t.len-1 == n.sons[genericParamsPos].len:
+          foundObj = true
+          x.methods.safeAdd((col,s))
+    if not foundObj:
+      message(n.info, warnDeprecated, "generic method not attachable to object type")
+  else:
+    # why check for the body? bug #2400 has none. Checking for sfForward makes
+    # no sense either.
+    # and result.sons[bodyPos].kind != nkEmpty:
+    if hasObjParam(s):
+      methodDef(c.graph, s, fromCache=false)
+    else:
+      localError(n.info, errXNeedsParamObjectType, "method")
+
 proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
                 validPragmas: TSpecialWords,
                 phase = stepRegisterSymbol): PNode =
@@ -1197,7 +1246,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     assert phase == stepRegisterSymbol
 
     if n[namePos].kind == nkEmpty:
-      s = newSym(kind, c.cache.idAnon, getCurrOwner(), n.info)
+      s = newSym(kind, c.cache.idAnon, getCurrOwner(c), n.info)
       incl(s.flags, sfUsed)
       isAnon = true
     else:
@@ -1214,7 +1263,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
         return
   else:
     s = n[namePos].sym
-    s.owner = getCurrOwner()
+    s.owner = getCurrOwner(c)
     typeIsDetermined = s.typ == nil
     s.ast = n
     #s.scope = c.currentScope
@@ -1223,7 +1272,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
   # where the proc was declared
   let oldScope = c.currentScope
   #c.currentScope = s.scope
-  pushOwner(s)
+  pushOwner(c, s)
   openScope(c)
   var gp: PNode
   if n.sons[genericParamsPos].kind != nkEmpty:
@@ -1289,8 +1338,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     if importantComments() and not isNil(proto.ast.comment):
       n.comment = proto.ast.comment
     proto.ast = n             # needed for code generation
-    popOwner()
-    pushOwner(s)
+    popOwner(c)
+    pushOwner(c, s)
   s.options = gOptions
   if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n)
   if s.name.s[0] in {'.', '('}:
@@ -1304,30 +1353,40 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     # Macros and Templates can have generic parameters, but they are
     # only used for overload resolution (there is no instantiation of
     # the symbol, so we must process the body now)
-    pushProcCon(c, s)
-    if n.sons[genericParamsPos].kind == nkEmpty or usePseudoGenerics:
-      if not usePseudoGenerics: paramsTypeCheck(c, s.typ)
-
-      c.p.wasForwarded = proto != nil
-      maybeAddResult(c, s, n)
-      if lfDynamicLib notin s.loc.flags:
-        # no semantic checking for importc:
-        let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
-        # unfortunately we cannot skip this step when in 'system.compiles'
-        # context as it may even be evaluated in 'system.compiles':
-        n.sons[bodyPos] = transformBody(c.module, semBody, s)
+    if not usePseudoGenerics and gIdeCmd in {ideSug, ideCon} and not
+        cursorInProc(n.sons[bodyPos]):
+      discard "speed up nimsuggest"
+      if s.kind == skMethod: semMethodPrototype(c, s, n)
     else:
-      if s.typ.sons[0] != nil and kind != skIterator:
-        addDecl(c, newSym(skUnknown, getIdent"result", nil, n.info))
-      openScope(c)
-      n.sons[bodyPos] = semGenericStmt(c, n.sons[bodyPos])
-      closeScope(c)
-      fixupInstantiatedSymbols(c, s)
-    if sfImportc in s.flags:
-      # so we just ignore the body after semantic checking for importc:
-      n.sons[bodyPos] = ast.emptyNode
-    popProcCon(c)
+      pushProcCon(c, s)
+      if n.sons[genericParamsPos].kind == nkEmpty or usePseudoGenerics:
+        if not usePseudoGenerics: paramsTypeCheck(c, s.typ)
+
+        c.p.wasForwarded = proto != nil
+        maybeAddResult(c, s, n)
+        if s.kind == skMethod: semMethodPrototype(c, s, n)
+
+        if lfDynamicLib notin s.loc.flags:
+          # no semantic checking for importc:
+          let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
+          # unfortunately we cannot skip this step when in 'system.compiles'
+          # context as it may even be evaluated in 'system.compiles':
+          n.sons[bodyPos] = transformBody(c.module, semBody, s)
+      else:
+        if s.typ.sons[0] != nil and kind != skIterator:
+          addDecl(c, newSym(skUnknown, getIdent"result", nil, n.info))
+
+        openScope(c)
+        n.sons[bodyPos] = semGenericStmt(c, n.sons[bodyPos])
+        closeScope(c)
+        fixupInstantiatedSymbols(c, s)
+        if s.kind == skMethod: semMethodPrototype(c, s, n)
+      if sfImportc in s.flags:
+        # so we just ignore the body after semantic checking for importc:
+        n.sons[bodyPos] = ast.emptyNode
+      popProcCon(c)
   else:
+    if s.kind == skMethod: semMethodPrototype(c, s, n)
     if proto != nil: localError(n.info, errImplOfXexpected, proto.name.s)
     if {sfImportc, sfBorrow} * s.flags == {} and s.magic == mNone:
       incl(s.flags, sfForward)
@@ -1335,7 +1394,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
   sideEffectsCheck(c, s)
   closeScope(c)           # close scope for parameters
   # c.currentScope = oldScope
-  popOwner()
+  popOwner(c)
   if n.sons[patternPos].kind != nkEmpty:
     c.patterns.add(s)
   if isAnon: result.typ = s.typ
@@ -1354,7 +1413,7 @@ proc semIterator(c: PContext, n: PNode): PNode =
   let isAnon = n[namePos].kind == nkEmpty
   if n[namePos].kind == nkSym:
     # gensym'ed iterators might need to become closure iterators:
-    n[namePos].sym.owner = getCurrOwner()
+    n[namePos].sym.owner = getCurrOwner(c)
     n[namePos].sym.kind = skIterator
   result = semProcAux(c, n, skIterator, iteratorPragmas)
   var s = result.sons[namePos].sym
@@ -1381,46 +1440,22 @@ proc semIterator(c: PContext, n: PNode): PNode =
 proc semProc(c: PContext, n: PNode): PNode =
   result = semProcAux(c, n, skProc, procPragmas)
 
-proc hasObjParam(s: PSym): bool =
-  var t = s.typ
-  for col in countup(1, sonsLen(t)-1):
-    if skipTypes(t.sons[col], skipPtrs).kind == tyObject:
-      return true
-
-proc finishMethod(c: PContext, s: PSym) =
-  if hasObjParam(s):
-    methodDef(s, false)
-
 proc semMethod(c: PContext, n: PNode): PNode =
   if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "method")
   result = semProcAux(c, n, skMethod, methodPragmas)
-  # macros can transform methods to nothing:
+  # macros can transform converters to nothing:
   if namePos >= result.safeLen: return result
   var s = result.sons[namePos].sym
-  if isGenericRoutine(s):
-    let tt = s.typ
-    var foundObj = false
-    # we start at 1 for now so that tparsecombnum continues to compile.
-    # XXX Revisit this problem later.
-    for col in countup(1, sonsLen(tt)-1):
-      let t = tt.sons[col]
-      if t != nil and t.kind == tyGenericInvocation:
-        var x = skipTypes(t.sons[0], {tyVar, tyPtr, tyRef, tyGenericInst,
-                                      tyGenericInvocation, tyGenericBody,
-                                      tyAlias})
-        if x.kind == tyObject and t.len-1 == result.sons[genericParamsPos].len:
-          foundObj = true
-          x.methods.safeAdd((col,s))
-    if not foundObj:
-      message(n.info, warnDeprecated, "generic method not attachable to object type")
-  else:
-    # why check for the body? bug #2400 has none. Checking for sfForward makes
-    # no sense either.
-    # and result.sons[bodyPos].kind != nkEmpty:
-    if hasObjParam(s):
-      methodDef(s, fromCache=false)
-    else:
-      localError(n.info, errXNeedsParamObjectType, "method")
+  # we need to fix the 'auto' return type for the dispatcher here (see tautonotgeneric
+  # test case):
+  let disp = getDispatcher(s)
+  # auto return type?
+  if disp != nil and disp.typ.sons[0] != nil and disp.typ.sons[0].kind == tyExpr:
+    let ret = s.typ.sons[0]
+    disp.typ.sons[0] = ret
+    if disp.ast[resultPos].kind == nkSym:
+      if isEmptyType(ret): disp.ast.sons[resultPos] = emptyNode
+      else: disp.ast[resultPos].sym.typ = ret
 
 proc semConverterDef(c: PContext, n: PNode): PNode =
   if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "converter")
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 420e36116..d7134402f 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -60,7 +60,7 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode =
     # (s.kind notin routineKinds or s.magic != mNone):
     # for instance 'nextTry' is both in tables.nim and astalgo.nim ...
     result = newSymNode(s, n.info)
-    markUsed(n.info, s)
+    markUsed(n.info, s, c.graph.usageSym)
   else:
     # semantic checking requires a type; ``fitNode`` deals with it
     # appropriately
@@ -561,7 +561,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
   styleCheckDef(s)
   # check parameter list:
   #s.scope = c.currentScope
-  pushOwner(s)
+  pushOwner(c, s)
   openScope(c)
   n.sons[namePos] = newSymNode(s, n.sons[namePos].info)
   if n.sons[pragmasPos].kind != nkEmpty:
@@ -615,7 +615,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
   # only parameters are resolved, no type checking is performed
   semIdeForTemplateOrGeneric(c, n.sons[bodyPos], ctx.cursorInBody)
   closeScope(c)
-  popOwner()
+  popOwner(c)
   s.ast = n
   result = n
   if n.sons[bodyPos].kind == nkEmpty:
@@ -760,7 +760,7 @@ proc semPattern(c: PContext, n: PNode): PNode =
   ctx.toMixin = initIntSet()
   ctx.toInject = initIntSet()
   ctx.c = c
-  ctx.owner = getCurrOwner()
+  ctx.owner = getCurrOwner(c)
   result = flattenStmts(semPatternBody(ctx, n))
   if result.kind in {nkStmtList, nkStmtListExpr}:
     if result.len == 1:
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index 440edd226..97af2c5cc 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -306,7 +306,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
     else:
       result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared})
     if result != nil:
-      markUsed(n.info, result)
+      markUsed(n.info, result, c.graph.usageSym)
       styleCheckUse(n.info, result)
       if result.kind == skParam and result.typ.kind == tyTypeDesc:
         # This is a typedesc param. is it already bound?
@@ -608,7 +608,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
     let rec = rectype.sym
     for i in countup(0, sonsLen(n)-3):
       var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported})
-      suggestSym(n.sons[i].info, f)
+      suggestSym(n.sons[i].info, f, c.graph.usageSym)
       f.typ = typ
       f.position = pos
       if (rec != nil) and ({sfImportc, sfExportc} * rec.flags != {}) and
@@ -750,7 +750,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
         return genericParams.sons[i].typ
 
     let owner = if typeClass.sym != nil: typeClass.sym
-                else: getCurrOwner()
+                else: getCurrOwner(c)
     var s = newSym(skType, finalTypId, owner, info)
     if typId == nil: s.flags.incl(sfAnon)
     s.linkTo(typeClass)
@@ -850,7 +850,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
 
   of tyGenericInst:
     if paramType.lastSon.kind == tyUserTypeClass:
-      var cp = copyType(paramType, getCurrOwner(), false)
+      var cp = copyType(paramType, getCurrOwner(c), false)
       cp.kind = tyUserTypeClassInst
       return addImplicitGeneric(cp)
 
@@ -876,10 +876,10 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
       result = liftingWalk(expanded, true)
 
   of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot:
-    result = addImplicitGeneric(copyType(paramType, getCurrOwner(), true))
+    result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), true))
 
   of tyGenericParam:
-    markUsed(info, paramType.sym)
+    markUsed(info, paramType.sym, c.graph.usageSym)
     styleCheckUse(info, paramType.sym)
     if tfWildcard in paramType.flags:
       paramType.flags.excl tfWildcard
@@ -1267,7 +1267,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
     of mExpr:
       result = semTypeNode(c, n.sons[0], nil)
       if result != nil:
-        result = copyType(result, getCurrOwner(), false)
+        result = copyType(result, getCurrOwner(c), false)
         for i in countup(1, n.len - 1):
           result.rawAddSon(semTypeNode(c, n.sons[i], nil))
     of mDistinct:
@@ -1330,7 +1330,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
       else:
         assignType(prev, t)
         result = prev
-      markUsed(n.info, n.sym)
+      markUsed(n.info, n.sym, c.graph.usageSym)
       styleCheckUse(n.info, n.sym)
     else:
       if s.kind != skError: localError(n.info, errTypeExpected)
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index d3c142954..f2caab41f 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -73,7 +73,7 @@ type
 const
   isNilConversion = isConvertible # maybe 'isIntConv' fits better?
 
-proc markUsed*(info: TLineInfo, s: PSym)
+proc markUsed*(info: TLineInfo, s: PSym; usageSym: var PSym)
 
 template hasFauxMatch*(c: TCandidate): bool = c.fauxMatch != tyNone
 
@@ -543,7 +543,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
       else:
         return isNone
     when useEffectSystem:
-      if not compatibleEffects(f, a): return isNone
+      if compatibleEffects(f, a) != efCompat: return isNone
 
   of tyNil:
     result = f.allowsNil
@@ -1291,7 +1291,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
       dest = generateTypeInstance(c, m.bindings, arg, dest)
     let fdest = typeRel(m, f, dest)
     if fdest in {isEqual, isGeneric}:
-      markUsed(arg.info, c.converters[i])
+      markUsed(arg.info, c.converters[i], c.graph.usageSym)
       var s = newSymNode(c.converters[i])
       s.typ = c.converters[i].typ
       s.info = arg.info
@@ -1551,7 +1551,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
       else: result = nil
     else:
       # only one valid interpretation found:
-      markUsed(arg.info, arg.sons[best].sym)
+      markUsed(arg.info, arg.sons[best].sym, m.c.graph.usageSym)
       styleCheckUse(arg.info, arg.sons[best].sym)
       result = paramTypesMatchAux(m, f, arg.sons[best].typ, arg.sons[best],
                                   argOrig)
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index 66876b9b5..9ae427583 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -28,7 +28,7 @@ type
     column*: int                 # Starts at 0
     doc*: string           # Not escaped (yet)
     symkind*: TSymKind
-    forth*: string               # XXX TODO object on symkind
+    forth*: string               # type
     quality*: range[0..100]   # matching quality
     isGlobal*: bool # is a global variable
     tokenLen*: int
@@ -88,7 +88,7 @@ proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo;
     when not defined(noDocgen):
       result.doc = s.extractDocComment
 
-proc `$`(suggest: Suggest): string =
+proc `$`*(suggest: Suggest): string =
   result = $suggest.section
   result.add(sep)
   if suggest.section == ideHighlight:
@@ -131,7 +131,7 @@ proc suggestResult(s: Suggest) =
   if not isNil(suggestionResultHook):
     suggestionResultHook(s)
   else:
-    suggestWriteln($(s))
+    suggestWriteln($s)
 
 proc filterSym(s: PSym): bool {.inline.} =
   result = s.kind != skModule
@@ -297,10 +297,10 @@ proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) =
       suggestOperations(c, n, typ, outputs)
 
 type
-  TCheckPointResult = enum
+  TCheckPointResult* = enum
     cpNone, cpFuzzy, cpExact
 
-proc inCheckpoint(current: TLineInfo): TCheckPointResult =
+proc inCheckpoint*(current: TLineInfo): TCheckPointResult =
   if current.fileIndex == gTrackPos.fileIndex:
     if current.line == gTrackPos.line and
         abs(current.col-gTrackPos.col) < 4:
@@ -353,10 +353,10 @@ when defined(nimsuggest):
     s.allUsages.add(info)
 
 var
-  usageSym*: PSym
+  #usageSym*: PSym
   lastLineInfo*: TLineInfo
 
-proc findUsages(info: TLineInfo; s: PSym) =
+proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) =
   if suggestVersion < 2:
     if usageSym == nil and isTracked(info, s.name.s.len):
       usageSym = s
@@ -385,7 +385,7 @@ proc ensureIdx[T](x: var T, y: int) =
 proc ensureSeq[T](x: var seq[T]) =
   if x == nil: newSeq(x, 0)
 
-proc suggestSym*(info: TLineInfo; s: PSym; isDecl=true) {.inline.} =
+proc suggestSym*(info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} =
   ## misnamed: should be 'symDeclared'
   when defined(nimsuggest):
     if suggestVersion == 2:
@@ -395,20 +395,20 @@ proc suggestSym*(info: TLineInfo; s: PSym; isDecl=true) {.inline.} =
         s.addNoDup(info)
 
     if gIdeCmd == ideUse:
-      findUsages(info, s)
+      findUsages(info, s, usageSym)
     elif gIdeCmd == ideDef:
       findDefinition(info, s)
     elif gIdeCmd == ideDus and s != nil:
       if isTracked(info, s.name.s.len):
         suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100))
-      findUsages(info, s)
+      findUsages(info, s, usageSym)
     elif gIdeCmd == ideHighlight and info.fileIndex == gTrackPos.fileIndex:
       suggestResult(symToSuggest(s, isLocal=false, $ideHighlight, info, 100))
     elif gIdeCmd == ideOutline and info.fileIndex == gTrackPos.fileIndex and
         isDecl:
       suggestResult(symToSuggest(s, isLocal=false, $ideOutline, info, 100))
 
-proc markUsed(info: TLineInfo; s: PSym) =
+proc markUsed(info: TLineInfo; s: PSym; usageSym: var PSym) =
   incl(s.flags, sfUsed)
   if s.kind == skEnumField and s.owner != nil:
     incl(s.owner.flags, sfUsed)
@@ -416,11 +416,11 @@ proc markUsed(info: TLineInfo; s: PSym) =
     if sfDeprecated in s.flags: message(info, warnDeprecated, s.name.s)
     if sfError in s.flags: localError(info, errWrongSymbolX, s.name.s)
   when defined(nimsuggest):
-    suggestSym(info, s, false)
+    suggestSym(info, s, usageSym, false)
 
-proc useSym*(sym: PSym): PNode =
+proc useSym*(sym: PSym; usageSym: var PSym): PNode =
   result = newSymNode(sym)
-  markUsed(result.info, sym)
+  markUsed(result.info, sym, usageSym)
 
 proc safeSemExpr*(c: PContext, n: PNode): PNode =
   # use only for idetools support!
diff --git a/compiler/transf.nim b/compiler/transf.nim
index e14985145..0c53c0cbf 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -694,9 +694,10 @@ proc transformCall(c: PTransf, n: PNode): PTransNode =
     # bugfix: check after 'transformSons' if it's still a method call:
     # use the dispatcher for the call:
     if s.sons[0].kind == nkSym and s.sons[0].sym.kind == skMethod:
-      let t = lastSon(s.sons[0].sym.ast)
-      if t.kind != nkSym or sfDispatcher notin t.sym.flags:
-        methodDef(s.sons[0].sym, false)
+      when false:
+        let t = lastSon(s.sons[0].sym.ast)
+        if t.kind != nkSym or sfDispatcher notin t.sym.flags:
+          methodDef(s.sons[0].sym, false)
       result = methodCall(s).PTransNode
     else:
       result = s.PTransNode
diff --git a/compiler/types.nim b/compiler/types.nim
index d5ec2972a..b237753ae 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -1377,7 +1377,16 @@ proc compatibleEffectsAux(se, re: PNode): bool =
       return false
   result = true
 
-proc compatibleEffects*(formal, actual: PType): bool =
+type
+  EffectsCompat* = enum
+    efCompat
+    efRaisesDiffer
+    efRaisesUnknown
+    efTagsDiffer
+    efTagsUnknown
+    efLockLevelsDiffer
+
+proc compatibleEffects*(formal, actual: PType): EffectsCompat =
   # for proc type compatibility checking:
   assert formal.kind == tyProc and actual.kind == tyProc
   internalAssert formal.n.sons[0].kind == nkEffectList
@@ -1393,18 +1402,21 @@ proc compatibleEffects*(formal, actual: PType): bool =
     # 'r.msgHandler = if isNil(msgHandler): defaultMsgHandler else: msgHandler'
     if not isNil(se) and se.kind != nkArgList:
       # spec requires some exception or tag, but we don't know anything:
-      if real.len == 0: return false
-      result = compatibleEffectsAux(se, real.sons[exceptionEffects])
-      if not result: return
+      if real.len == 0: return efRaisesUnknown
+      let res = compatibleEffectsAux(se, real.sons[exceptionEffects])
+      if not res: return efRaisesDiffer
 
     let st = spec.sons[tagEffects]
     if not isNil(st) and st.kind != nkArgList:
       # spec requires some exception or tag, but we don't know anything:
-      if real.len == 0: return false
-      result = compatibleEffectsAux(st, real.sons[tagEffects])
-      if not result: return
-  result = formal.lockLevel.ord < 0 or
-      actual.lockLevel.ord <= formal.lockLevel.ord
+      if real.len == 0: return efTagsUnknown
+      let res = compatibleEffectsAux(st, real.sons[tagEffects])
+      if not res: return efTagsDiffer
+  if formal.lockLevel.ord < 0 or
+      actual.lockLevel.ord <= formal.lockLevel.ord:
+    result = efCompat
+  else:
+    result = efLockLevelsDiffer
 
 proc isCompileTimeOnly*(t: PType): bool {.inline.} =
   result = t.kind in {tyTypeDesc, tyStatic}
@@ -1508,6 +1520,21 @@ proc typeMismatch*(n: PNode, formal, actual: PType) =
     let named = typeToString(formal)
     let desc = typeToString(formal, preferDesc)
     let x = if named == desc: named else: named & " = " & desc
-    localError(n.info, errGenerated, msgKindToString(errTypeMismatch) &
-        typeToString(actual) & ") " &
-        `%`(msgKindToString(errButExpectedX), [x]))
+    var msg = msgKindToString(errTypeMismatch) &
+              typeToString(actual) & ") " &
+              msgKindToString(errButExpectedX) % [x]
+
+    if formal.kind == tyProc and actual.kind == tyProc:
+      case compatibleEffects(formal, actual)
+      of efCompat: discard
+      of efRaisesDiffer:
+        msg.add "\n.raise effects differ"
+      of efRaisesUnknown:
+        msg.add "\n.raise effect is 'can raise any'"
+      of efTagsDiffer:
+        msg.add "\n.tag effects differ"
+      of efTagsUnknown:
+        msg.add "\n.tag effect is 'any tag allowed'"
+      of efLockLevelsDiffer:
+        msg.add "\nlock levels differ"
+    localError(n.info, errGenerated, msg)
diff --git a/compiler/vm.nim b/compiler/vm.nim
index 5d4784281..6a9545193 100644
--- a/compiler/vm.nim
+++ b/compiler/vm.nim
@@ -1556,7 +1556,10 @@ proc myProcess(c: PPassContext, n: PNode): PNode =
     result = n
   oldErrorCount = msgs.gErrorCounter
 
-const evalPass* = makePass(myOpen, nil, myProcess, myProcess)
+proc myClose(graph: ModuleGraph; c: PPassContext, n: PNode): PNode =
+  myProcess(c, n)
+
+const evalPass* = makePass(myOpen, nil, myProcess, myClose)
 
 proc evalConstExprAux(module: PSym; cache: IdentCache; prc: PSym, n: PNode,
                       mode: TEvalMode): PNode =
diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim
index a124d7537..f28a124d2 100644
--- a/lib/system/alloc.nim
+++ b/lib/system/alloc.nim
@@ -292,10 +292,15 @@ proc writeFreeList(a: MemRegion) =
               it, it.next, it.prev, it.size)
     it = it.next
 
+const nimMaxHeap {.intdefine.} = 0
+
 proc requestOsChunks(a: var MemRegion, size: int): PBigChunk =
   when not defined(emscripten):
     if not a.blockChunkSizeIncrease:
       let usedMem = a.currMem # - a.freeMem
+      when nimMaxHeap != 0:
+        if usedMem > nimMaxHeap * 1024 * 1024:
+          raiseOutOfMem()
       if usedMem < 64 * 1024:
         a.nextChunkSize = PageSize*4
       else:
diff --git a/tests/metatype/tautonotgeneric.nim b/tests/metatype/tautonotgeneric.nim
index a55ae488e..f0d6932f9 100644
--- a/tests/metatype/tautonotgeneric.nim
+++ b/tests/metatype/tautonotgeneric.nim
@@ -1,15 +1,24 @@
 discard """
-  output: "wof!"
+  output: '''wof!
+wof!'''
 """
 
 # bug #1659
 type Animal = ref object {.inheritable.}
 type Dog = ref object of Animal
 
-method say(a: Animal): auto = "wat!"
+method say(a: Animal): auto {.base.} = "wat!"
 method say(a: Dog): auto = "wof!"
 
 proc saySomething(a: Animal): auto = a.say()
 
+
+method ec(a: Animal): auto {.base.} = echo "wat!"
+method ec(a: Dog): auto = echo "wof!"
+
+proc ech(a: Animal): auto = a.ec()
+
+
 var a = Dog()
 echo saySomething(a)
+ech a
diff --git a/tests/misc/parsecomb.nim b/tests/misc/parsecomb.nim
index 05fe97ad1..4ff2f65d2 100644
--- a/tests/misc/parsecomb.nim
+++ b/tests/misc/parsecomb.nim
@@ -29,10 +29,10 @@ method runInput[T, O](self: Parser[T, O], inp: Input[T]): Result[T, O] =
   # XXX: above needed for now, as without the `tmp` bit below, it compiles to invalid C.
   tmp(self)(inp)
 
-method run*[T, O](self: Parser[T, O], toks: seq[T]): Result[T, O] =
+proc run*[T, O](self: Parser[T, O], toks: seq[T]): Result[T, O] =
   self.runInput(Input[T](toks: toks, index: 0))
 
-method chain*[T, O1, O2](self: Parser[T, O1], nextp: proc (v: O1): Parser[T, O2]): Parser[T, O2] =
+proc chain*[T, O1, O2](self: Parser[T, O1], nextp: proc (v: O1): Parser[T, O2]): Parser[T, O2] =
   result = proc (inp: Input[T]): Result[T, O2] =
     let r = self.runInput(inp)
     case r.kind:
@@ -41,7 +41,7 @@ method chain*[T, O1, O2](self: Parser[T, O1], nextp: proc (v: O1): Parser[T, O2]
     of rkFailure:
       Result[T, O2](kind: rkFailure)
 
-method skip[T](self: Input[T], n: int): Input[T] =
+method skip[T](self: Input[T], n: int): Input[T] {.base.} =
   Input[T](toks: self.toks, index: self.index + n)
 
 proc pskip*[T](n: int): Parser[T, tuple[]] =
@@ -69,11 +69,11 @@ proc `+`*[T, O](first: Parser[T, O], second: Parser[T, O]): Parser[T, O] =
 
 # end of primitives (definitions involving Parser(..))
 
-method map*[T, O1, O2](self: Parser[T, O1], p: proc (v: O1): O2): Parser[T, O2] =
+proc map*[T, O1, O2](self: Parser[T, O1], p: proc (v: O1): O2): Parser[T, O2] =
   self.chain(proc (v: O1): Parser[T, O2] =
     unit[T, O2](p(v)))
 
-method then*[T, O1, O2](self: Parser[T, O1], next: Parser[T, O2]): Parser[T, O2] =
+proc then*[T, O1, O2](self: Parser[T, O1], next: Parser[T, O2]): Parser[T, O2] =
   self.chain(proc (v: O1): Parser[T, O2] =
     next)
 
diff --git a/tools/nimsuggest/nimsuggest.nim b/tools/nimsuggest/nimsuggest.nim
index 7891f73e5..57d343ae4 100644
--- a/tools/nimsuggest/nimsuggest.nim
+++ b/tools/nimsuggest/nimsuggest.nim
@@ -13,11 +13,11 @@ import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp
 # Do NOT import suggest. It will lead to wierd bugs with
 # suggestionResultHook, because suggest.nim is included by sigmatch.
 # So we import that one instead.
-import compiler/options, compiler/commands, compiler/modules, compiler/sem,
-  compiler/passes, compiler/passaux, compiler/msgs, compiler/nimconf,
-  compiler/extccomp, compiler/condsyms, compiler/lists,
-  compiler/sigmatch, compiler/ast, compiler/scriptconfig,
-  compiler/idents, compiler/modulegraphs
+import compiler / [options, commands, modules, sem,
+  passes, passaux, msgs, nimconf,
+  extccomp, condsyms,
+  sigmatch, ast, scriptconfig,
+  idents, modulegraphs, compilerlog, vm]
 
 when defined(windows):
   import winlean
@@ -40,6 +40,7 @@ Options:
   --log                   enable verbose logging to nimsuggest.log file
   --v2                    use version 2 of the protocol; more features and
                           much faster
+  --refresh               perform automatic refreshes to keep the analysis precise
   --tester                implies --v2 and --stdin and outputs a line
                           '""" & DummyEof & """' for the tester
 
@@ -57,10 +58,11 @@ var
   gMode: Mode
   gEmitEof: bool # whether we write '!EOF!' dummy lines
   gLogging = false
+  gRefresh: bool
 
 const
   seps = {':', ';', ' ', '\t'}
-  Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline file.nim[;dirtyfile.nim]:line:col\n" &
+  Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline|known file.nim[;dirtyfile.nim]:line:col\n" &
          "type 'quit' to quit\n" &
          "type 'debug' to toggle debug mode on/off\n" &
          "type 'terse' to toggle terse mode on/off"
@@ -68,12 +70,6 @@ const
 type
   EUnexpectedCommand = object of Exception
 
-proc logStr(line: string) =
-  var f: File
-  if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
-    f.writeLine(line)
-    f.close()
-
 proc parseQuoted(cmd: string; outp: var string; start: int): int =
   var i = start
   i += skipWhitespace(cmd, i)
@@ -150,7 +146,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
   gTrackPos = newLineInfo(dirtyIdx, line, col)
   gErrorCounter = 0
   if suggestVersion < 2:
-    usageSym = nil
+    graph.usageSym = nil
   if not isKnownFile:
     graph.compileProject(cache)
   if suggestVersion == 2 and gIdeCmd in {ideUse, ideDus} and
@@ -163,7 +159,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
     if gIdeCmd != ideMod:
       graph.compileProject(cache, modIdx)
   if gIdeCmd in {ideUse, ideDus}:
-    let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: usageSym
+    let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: graph.usageSym
     if u != nil:
       listUsages(u)
     else:
@@ -180,7 +176,7 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode;
     dirtyfile = args[3].getStr(nil)
   execute(cmd, file, dirtyfile, int(line), int(column), graph, cache)
 
-proc returnEpc(socket: var Socket, uid: BiggestInt, s: SexpNode|string,
+proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
                return_symbol = "return") =
   let response = $convertSexp([newSSymbol(return_symbol), uid, s])
   socket.send(toHex(len(response), 6))
@@ -198,7 +194,7 @@ template sendEpc(results: typed, tdef, hook: untyped) =
   let res = sexp(results)
   if gLogging:
     logStr($res)
-  returnEPC(client, uid, res)
+  returnEpc(client, uid, res)
 
 template checkSanity(client, sizeHex, size, messageBuffer: typed) =
   if client.recv(sizeHex, 6) != 6:
@@ -208,6 +204,48 @@ template checkSanity(client, sizeHex, size, messageBuffer: typed) =
   if client.recv(messageBuffer, size) != size:
     raise newException(ValueError, "didn't get all the bytes")
 
+var
+  requests: Channel[string]
+  results: Channel[Suggest]
+
+proc toStdout() {.gcsafe.} =
+  while true:
+    let res = results.recv()
+    case res.section
+    of ideNone: break
+    of ideChk: echo res.doc
+    of ideKnown: echo res.quality == 1
+    else: echo res
+
+proc toSocket(stdoutSocket: Socket) {.gcsafe.} =
+  while true:
+    let res = results.recv()
+    case res.section
+    of ideNone: break
+    of ideChk: stdoutSocket.send(res.doc & "\c\L")
+    of ideKnown: stdoutSocket.send($(res.quality == 1) & "\c\L")
+    else: stdoutSocket.send($res & "\c\L")
+
+proc toEpc(client: Socket; uid: BiggestInt) {.gcsafe.} =
+  var list = newSList()
+  while true:
+    let res = results.recv()
+    case res.section
+    of ideNone: break
+    of ideChk:
+      list.add sexp(res.doc)
+    of ideKnown:
+      list.add sexp(res.quality == 1)
+    else:
+      list.add sexp(res)
+  returnEpc(client, uid, list)
+
+proc writelnToChannel(line: string) =
+  results.send(Suggest(section: ideChk, doc: line))
+
+proc sugResultHook(s: Suggest) =
+  results.send(s)
+
 template setVerbosity(level: typed) =
   gVerbosity = level
   gNotes = NotesVerbosity[gVerbosity]
@@ -217,16 +255,128 @@ proc connectToNextFreePort(server: Socket, host: string): Port =
   let (_, port) = server.getLocalAddr
   result = port
 
-proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) =
+type
+  ThreadParams = tuple[port: Port; address: string]
+
+proc replStdin(x: ThreadParams) {.thread.} =
+  if gEmitEof:
+    echo DummyEof
+    while true:
+      let line = readLine(stdin)
+      requests.send line
+      toStdout()
+      echo DummyEof
+      flushFile(stdout)
+  else:
+    echo Help
+    var line = ""
+    while readLineFromStdin("> ", line):
+      requests.send line
+      toStdout()
+      echo ""
+      flushFile(stdout)
+
+proc replTcp(x: ThreadParams) {.thread.} =
+  var server = newSocket()
+  server.bindAddr(x.port, x.address)
+  var inp = "".TaintedString
+  server.listen()
+  while true:
+    var stdoutSocket = newSocket()
+    accept(server, stdoutSocket)
+
+    stdoutSocket.readLine(inp)
+    requests.send inp
+    toSocket(stdoutSocket)
+    stdoutSocket.send("\c\L")
+    stdoutSocket.close()
+
+proc argsToStr(x: SexpNode): string =
+  if x.kind != SList: return x.getStr
+  doAssert x.kind == SList
+  doAssert x.len >= 4
+  let file = x[0].getStr
+  let line = x[1].getNum
+  let col = x[2].getNum
+  let dirty = x[3].getStr
+  result = x[0].getStr.escape
+  if dirty.len > 0:
+    result.add ';'
+    result.add dirty.escape
+  result.add ':'
+  result.add line
+  result.add ':'
+  result.add col
+
+proc replEpc(x: ThreadParams) {.thread.} =
+  var server = newSocket()
+  let port = connectToNextFreePort(server, "localhost")
+  server.listen()
+  echo port
+
+  var client = newSocket()
+  # Wait for connection
+  accept(server, client)
+  while true:
+    var
+      sizeHex = ""
+      size = 0
+      messageBuffer = ""
+    checkSanity(client, sizeHex, size, messageBuffer)
+    let
+      message = parseSexp($messageBuffer)
+      epcApi = message[0].getSymbol
+    case epcApi
+    of "call":
+      let
+        uid = message[1].getNum
+        args = message[3]
+
+      gIdeCmd = parseIdeCmd(message[2].getSymbol)
+      case gIdeCmd
+      of ideChk:
+        setVerbosity(1)
+        # Use full path because other emacs plugins depends it
+        gListFullPaths = true
+        incl(gGlobalOptions, optIdeDebug)
+      of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight:
+        setVerbosity(0)
+      else: discard
+      let cmd = $gIdeCmd & " " & args.argsToStr
+      if gLogging:
+        logStr "MSG CMD: " & cmd
+      requests.send(cmd)
+      toEpc(client, uid)
+    of "methods":
+      returnEpc(client, message[1].getNum, listEpc())
+    of "epc-error":
+      # an unhandled exception forces down the whole process anyway, so we
+      # use 'quit' here instead of 'raise'
+      quit("recieved epc error: " & $messageBuffer)
+    else:
+      let errMessage = case epcApi
+                       of "return", "return-error":
+                         "no return expected"
+                       else:
+                         "unexpected call: " & epcAPI
+      quit errMessage
+
+proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache) =
+  template sentinel() =
+    # send sentinel for the input reading thread:
+    results.send(Suggest(section: ideNone))
+
   template toggle(sw) =
     if sw in gGlobalOptions:
       excl(gGlobalOptions, sw)
     else:
       incl(gGlobalOptions, sw)
+    sentinel()
     return
 
   template err() =
     echo Help
+    sentinel()
     return
 
   var opc = ""
@@ -246,6 +396,7 @@ proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) =
   of "quit": quit()
   of "debug": toggle optIdeDebug
   of "terse": toggle optIdeTerse
+  of "known": gIdeCmd = ideKnown
   else: err()
   var dirtyfile = ""
   var orig = ""
@@ -259,25 +410,74 @@ proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) =
   i += skipWhile(cmd, seps, i)
   i += parseInt(cmd, col, i)
 
-  execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
+  if gIdeCmd == ideKnown:
+    results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(orig))))
+  else:
+    execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
+  sentinel()
+
+proc recompileFullProject(graph: ModuleGraph; cache: IdentCache) =
+  echo "recompiling full project"
+  resetSystemArtifacts()
+  vm.globalCtx = nil
+  graph.resetAllModules()
+  GC_fullcollect()
+  compileProject(graph, cache)
+  echo "recompiled!"
+
+proc mainThread(graph: ModuleGraph; cache: IdentCache) =
+  if gLogging:
+    for it in searchPaths:
+      logStr(it)
+
+  proc wrHook(line: string) {.closure.} =
+    if gMode == mepc:
+      if gLogging: logStr(line)
+    else:
+      writelnToChannel(line)
+
+  msgs.writelnHook = wrHook
+  suggestionResultHook = sugResultHook
+  graph.doStopCompile = proc (): bool = requests.peek() > 0
+  var idle = 0
+  while true:
+    let (hasData, req) = requests.tryRecv()
+    if hasData:
+      msgs.writelnHook = wrHook
+      suggestionResultHook = sugResultHook
+
+      execCmd(req, graph, cache)
+      idle = 0
+    else:
+      os.sleep 250
+      idle += 1
+    if idle == 20 and gRefresh:
+      # we use some nimsuggest activity to enable a lazy recompile:
+      gIdeCmd = ideChk
+      msgs.writelnHook = proc (s: string) = discard
+      suggestionResultHook = proc (s: Suggest) = discard
+      recompileFullProject(graph, cache)
+
+var
+  inputThread: Thread[ThreadParams]
 
-proc serveStdin(graph: ModuleGraph; cache: IdentCache) =
+proc serveStdin(graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
   if gEmitEof:
     echo DummyEof
     while true:
       let line = readLine(stdin)
-      parseCmdLine line, graph, cache
+      execCmd line, graph, cache
       echo DummyEof
       flushFile(stdout)
   else:
     echo Help
     var line = ""
     while readLineFromStdin("> ", line):
-      parseCmdLine line, graph, cache
+      execCmd line, graph, cache
       echo ""
       flushFile(stdout)
 
-proc serveTcp(graph: ModuleGraph; cache: IdentCache) =
+proc serveTcp(graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
   var server = newSocket()
   server.bindAddr(gPort, gAddress)
   var inp = "".TaintedString
@@ -291,12 +491,12 @@ proc serveTcp(graph: ModuleGraph; cache: IdentCache) =
     accept(server, stdoutSocket)
 
     stdoutSocket.readLine(inp)
-    parseCmdLine inp.string, graph, cache
+    execCmd inp.string, graph, cache
 
     stdoutSocket.send("\c\L")
     stdoutSocket.close()
-    
-proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) =
+
+proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
   var client = newSocket()
   # Wait for connection
   accept(server, client)
@@ -355,33 +555,49 @@ proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
   incl gGlobalOptions, optCaasEnabled
   isServing = true
   wantMainModule()
-  appendStr(searchPaths, options.libpath)
+  add(searchPaths, options.libpath)
   #if gProjectFull.len != 0:
     # current path is always looked first for modules
   #  prependStr(searchPaths, gProjectPath)
 
   # do not stop after the first error:
   msgs.gErrorMax = high(int)
+  # compile the project before showing any input so that we already
+  # can answer questions right away:
+  compileProject(graph, cache)
+
+  open(requests)
+  open(results)
 
   case gMode
-  of mstdin:
-    compileProject(graph, cache)
-    #modules.gFuzzyGraphChecking = false
-    serveStdin(graph, cache)
-  of mtcp:
-    # until somebody accepted the connection, produce no output (logging is too
-    # slow for big projects):
-    msgs.writelnHook = proc (msg: string) = discard
-    compileProject(graph, cache)
-    #modules.gFuzzyGraphChecking = false
-    serveTcp(graph, cache)
-  of mepc:
-    var server = newSocket()
-    let port = connectToNextFreePort(server, "localhost")
-    server.listen()
-    echo port
-    compileProject(graph, cache)
-    serveEpc(server, graph, cache)
+  of mstdin: createThread(inputThread, replStdin, (gPort, gAddress))
+  of mtcp: createThread(inputThread, replTcp, (gPort, gAddress))
+  of mepc: createThread(inputThread, replEpc, (gPort, gAddress))
+  mainThread(graph, cache)
+  joinThread(inputThread)
+  close(requests)
+  close(results)
+
+  when false:
+    case gMode
+    of mstdin:
+      compileProject(graph, cache)
+      #modules.gFuzzyGraphChecking = false
+      serveStdin(graph, cache)
+    of mtcp:
+      # until somebody accepted the connection, produce no output (logging is too
+      # slow for big projects):
+      msgs.writelnHook = proc (msg: string) = discard
+      compileProject(graph, cache)
+      #modules.gFuzzyGraphChecking = false
+      serveTcp(graph, cache)
+    of mepc:
+      var server = newSocket()
+      let port = connectToNextFreePort(server, "localhost")
+      server.listen()
+      echo port
+      compileProject(graph, cache)
+      serveEpc(server, graph, cache)
 
 proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
   var p = parseopt.initOptParser(cmd)
@@ -401,16 +617,14 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
       of "epc":
         gMode = mepc
         gVerbosity = 0          # Port number gotta be first.
-      of "debug":
-        incl(gGlobalOptions, optIdeDebug)
-      of "v2":
-        suggestVersion = 2
+      of "debug": incl(gGlobalOptions, optIdeDebug)
+      of "v2": suggestVersion = 2
       of "tester":
         suggestVersion = 2
         gMode = mstdin
         gEmitEof = true
-      of "log":
-        gLogging = true
+      of "log": gLogging = true
+      of "refresh": gRefresh = true
       else: processSwitch(pass, p)
     of cmdArgument:
       options.gProjectName = unixToNativePath(p.key)
diff --git a/tools/nimsuggest/nimsuggest.nim.cfg b/tools/nimsuggest/nimsuggest.nim.cfg
index 949bd18e8..2e14a4dd3 100644
--- a/tools/nimsuggest/nimsuggest.nim.cfg
+++ b/tools/nimsuggest/nimsuggest.nim.cfg
@@ -8,9 +8,18 @@ path:"$lib/packages/docutils"
 
 define:useStdoutAsStdmsg
 define:nimsuggest
+# die when nimsuggest uses more than 4GB:
+@if cpu32:
+  define:"nimMaxHeap=2000"
+@else:
+  define:"nimMaxHeap=4000"
+@end
 
 #cs:partial
 #define:useNodeIds
 #define:booting
 #define:noDocgen
 --path:"$nim"
+--threads:on
+--noNimblePath
+--path:"../../compiler"
diff --git a/tools/nimsuggest/sexp.nim b/tools/nimsuggest/sexp.nim
index cf08111d7..61bba10bc 100644
--- a/tools/nimsuggest/sexp.nim
+++ b/tools/nimsuggest/sexp.nim
@@ -561,16 +561,16 @@ proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true,
     result.add(escapeJson(node.str))
   of SInt:
     if lstArr: result.indent(currIndent)
-    result.add($node.num)
+    result.add(node.num)
   of SFloat:
     if lstArr: result.indent(currIndent)
-    result.add($node.fnum)
+    result.add(node.fnum)
   of SNil:
     if lstArr: result.indent(currIndent)
     result.add("nil")
   of SSymbol:
     if lstArr: result.indent(currIndent)
-    result.add($node.symbol)
+    result.add(node.symbol)
   of SList:
     if lstArr: result.indent(currIndent)
     if len(node.elems) != 0:
diff --git a/tools/nimsuggest/tester.nim b/tools/nimsuggest/tester.nim
index 156d3ddb9..c4542e089 100644
--- a/tools/nimsuggest/tester.nim
+++ b/tools/nimsuggest/tester.nim
@@ -3,7 +3,7 @@
 # before 'nimsuggest' is invoked to ensure this token doesn't make a
 # crucial difference for Nim's parser.
 
-import os, osproc, strutils, streams, re
+import os, osproc, strutils, streams, re, sexp, net
 
 type
   Test = object
@@ -17,7 +17,7 @@ const
 
 template tpath(): untyped = getAppDir() / "tests"
 
-proc parseTest(filename: string): Test =
+proc parseTest(filename: string; epcMode=false): Test =
   const cursorMarker = "#[!]#"
   let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
   result.dest = getTempDir() / extractFilename(filename)
@@ -31,7 +31,10 @@ proc parseTest(filename: string): Test =
   for x in lines(filename):
     let marker = x.find(cursorMarker)+1
     if marker > 0:
-      markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker
+      if epcMode:
+        markers.add "(\"" & filename & "\" " & $i & " " & $marker & " \"" & result.dest & "\")"
+      else:
+        markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker
       tmp.writeLine x.replace(cursorMarker, "")
     else:
       tmp.writeLine x
@@ -133,6 +136,126 @@ proc smartCompare(pattern, x: string): bool =
   if pattern.contains('*'):
     result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {}))
 
+proc sendEpcStr(socket: Socket; cmd: string) =
+  let s = cmd.find(' ')
+  doAssert s > 0
+  var args = cmd.substr(s+1)
+  if not args.startsWith("("): args = escapeJson(args)
+  let c = "(call 567 " & cmd.substr(0, s) & args & ")"
+  socket.send toHex(c.len, 6)
+  socket.send c
+
+proc recvEpc(socket: Socket): string =
+  var L = newStringOfCap(6)
+  if socket.recv(L, 6) != 6:
+    raise newException(ValueError, "recv A failed")
+  let x = parseHexInt(L)
+  result = newString(x)
+  if socket.recv(result, x) != x:
+    raise newException(ValueError, "recv B failed")
+
+proc sexpToAnswer(s: SexpNode): string =
+  result = ""
+  doAssert s.kind == SList
+  doAssert s.len >= 3
+  let m = s[2]
+  if m.kind != SList:
+    echo s
+  doAssert m.kind == SList
+  for a in m:
+    doAssert a.kind == SList
+    var first = true
+    #s.section,
+    #s.symkind,
+    #s.qualifiedPath.map(newSString),
+    #s.filePath,
+    #s.forth,
+    #s.line,
+    #s.column,
+    #s.doc
+    if a.len >= 8:
+      let section = a[0].getStr
+      let symk = a[1].getStr
+      let qp = a[2]
+      let file = a[3].getStr
+      let typ = a[4].getStr
+      let line = a[5].getNum
+      let col = a[6].getNum
+      let doc = a[7].getStr.escape
+      result.add section
+      result.add '\t'
+      result.add symk
+      result.add '\t'
+      var i = 0
+      for aa in qp:
+        if i > 0: result.add '.'
+        result.add aa.getStr
+        inc i
+      result.add '\t'
+      result.add typ
+      result.add '\t'
+      result.add file
+      result.add '\t'
+      result.add line
+      result.add '\t'
+      result.add col
+      result.add '\t'
+      result.add doc
+      result.add '\t'
+      # for now Nim EPC does not return the quality
+      result.add "100"
+    result.add '\L'
+
+proc doReport(filename, answer, resp: string; report: var string) =
+  if resp != answer and not smartCompare(resp, answer):
+    report.add "\nTest failed: " & filename
+    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)
+        report.add "\n  But got:   " & answer.substr(i)
+        hasDiff = true
+        break
+    if not hasDiff:
+      report.add "\n  Expected:  " & resp
+      report.add "\n  But got:   " & answer
+
+proc runEpcTest(filename: string): int =
+  let s = parseTest(filename, true)
+  for cmd in s.startup:
+    if not runCmd(cmd, s.dest):
+      quit "invalid command: " & cmd
+  let epccmd = s.cmd.replace("--tester", "--epc --v2")
+  let cl = parseCmdLine(epccmd)
+  var p = startProcess(command=cl[0], args=cl[1 .. ^1],
+                       options={poStdErrToStdOut, poUsePath,
+                       poInteractive, poDemon})
+  let outp = p.outputStream
+  let inp = p.inputStream
+  var report = ""
+  var a = newStringOfCap(120)
+  try:
+    # read the port number:
+    if outp.readLine(a):
+      let port = parseInt(a)
+      var socket = newSocket()
+      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)
+            doReport(filename, answer, resp, report)
+    else:
+      raise newException(ValueError, "cannot read port number")
+  finally:
+    close(p)
+  if report.len > 0:
+    echo "==== EPC ========================================"
+    echo report
+  result = report.len
+
 proc runTest(filename: string): int =
   let s = parseTest filename
   for cmd in s.startup:
@@ -159,31 +282,28 @@ proc runTest(filename: string): int =
           if a == DummyEof: break
           answer.add a
           answer.add '\L'
-        if resp != answer and not smartCompare(resp, answer):
-          report.add "\nTest failed: " & filename
-          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)
-              report.add "\n  But got:   " & answer.substr(i)
-              hasDiff = true
-              break
-          if not hasDiff:
-            report.add "\n  Expected:  " & resp
-            report.add "\n  But got:   " & answer
+        doReport(filename, answer, resp, report)
   finally:
     inp.writeLine("quit")
     inp.flush()
     close(p)
   if report.len > 0:
+    echo "==== STDIN ======================================"
     echo report
   result = report.len
 
 proc main() =
   var failures = 0
-  for x in walkFiles(getAppDir() / "tests/t*.nim"):
-    echo "Test ", x
-    failures += runTest(expandFilename(x))
+  when false:
+    let x = getAppDir() / "tests/twithin_macro.nim"
+    let xx = expandFilename x
+    failures += runEpcTest(xx)
+  else:
+    for x in walkFiles(getAppDir() / "tests/t*.nim"):
+      echo "Test ", x
+      let xx = expandFilename x
+      failures += runTest(xx)
+      failures += runEpcTest(xx)
   if failures > 0:
     quit 1
 
diff --git a/tools/nimsuggest/tests/twithin_macro.nim b/tools/nimsuggest/tests/twithin_macro.nim
index d67984707..7392dd605 100644
--- a/tools/nimsuggest/tests/twithin_macro.nim
+++ b/tools/nimsuggest/tests/twithin_macro.nim
@@ -206,8 +206,8 @@ $nimsuggest --tester $file
 >sug $1
 sug;;skField;;name;;string;;$file;;166;;6;;"";;100
 sug;;skField;;age;;int;;$file;;167;;6;;"";;100
-sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int{.noSideEffect, gcsafe, locks: 0.};;$file;;169;;9;;"";;100
+sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100
 sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"Iterates over the children of the NimNode ``n``.";;100
-sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string{.noSideEffect, gcsafe, locks: 0.};;$file;;168;;9;;"";;100
-sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string{.noSideEffect, gcsafe, locks: 0.};;$file;;184;;9;;"";;100*
+sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100
+sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100*
 """