summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/modulegraphs.nim25
-rw-r--r--compiler/options.nim2
-rw-r--r--compiler/sempass2.nim63
-rw-r--r--compiler/suggest.nim44
-rw-r--r--compiler/suggestsymdb.nim212
5 files changed, 324 insertions, 22 deletions
diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim
index 9885a9b45..09eaf5f6d 100644
--- a/compiler/modulegraphs.nim
+++ b/compiler/modulegraphs.nim
@@ -11,9 +11,9 @@
 ## represents a complete Nim project. Single modules can either be kept in RAM
 ## or stored in a rod-file.
 
-import std/[intsets, tables, hashes, strtabs]
+import std/[intsets, tables, hashes, strtabs, algorithm]
 import ../dist/checksums/src/checksums/md5
-import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages
+import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages, suggestsymdb
 import ic / [packed_ast, ic]
 
 
@@ -55,11 +55,6 @@ type
     concreteTypes*: seq[FullId]
     inst*: PInstantiation
 
-  SymInfoPair* = object
-    sym*: PSym
-    info*: TLineInfo
-    isDecl*: bool
-
   PipelinePass* = enum
     NonePass
     SemPass
@@ -108,7 +103,7 @@ type
     doStopCompile*: proc(): bool {.closure.}
     usageSym*: PSym # for nimsuggest
     owners*: seq[PSym]
-    suggestSymbols*: Table[FileIndex, seq[SymInfoPair]]
+    suggestSymbols*: SuggestSymbolDatabase
     suggestErrors*: Table[FileIndex, seq[Suggest]]
     methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization!
     bucketTable*: CountTable[ItemId]
@@ -506,7 +501,7 @@ proc initModuleGraphFields(result: ModuleGraph) =
   result.importStack = @[]
   result.inclToMod = initTable[FileIndex, FileIndex]()
   result.owners = @[]
-  result.suggestSymbols = initTable[FileIndex, seq[SymInfoPair]]()
+  result.suggestSymbols = initTable[FileIndex, SuggestFileSymbolDatabase]()
   result.suggestErrors = initTable[FileIndex, seq[Suggest]]()
   result.methods = @[]
   result.compilerprocs = initStrTable()
@@ -712,16 +707,14 @@ func belongsToStdlib*(graph: ModuleGraph, sym: PSym): bool =
   ## Check if symbol belongs to the 'stdlib' package.
   sym.getPackageSymbol.getPackageId == graph.systemModule.getPackageId
 
-proc `==`*(a, b: SymInfoPair): bool =
-  result = a.sym == b.sym and a.info.exactEquals(b.info)
-
-proc fileSymbols*(graph: ModuleGraph, fileIdx: FileIndex): seq[SymInfoPair] =
-  result = graph.suggestSymbols.getOrDefault(fileIdx, @[])
+proc fileSymbols*(graph: ModuleGraph, fileIdx: FileIndex): SuggestFileSymbolDatabase =
+  result = graph.suggestSymbols.getOrDefault(fileIdx, newSuggestFileSymbolDatabase(fileIdx, optIdeExceptionInlayHints in graph.config.globalOptions))
+  doAssert(result.fileIndex == fileIdx)
 
 iterator suggestSymbolsIter*(g: ModuleGraph): SymInfoPair =
   for xs in g.suggestSymbols.values:
-    for x in xs:
-      yield x
+    for i in xs.lineInfo.low..xs.lineInfo.high:
+      yield xs.getSymInfoPair(i)
 
 iterator suggestErrorsIter*(g: ModuleGraph): Suggest =
   for xs in g.suggestErrors.values:
diff --git a/compiler/options.nim b/compiler/options.nim
index 3be1758eb..356aa6cc8 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -86,6 +86,7 @@ type                          # please make sure we have under 32 options
                               # also: generate header file
     optIdeDebug               # idetools: debug mode
     optIdeTerse               # idetools: use terse descriptions
+    optIdeExceptionInlayHints
     optExcessiveStackTrace    # fully qualified module filenames
     optShowAllMismatches      # show all overloading resolution candidates
     optWholeProject           # for 'doc': output any dependency
@@ -298,6 +299,7 @@ type
   SuggestInlayHintKind* = enum
     sihkType = "Type",
     sihkParameter = "Parameter"
+    sihkException = "Exception"
 
   SuggestInlayHint* = ref object
     kind*: SuggestInlayHintKind
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 7ddd371cd..64f6e42e2 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -11,9 +11,9 @@ import
   ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
   wordrecg, options, guards, lineinfos, semfold, semdata,
   modulegraphs, varpartitions, typeallowed, nilcheck, errorhandling,
-  semstrictfuncs
+  semstrictfuncs, suggestsymdb
 
-import std/[tables, intsets, strutils]
+import std/[tables, intsets, strutils, sequtils]
 
 when defined(nimPreviewSlimSystem):
   import std/assertions
@@ -66,8 +66,12 @@ discard """
 """
 
 type
+  CaughtExceptionsStack = object
+    nodes: seq[seq[PType]]
   TEffects = object
     exc: PNode  # stack of exceptions
+    when defined(nimsuggest):
+      caughtExceptions: CaughtExceptionsStack
     tags: PNode # list of tags
     forbids: PNode # list of tags
     bottom, inTryStmt, inExceptOrFinallyStmt, leftPartOfAsgn, inIfStmt, currentBlock: int
@@ -411,7 +415,7 @@ proc throws(tracked, n, orig: PNode) =
     else:
       tracked.add n
 
-proc getEbase(g: ModuleGraph; info: TLineInfo): PType =
+proc getEbase*(g: ModuleGraph; info: TLineInfo): PType =
   result = g.sysTypeFromName(info, "Exception")
 
 proc excType(g: ModuleGraph; n: PNode): PType =
@@ -492,6 +496,18 @@ proc catchesAll(tracked: PEffects) =
   if tracked.exc.len > 0:
     setLen(tracked.exc.sons, tracked.bottom)
 
+proc push(s: var CaughtExceptionsStack) =
+  s.nodes.add(@[])
+
+proc pop(s: var CaughtExceptionsStack) =
+  s.nodes.del(high(s.nodes))
+
+proc addCatch(s: var CaughtExceptionsStack, e: PType) =
+  s.nodes[high(s.nodes)].add(e)
+
+proc addCatchAll(s: var CaughtExceptionsStack) =
+  s.nodes[high(s.nodes)].add(nil)
+
 proc track(tracked: PEffects, n: PNode)
 proc trackTryStmt(tracked: PEffects, n: PNode) =
   let oldBottom = tracked.bottom
@@ -500,12 +516,33 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
   let oldState = tracked.init.len
   var inter: TIntersection = @[]
 
+  when defined(nimsuggest):
+    tracked.caughtExceptions.push
+    for i in 1..<n.len:
+      let b = n[i]
+      if b.kind == nkExceptBranch:
+        if b.len == 1:
+          tracked.caughtExceptions.addCatchAll
+        else:
+          for j in 0..<b.len - 1:
+            if b[j].isInfixAs():
+              assert(b[j][1].kind == nkType)
+              tracked.caughtExceptions.addCatch(b[j][1].typ)
+            else:
+              assert(b[j].kind == nkType)
+              tracked.caughtExceptions.addCatch(b[j].typ)
+      else:
+        assert b.kind == nkFinally
+
   inc tracked.inTryStmt
   track(tracked, n[0])
   dec tracked.inTryStmt
   for i in oldState..<tracked.init.len:
     addToIntersection(inter, tracked.init[i], bsNone)
 
+  when defined(nimsuggest):
+    tracked.caughtExceptions.pop
+
   var branches = 1
   var hasFinally = false
   inc tracked.inExceptOrFinallyStmt
@@ -917,6 +954,19 @@ proc checkForSink(tracked: PEffects; n: PNode) =
   if tracked.inIfStmt == 0 and optSinkInference in tracked.config.options:
     checkForSink(tracked.config, tracked.c.idgen, tracked.owner, n)
 
+proc markCaughtExceptions(tracked: PEffects; g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym) =
+  when defined(nimsuggest):
+    proc internalMarkCaughtExceptions(tracked: PEffects; q: var SuggestFileSymbolDatabase; info: TLineInfo) =
+      var si = q.findSymInfoIndex(info)
+      if si != -1:
+        q.caughtExceptionsSet[si] = true
+        for w1 in tracked.caughtExceptions.nodes:
+          for w2 in w1:
+            q.caughtExceptions[si].add(w2)
+
+    if optIdeExceptionInlayHints in tracked.config.globalOptions:
+      internalMarkCaughtExceptions(tracked, g.suggestSymbols.mgetOrPut(info.fileIndex, newSuggestFileSymbolDatabase(info.fileIndex, true)), info)
+
 proc trackCall(tracked: PEffects; n: PNode) =
   template gcsafeAndSideeffectCheck() =
     if notGcSafe(op) and not importedFromC(a):
@@ -937,6 +987,13 @@ proc trackCall(tracked: PEffects; n: PNode) =
     if tracked.owner.kind != skMacro and n.typ.skipTypes(abstractVar).kind != tyOpenArray:
       createTypeBoundOps(tracked, n.typ, n.info)
 
+  when defined(nimsuggest):
+    var actualLoc = a.info
+    if n.kind == nkHiddenCallConv:
+      actualLoc = n.info
+    if a.kind == nkSym:
+      markCaughtExceptions(tracked, tracked.graph, actualLoc, a.sym, tracked.graph.usageSym)
+
   let notConstExpr = getConstExpr(tracked.ownerModule, n, tracked.c.idgen, tracked.graph) == nil
   if notConstExpr:
     if a.kind == nkCast and a[1].typ.kind == tyProc:
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index 21986b4d9..616ecd466 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -32,7 +32,7 @@
 
 # included from sigmatch.nim
 
-import prefixmatches
+import prefixmatches, suggestsymdb
 from wordrecg import wDeprecated, wError, wAddr, wYield
 
 import std/[algorithm, sets, parseutils, tables]
@@ -114,6 +114,10 @@ proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo; skip
     result = skipUntil(line, '`', column)
     if cmpIgnoreStyle(line[column..column + result - 1], ident) != 0:
       result = 0
+  elif column >= 0 and line[column] == '`' and isOpeningBacktick(column):
+    result = skipUntil(line, '`', column + 1) + 2
+    if cmpIgnoreStyle(line[column + 1..column + result - 2], ident) != 0:
+      result = 0
   elif ident[0] in linter.Letters and ident[^1] != '=':
     result = identLen(line, column)
     if cmpIgnoreStyle(line[column..column + result - 1], ident[0..min(result-1,len(ident)-1)]) != 0:
@@ -265,7 +269,7 @@ proc `$`*(suggest: Suggest): string =
       result.add(sep)
       result.add($suggest.endCol)
 
-proc suggestToSuggestInlayHint*(sug: Suggest): SuggestInlayHint =
+proc suggestToSuggestInlayTypeHint*(sug: Suggest): SuggestInlayHint =
   SuggestInlayHint(
     kind: sihkType,
     line: sug.line,
@@ -277,6 +281,30 @@ proc suggestToSuggestInlayHint*(sug: Suggest): SuggestInlayHint =
     tooltip: ""
   )
 
+proc suggestToSuggestInlayExceptionHintLeft*(sug: Suggest, propagatedExceptions: seq[PType]): SuggestInlayHint =
+  SuggestInlayHint(
+    kind: sihkException,
+    line: sug.line,
+    column: sug.column,
+    label: "try ",
+    paddingLeft: false,
+    paddingRight: false,
+    allowInsert: false,
+    tooltip: "propagated exceptions: " & $propagatedExceptions
+  )
+
+proc suggestToSuggestInlayExceptionHintRight*(sug: Suggest, propagatedExceptions: seq[PType]): SuggestInlayHint =
+  SuggestInlayHint(
+    kind: sihkException,
+    line: sug.line,
+    column: sug.column + sug.tokenLen,
+    label: "!",
+    paddingLeft: false,
+    paddingRight: false,
+    allowInsert: false,
+    tooltip: "propagated exceptions: " & $propagatedExceptions
+  )
+
 proc suggestResult*(conf: ConfigRef; s: Suggest) =
   if not isNil(conf.suggestionResultHook):
     conf.suggestionResultHook(s)
@@ -534,6 +562,16 @@ proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool =
   else:
     result = false
 
+proc isTracked*(current, trackPos: TinyLineInfo, tokenLen: int): bool =
+  if current.line==trackPos.line:
+    let col = trackPos.col
+    if col >= current.col and col <= current.col+tokenLen-1:
+      result = true
+    else:
+      result = false
+  else:
+    result = false
+
 when defined(nimsuggest):
   # Since TLineInfo defined a == operator that doesn't include the column,
   # we map TLineInfo to a unique int here for this lookup table:
@@ -584,7 +622,7 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i
   ## misnamed: should be 'symDeclared'
   let conf = g.config
   when defined(nimsuggest):
-    g.suggestSymbols.mgetOrPut(info.fileIndex, @[]).add SymInfoPair(sym: s, info: info, isDecl: isDecl)
+    g.suggestSymbols.add SymInfoPair(sym: s, info: info, isDecl: isDecl), optIdeExceptionInlayHints in g.config.globalOptions
 
     if conf.suggestVersion == 0:
       if s.allUsages.len == 0:
diff --git a/compiler/suggestsymdb.nim b/compiler/suggestsymdb.nim
new file mode 100644
index 000000000..e1e67afbe
--- /dev/null
+++ b/compiler/suggestsymdb.nim
@@ -0,0 +1,212 @@
+import std/[intsets, tables, algorithm, assertions]
+import ast, lineinfos, msgs
+
+type
+  PackedBoolArray* = object
+    s: IntSet
+    len: int
+
+  TinyLineInfo* = object
+    line*: uint16
+    col*: int16
+
+  SymInfoPair* = object
+    sym*: PSym
+    info*: TLineInfo
+    caughtExceptions*: seq[PType]
+    caughtExceptionsSet*: bool
+    isDecl*: bool
+
+  SuggestFileSymbolDatabase* = object
+    lineInfo*: seq[TinyLineInfo]
+    sym*: seq[PSym]
+    caughtExceptions*: seq[seq[PType]]
+    caughtExceptionsSet*: PackedBoolArray
+    isDecl*: PackedBoolArray
+    fileIndex*: FileIndex
+    trackCaughtExceptions*: bool
+    isSorted*: bool
+
+  SuggestSymbolDatabase* = Table[FileIndex, SuggestFileSymbolDatabase]
+
+
+func newPackedBoolArray*(): PackedBoolArray =
+  PackedBoolArray(
+    s: initIntSet(),
+    len: 0
+  )
+
+func low*(s: PackedBoolArray): int =
+  0
+
+func high*(s: PackedBoolArray): int =
+  s.len - 1
+
+func `[]`*(s: PackedBoolArray; idx: int): bool =
+  s.s.contains(idx)
+
+proc `[]=`*(s: var PackedBoolArray; idx: int; v: bool) =
+  if v:
+    s.s.incl(idx)
+  else:
+    s.s.excl(idx)
+
+proc add*(s: var PackedBoolArray; v: bool) =
+  inc(s.len)
+  if v:
+    s.s.incl(s.len - 1)
+
+proc reverse*(s: var PackedBoolArray) =
+  var
+    reversedSet = initIntSet()
+  for i in 0..s.high:
+    if s.s.contains(i):
+      reversedSet.incl(s.high - i)
+  s.s = reversedSet
+
+proc getSymInfoPair*(s: SuggestFileSymbolDatabase; idx: int): SymInfoPair =
+  SymInfoPair(
+    sym: s.sym[idx],
+    info: TLineInfo(
+      line: s.lineInfo[idx].line,
+      col: s.lineInfo[idx].col,
+      fileIndex: s.fileIndex
+    ),
+    caughtExceptions:
+      if s.trackCaughtExceptions:
+        s.caughtExceptions[idx]
+      else:
+        @[],
+    caughtExceptionsSet:
+      if s.trackCaughtExceptions:
+        s.caughtExceptionsSet[idx]
+      else:
+        false,
+    isDecl: s.isDecl[idx]
+  )
+
+proc reverse*(s: var SuggestFileSymbolDatabase) =
+  s.lineInfo.reverse()
+  s.sym.reverse()
+  s.caughtExceptions.reverse()
+  s.caughtExceptionsSet.reverse()
+  s.isDecl.reverse()
+
+proc newSuggestFileSymbolDatabase*(aFileIndex: FileIndex; aTrackCaughtExceptions: bool): SuggestFileSymbolDatabase =
+  SuggestFileSymbolDatabase(
+    lineInfo: @[],
+    sym: @[],
+    caughtExceptions: @[],
+    caughtExceptionsSet: newPackedBoolArray(),
+    isDecl: newPackedBoolArray(),
+    fileIndex: aFileIndex,
+    trackCaughtExceptions: aTrackCaughtExceptions,
+    isSorted: true
+  )
+
+proc exactEquals*(a, b: TinyLineInfo): bool =
+  result = a.line == b.line and a.col == b.col
+
+proc `==`*(a, b: SymInfoPair): bool =
+  result = a.sym == b.sym and a.info.exactEquals(b.info)
+
+func cmp*(a: TinyLineInfo; b: TinyLineInfo): int =
+  result = cmp(a.line, b.line)
+  if result == 0:
+    result = cmp(a.col, b.col)
+
+func compare*(s: var SuggestFileSymbolDatabase; i, j: int): int =
+  result = cmp(s.lineInfo[i], s.lineInfo[j])
+  if result == 0:
+    result = cmp(s.isDecl[i], s.isDecl[j])
+
+proc exchange(s: var SuggestFileSymbolDatabase; i, j: int) =
+  if i == j:
+    return
+  var tmp1 = s.lineInfo[i]
+  s.lineInfo[i] = s.lineInfo[j]
+  s.lineInfo[j] = tmp1
+  if s.trackCaughtExceptions:
+    var tmp2 = s.caughtExceptions[i]
+    s.caughtExceptions[i] = s.caughtExceptions[j]
+    s.caughtExceptions[j] = tmp2
+    var tmp3 = s.caughtExceptionsSet[i]
+    s.caughtExceptionsSet[i] = s.caughtExceptionsSet[j]
+    s.caughtExceptionsSet[j] = tmp3
+  var tmp4 = s.isDecl[i]
+  s.isDecl[i] = s.isDecl[j]
+  s.isDecl[j] = tmp4
+  var tmp5 = s.sym[i]
+  s.sym[i] = s.sym[j]
+  s.sym[j] = tmp5
+
+proc quickSort(s: var SuggestFileSymbolDatabase; ll, rr: int) =
+  var
+    i, j, pivotIdx: int
+    l = ll
+    r = rr
+  while true:
+    i = l
+    j = r
+    pivotIdx = l + ((r - l) shr 1)
+    while true:
+      while (i < pivotIdx) and (s.compare(pivotIdx, i) > 0):
+        inc i
+      while (j > pivotIdx) and (s.compare(pivotIdx, j) < 0):
+        dec j
+      if i < j:
+        s.exchange(i, j)
+        if pivotIdx == i:
+          pivotIdx = j
+          inc i
+        elif pivotIdx == j:
+          pivotIdx = i
+          dec j
+        else:
+          inc i
+          dec j
+      else:
+        break
+    if (pivotIdx - l) < (r - pivotIdx):
+      if (l + 1) < pivotIdx:
+        s.quickSort(l, pivotIdx - 1)
+      l = pivotIdx + 1
+    else:
+      if (pivotIdx + 1) < r:
+        s.quickSort(pivotIdx + 1, r)
+      if (l + 1) < pivotIdx:
+        r = pivotIdx - 1
+      else:
+        break
+    if l >= r:
+      break
+
+proc sort*(s: var SuggestFileSymbolDatabase) =
+  s.quickSort(s.lineInfo.low, s.lineInfo.high)
+  s.isSorted = true
+
+proc add*(s: var SuggestFileSymbolDatabase; v: SymInfoPair) =
+  doAssert(v.info.fileIndex == s.fileIndex)
+  s.lineInfo.add(TinyLineInfo(
+    line: v.info.line,
+    col: v.info.col
+  ))
+  s.sym.add(v.sym)
+  s.isDecl.add(v.isDecl)
+  if s.trackCaughtExceptions:
+    s.caughtExceptions.add(v.caughtExceptions)
+    s.caughtExceptionsSet.add(v.caughtExceptionsSet)
+  s.isSorted = false
+
+proc add*(s: var SuggestSymbolDatabase; v: SymInfoPair; trackCaughtExceptions: bool) =
+  s.mgetOrPut(v.info.fileIndex, newSuggestFileSymbolDatabase(v.info.fileIndex, trackCaughtExceptions)).add(v)
+
+proc findSymInfoIndex*(s: var SuggestFileSymbolDatabase; li: TLineInfo): int =
+  doAssert(li.fileIndex == s.fileIndex)
+  if not s.isSorted:
+    s.sort()
+  var q = TinyLineInfo(
+    line: li.line,
+    col: li.col
+  )
+  result = binarySearch(s.lineInfo, q, cmp)