Skip to content

Commit

Permalink
+ added nimsuggest support for exception inlay hints (nim-lang#23202)
Browse files Browse the repository at this point in the history
This adds nimsuggest support for displaying inlay hints for exceptions.
An inlay hint is displayed around function calls, that can raise an
exception, which isn't handled in the current subroutine (in other
words, exceptions that can propagate back to the caller). On mouse hover
on top of the hint, a list of exceptions that could propagate is shown.

The changes, required to support this are already commited to
nimlangserver and the VS code extension. The extension and the server
allow configuration for whether these new exception hints are enabled
(they can be enabled or disabled independently from the type hints), as
well as the inlay strings that are inserted before and after the name of
the function, around the function call. Potentially, one of these
strings can be empty, for example, the user can choose to add an inlay
hint only before the name of the function, or only after the name of the
function.
  • Loading branch information
nickysn committed Mar 15, 2024
1 parent c2c0077 commit 899ba01
Show file tree
Hide file tree
Showing 7 changed files with 480 additions and 50 deletions.
25 changes: 9 additions & 16 deletions compiler/modulegraphs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]


Expand Down Expand Up @@ -55,11 +55,6 @@ type
concreteTypes*: seq[FullId]
inst*: PInstantiation

SymInfoPair* = object
sym*: PSym
info*: TLineInfo
isDecl*: bool

PipelinePass* = enum
NonePass
SemPass
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions compiler/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -298,6 +299,7 @@ type
SuggestInlayHintKind* = enum
sihkType = "Type",
sihkParameter = "Parameter"
sihkException = "Exception"

SuggestInlayHint* = ref object
kind*: SuggestInlayHintKind
Expand Down
63 changes: 60 additions & 3 deletions compiler/sempass2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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:
Expand Down
44 changes: 41 additions & 3 deletions compiler/suggest.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 899ba01

Please sign in to comment.