NimSuggest: Fix for the inlay exception hints with generic procs (#23610)

Based on the fix, started by SirOlaf in #23414

---------

Co-authored-by: SirOlaf <>
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
(cherry picked from commit 478773ffb1)
This commit is contained in:
Nikolay Nikolov
2025-07-18 09:44:36 +03:00
committed by narimiran
parent 27feeea129
commit e9c5b4f494
8 changed files with 126 additions and 76 deletions

View File

@@ -456,10 +456,10 @@ template getPContext(): untyped =
else: c.c
when defined(nimsuggest):
template onUse*(info: TLineInfo; s: PSym) = discard
template onUse*(info: TLineInfo; s: PSym; isGenericInstance = false) = discard
template onDefResolveForward*(info: TLineInfo; s: PSym) = discard
else:
template onUse*(info: TLineInfo; s: PSym) = discard
template onUse*(info: TLineInfo; s: PSym; isGenericInstance = false) = discard
template onDef*(info: TLineInfo; s: PSym) = discard
template onDefResolveForward*(info: TLineInfo; s: PSym) = discard

View File

@@ -836,9 +836,12 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
assert x.state == csMatch
var finalCallee = x.calleeSym
let info = getCallLineInfo(n)
markUsed(c, info, finalCallee)
onUse(info, finalCallee)
markUsed(c, info, finalCallee, isGenericInstance = false)
onUse(info, finalCallee, isGenericInstance = false)
assert finalCallee.ast != nil
if x.matchedErrorType:
markUsed(c, info, finalCallee, isGenericInstance = true)
onUse(info, finalCallee, isGenericInstance = true)
if x.matchedErrorType:
result = x.call
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
@@ -874,6 +877,8 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
x.call.add tn
else:
internalAssert c.config, false
markUsed(c, info, finalCallee, isGenericInstance = true)
onUse(info, finalCallee, isGenericInstance = true)
result = x.call
instGenericConvertersSons(c, result, x)
@@ -942,8 +947,10 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym, errors: var CandidateErr
var newInst = generateInstance(c, s, m.bindings, n.info)
newInst.typ.flags.excl tfUnresolved
let info = getCallLineInfo(n)
markUsed(c, info, s)
onUse(info, s)
markUsed(c, info, s, isGenericInstance = false)
onUse(info, s, isGenericInstance = false)
markUsed(c, info, newInst, isGenericInstance = true)
onUse(info, newInst, isGenericInstance = true)
result = newSymNode(newInst, info)
proc setGenericParams(c: PContext, n, expectedParams: PNode) =

View File

@@ -970,7 +970,7 @@ proc checkForSink(tracked: PEffects; n: PNode) =
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)
var si = q.findSymInfoIndex(info, true)
if si != -1:
q.caughtExceptionsSet[si] = true
for w1 in tracked.caughtExceptions.nodes:

View File

@@ -104,7 +104,7 @@ const
isNilConversion = isConvertible # maybe 'isIntConv' fits better?
maxInheritancePenalty = high(int) div 2
proc markUsed*(c: PContext; info: TLineInfo, s: PSym; checkStyle = true)
proc markUsed*(c: PContext; info: TLineInfo, s: PSym; checkStyle = true; isGenericInstance = false)
proc markOwnerModuleAsUsed*(c: PContext; s: PSym)
proc initCandidateAux(ctx: PContext,

View File

@@ -624,41 +624,43 @@ proc ensureIdx[T](x: var T, y: int) =
proc ensureSeq[T](x: var seq[T]) =
if x == nil: newSeq(x, 0)
proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} =
proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true; isGenericInstance=false) {.inline.} =
## misnamed: should be 'symDeclared'
let conf = g.config
when defined(nimsuggest):
g.suggestSymbols.add SymInfoPair(sym: s, info: info, isDecl: isDecl), optIdeExceptionInlayHints in g.config.globalOptions
if optIdeExceptionInlayHints in conf.globalOptions or not isGenericInstance:
g.suggestSymbols.add SymInfoPair(sym: s, info: info, isDecl: isDecl, isGenericInstance: isGenericInstance), optIdeExceptionInlayHints in g.config.globalOptions
if conf.suggestVersion == 0:
if s.allUsages.len == 0:
s.allUsages = @[info]
else:
s.addNoDup(info)
if not isGenericInstance:
if conf.suggestVersion == 0:
if s.allUsages.len == 0:
s.allUsages = @[info]
else:
s.addNoDup(info)
if conf.ideCmd == ideUse:
findUsages(g, info, s, usageSym)
elif conf.ideCmd == ideDef:
findDefinition(g, info, s, usageSym)
elif conf.ideCmd == ideDus and s != nil:
if isTracked(info, conf.m.trackPos, s.name.s.len):
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0))
findUsages(g, info, s, usageSym)
elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex:
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0))
elif conf.ideCmd == ideOutline and isDecl:
# if a module is included then the info we have is inside the include and
# we need to walk up the owners until we find the outer most module,
# which will be the last skModule prior to an skPackage.
var
parentFileIndex = info.fileIndex # assume we're in the correct module
parentModule = s.owner
while parentModule != nil and parentModule.kind == skModule:
parentFileIndex = parentModule.info.fileIndex
parentModule = parentModule.owner
if conf.ideCmd == ideUse:
findUsages(g, info, s, usageSym)
elif conf.ideCmd == ideDef:
findDefinition(g, info, s, usageSym)
elif conf.ideCmd == ideDus and s != nil:
if isTracked(info, conf.m.trackPos, s.name.s.len):
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0))
findUsages(g, info, s, usageSym)
elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex:
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0))
elif conf.ideCmd == ideOutline and isDecl:
# if a module is included then the info we have is inside the include and
# we need to walk up the owners until we find the outer most module,
# which will be the last skModule prior to an skPackage.
var
parentFileIndex = info.fileIndex # assume we're in the correct module
parentModule = s.owner
while parentModule != nil and parentModule.kind == skModule:
parentFileIndex = parentModule.info.fileIndex
parentModule = parentModule.owner
if parentFileIndex == conf.m.trackPos.fileIndex:
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0))
if parentFileIndex == conf.m.trackPos.fileIndex:
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0))
proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) =
var pragmaNode: PNode
@@ -702,26 +704,28 @@ proc markOwnerModuleAsUsed(c: PContext; s: PSym) =
else:
inc i
proc markUsed(c: PContext; info: TLineInfo; s: PSym; checkStyle = true) =
let conf = c.config
incl(s.flags, sfUsed)
if s.kind == skEnumField and s.owner != nil:
incl(s.owner.flags, sfUsed)
if sfDeprecated in s.owner.flags:
warnAboutDeprecated(conf, info, s)
if {sfDeprecated, sfError} * s.flags != {}:
if sfDeprecated in s.flags:
if not (c.lastTLineInfo.line == info.line and
c.lastTLineInfo.col == info.col):
proc markUsed(c: PContext; info: TLineInfo; s: PSym; checkStyle = true; isGenericInstance = false) =
if not isGenericInstance:
let conf = c.config
incl(s.flags, sfUsed)
if s.kind == skEnumField and s.owner != nil:
incl(s.owner.flags, sfUsed)
if sfDeprecated in s.owner.flags:
warnAboutDeprecated(conf, info, s)
c.lastTLineInfo = info
if {sfDeprecated, sfError} * s.flags != {}:
if sfDeprecated in s.flags:
if not (c.lastTLineInfo.line == info.line and
c.lastTLineInfo.col == info.col):
warnAboutDeprecated(conf, info, s)
c.lastTLineInfo = info
if sfError in s.flags: userError(conf, info, s)
if sfError in s.flags: userError(conf, info, s)
when defined(nimsuggest):
suggestSym(c.graph, info, s, c.graph.usageSym, false)
if checkStyle:
styleCheckUse(c, info, s)
markOwnerModuleAsUsed(c, s)
suggestSym(c.graph, info, s, c.graph.usageSym, isDecl = false, isGenericInstance = isGenericInstance)
if not isGenericInstance:
if checkStyle:
styleCheckUse(c, info, s)
markOwnerModuleAsUsed(c, s)
proc safeSemExpr*(c: PContext, n: PNode): PNode =
# use only for idetools support!

View File

@@ -16,6 +16,7 @@ type
caughtExceptions*: seq[PType]
caughtExceptionsSet*: bool
isDecl*: bool
isGenericInstance*: bool
SuggestFileSymbolDatabase* = object
lineInfo*: seq[TinyLineInfo]
@@ -23,6 +24,7 @@ type
caughtExceptions*: seq[seq[PType]]
caughtExceptionsSet*: PackedBoolArray
isDecl*: PackedBoolArray
isGenericInstance*: PackedBoolArray
fileIndex*: FileIndex
trackCaughtExceptions*: bool
isSorted*: bool
@@ -82,6 +84,11 @@ proc getSymInfoPair*(s: SuggestFileSymbolDatabase; idx: int): SymInfoPair =
s.caughtExceptionsSet[idx]
else:
false,
isGenericInstance:
if s.trackCaughtExceptions:
s.isGenericInstance[idx]
else:
false,
isDecl: s.isDecl[idx]
)
@@ -90,6 +97,7 @@ proc reverse*(s: var SuggestFileSymbolDatabase) =
s.sym.reverse()
s.caughtExceptions.reverse()
s.caughtExceptionsSet.reverse()
s.isGenericInstance.reverse()
s.isDecl.reverse()
proc newSuggestFileSymbolDatabase*(aFileIndex: FileIndex; aTrackCaughtExceptions: bool): SuggestFileSymbolDatabase =
@@ -99,6 +107,7 @@ proc newSuggestFileSymbolDatabase*(aFileIndex: FileIndex; aTrackCaughtExceptions
caughtExceptions: @[],
caughtExceptionsSet: newPackedBoolArray(),
isDecl: newPackedBoolArray(),
isGenericInstance: newPackedBoolArray(),
fileIndex: aFileIndex,
trackCaughtExceptions: aTrackCaughtExceptions,
isSorted: true
@@ -119,6 +128,8 @@ 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])
if result == 0 and s.trackCaughtExceptions:
result = cmp(s.isGenericInstance[i], s.isGenericInstance[j])
proc exchange(s: var SuggestFileSymbolDatabase; i, j: int) =
if i == j:
@@ -133,6 +144,9 @@ proc exchange(s: var SuggestFileSymbolDatabase; i, j: int) =
var tmp3 = s.caughtExceptionsSet[i]
s.caughtExceptionsSet[i] = s.caughtExceptionsSet[j]
s.caughtExceptionsSet[j] = tmp3
var tmp6 = s.isGenericInstance[i]
s.isGenericInstance[i] = s.isGenericInstance[j]
s.isGenericInstance[j] = tmp6
var tmp4 = s.isDecl[i]
s.isDecl[i] = s.isDecl[j]
s.isDecl[j] = tmp4
@@ -196,12 +210,17 @@ proc add*(s: var SuggestFileSymbolDatabase; v: SymInfoPair) =
if s.trackCaughtExceptions:
s.caughtExceptions.add(v.caughtExceptions)
s.caughtExceptionsSet.add(v.caughtExceptionsSet)
s.isGenericInstance.add(v.isGenericInstance)
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 =
proc findSymInfoIndex*(s: var SuggestFileSymbolDatabase; li: TLineInfo; isGenericInstance: bool): int =
# if trackCaughtExceptions is false, then all records in the database are not generic instances, so
# if we're searching for a generic instance, we find none
if isGenericInstance and not s.trackCaughtExceptions:
return -1
doAssert(li.fileIndex == s.fileIndex)
if not s.isSorted:
s.sort()
@@ -210,3 +229,17 @@ proc findSymInfoIndex*(s: var SuggestFileSymbolDatabase; li: TLineInfo): int =
col: li.col
)
result = binarySearch(s.lineInfo, q, cmp)
# if trackCaughtExceptions is false, then all records in the database are not generic instances, so
# if we're a searching for a non-generic instance, then we're done, we return what we have found
if not isGenericInstance and not s.trackCaughtExceptions:
return
# in this case trackCaughtExceptions is true, and the database contains both generic and non-generic instances, so we need
# to check the isGenericInstance flag also
if result != -1:
# search through a sequence of equal lineInfos to find a matching isGenericInstance
while result > 0 and s.isGenericInstance[result] != isGenericInstance and cmp(s.lineInfo[result], s.lineInfo[result - 1]) == 0:
dec result
while result < (s.lineInfo.len - 1) and s.isGenericInstance[result] != isGenericInstance and cmp(s.lineInfo[result], s.lineInfo[result + 1]) == 0:
inc result
if s.isGenericInstance[result] != isGenericInstance:
result = -1

View File

@@ -766,7 +766,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
prag.add("effectsOf: ")
prag.add(effectsOfStr)
if not hasImplicitRaises and prefer == preferInferredEffects and not isNil(t.owner) and not isNil(t.owner.typ) and not isNil(t.owner.typ.n) and (t.owner.typ.n.len > 0):
let effects = t.owner.typ.n[0]
let effects = t.n[0]
if effects.kind == nkEffectList and effects.len == effectListLen:
var inferredRaisesStr = ""
let effs = effects[exceptionEffects]

View File

@@ -822,7 +822,7 @@ func deduplicateSymInfoPair[SymInfoPair](xs: seq[SymInfoPair]): seq[SymInfoPair]
result.add(itm)
result.reverse()
func deduplicateSymInfoPair(xs: SuggestFileSymbolDatabase): SuggestFileSymbolDatabase =
func deduplicateSymInfoPair(xs: SuggestFileSymbolDatabase, isGenericInstance: bool): SuggestFileSymbolDatabase =
# xs contains duplicate items and we want to filter them by range because the
# sym may not match. This can happen when xs contains the same definition but
# with different signature because suggestSym might be called multiple times
@@ -833,6 +833,7 @@ func deduplicateSymInfoPair(xs: SuggestFileSymbolDatabase): SuggestFileSymbolDat
isDecl: newPackedBoolArray(),
caughtExceptions: newSeqOfCap[seq[PType]](xs.caughtExceptions.len),
caughtExceptionsSet: newPackedBoolArray(),
isGenericInstance: newPackedBoolArray(),
fileIndex: xs.fileIndex,
trackCaughtExceptions: xs.trackCaughtExceptions,
isSorted: false
@@ -846,14 +847,16 @@ func deduplicateSymInfoPair(xs: SuggestFileSymbolDatabase): SuggestFileSymbolDat
found = true
break
if not found:
result.add(xs.getSymInfoPair(i))
let q = xs.getSymInfoPair(i)
if q.isGenericInstance == isGenericInstance:
result.add(q)
dec i
result.reverse()
proc findSymData(graph: ModuleGraph, trackPos: TLineInfo):
proc findSymData(graph: ModuleGraph, trackPos: TLineInfo, isGenericInstance: bool = false):
ref SymInfoPair =
result = nil
let db = graph.fileSymbols(trackPos.fileIndex).deduplicateSymInfoPair
let db = graph.fileSymbols(trackPos.fileIndex).deduplicateSymInfoPair(isGenericInstance)
doAssert(db.fileIndex == trackPos.fileIndex)
for i in db.lineInfo.low..db.lineInfo.high:
if isTracked(db.lineInfo[i], TinyLineInfo(line: trackPos.line, col: trackPos.col), db.sym[i].name.s.len):
@@ -867,28 +870,28 @@ func isInRange*(current, startPos, endPos: TinyLineInfo, tokenLen: int): bool =
(current.line > startPos.line or (current.line == startPos.line and current.col>=startPos.col)) and
(current.line < endPos.line or (current.line == endPos.line and current.col <= endPos.col))
proc findSymDataInRange(graph: ModuleGraph, startPos, endPos: TLineInfo):
proc findSymDataInRange(graph: ModuleGraph, startPos, endPos: TLineInfo, isGenericInstance: bool = false):
seq[SymInfoPair] =
result = newSeq[SymInfoPair]()
let db = graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair
let db = graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair(isGenericInstance)
for i in db.lineInfo.low..db.lineInfo.high:
if isInRange(db.lineInfo[i], TinyLineInfo(line: startPos.line, col: startPos.col), TinyLineInfo(line: endPos.line, col: endPos.col), db.sym[i].name.s.len):
result.add(db.getSymInfoPair(i))
proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int):
proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int, isGenericInstance: bool = false):
ref SymInfoPair =
let
fileIdx = fileInfoIdx(graph.config, file)
trackPos = newLineInfo(fileIdx, line, col)
result = findSymData(graph, trackPos)
result = findSymData(graph, trackPos, isGenericInstance)
proc findSymDataInRange(graph: ModuleGraph, file: AbsoluteFile; startLine, startCol, endLine, endCol: int):
proc findSymDataInRange(graph: ModuleGraph, file: AbsoluteFile; startLine, startCol, endLine, endCol: int, isGenericInstance: bool = false):
seq[SymInfoPair] =
let
fileIdx = fileInfoIdx(graph.config, file)
startPos = newLineInfo(fileIdx, startLine, startCol)
endPos = newLineInfo(fileIdx, endLine, endCol)
result = findSymDataInRange(graph, startPos, endPos)
result = findSymDataInRange(graph, startPos, endPos, isGenericInstance)
proc markDirtyIfNeeded(graph: ModuleGraph, file: string, originalFileIdx: FileIndex) =
let sha = $sha1.secureHashFile(file)
@@ -937,7 +940,7 @@ proc suggestInlayHintResultException(graph: ModuleGraph, sym: PSym, info: TLineI
if sym.kind == skParam and sfEffectsDelayed in sym.flags:
return
var raisesList: seq[PType] = @[getEbase(graph, info)]
var raisesList: seq[PType] = @[]
let t = sym.typ
if not isNil(t) and not isNil(t.n) and t.n.len > 0 and t.n[0].len > exceptionEffects:
@@ -945,7 +948,6 @@ proc suggestInlayHintResultException(graph: ModuleGraph, sym: PSym, info: TLineI
if effects.kind == nkEffectList and effects.len == effectListLen:
let effs = effects[exceptionEffects]
if not isNil(effs):
raisesList = @[]
for eff in items(effs):
if not isNil(eff):
raisesList.add(eff.typ)
@@ -1154,7 +1156,7 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
incl m.flags, sfDirty
of ideOutline:
let n = parseFile(fileIndex, graph.cache, graph.config)
graph.iterateOutlineNodes(n, graph.fileSymbols(fileIndex).deduplicateSymInfoPair)
graph.iterateOutlineNodes(n, graph.fileSymbols(fileIndex).deduplicateSymInfoPair(false))
of ideChk:
myLog fmt "Reporting errors for {graph.suggestErrors.len} file(s)"
for sug in graph.suggestErrorsIter:
@@ -1206,7 +1208,7 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
# find first mention of the symbol in the file containing the definition.
# It is either the definition or the declaration.
var first: SymInfoPair = default(SymInfoPair)
let db = graph.fileSymbols(s.sym.info.fileIndex).deduplicateSymInfoPair
let db = graph.fileSymbols(s.sym.info.fileIndex).deduplicateSymInfoPair(false)
for i in db.lineInfo.low..db.lineInfo.high:
if s.sym.symbolEqual(db.sym[i]):
first = db.getSymInfoPair(i)
@@ -1279,12 +1281,16 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
else:
myLog fmt "Discarding unknown inlay hint parameter {token}"
let s = graph.findSymDataInRange(file, line, col, endLine, endCol)
for q in s:
if typeHints and q.sym.kind in {skLet, skVar, skForVar, skConst} and q.isDecl and not q.sym.hasUserSpecifiedType:
graph.suggestInlayHintResultType(q.sym, q.info, ideInlayHints)
if exceptionHints and q.sym.kind in {skProc, skFunc, skMethod, skVar, skLet, skParam} and not q.isDecl:
graph.suggestInlayHintResultException(q.sym, q.info, ideInlayHints, caughtExceptions = q.caughtExceptions, caughtExceptionsSet = q.caughtExceptionsSet)
if typeHints:
let s = graph.findSymDataInRange(file, line, col, endLine, endCol, false)
for q in s:
if typeHints and q.sym.kind in {skLet, skVar, skForVar, skConst} and q.isDecl and not q.sym.hasUserSpecifiedType:
graph.suggestInlayHintResultType(q.sym, q.info, ideInlayHints)
if exceptionHints:
let sGen = graph.findSymDataInRange(file, line, col, endLine, endCol, true)
for q in sGen:
if q.sym.kind in {skProc, skFunc, skMethod, skVar, skLet, skParam} and not q.isDecl:
graph.suggestInlayHintResultException(q.sym, q.info, ideInlayHints, caughtExceptions = q.caughtExceptions, caughtExceptionsSet = q.caughtExceptionsSet)
else:
myLog fmt "Discarding {cmd}"