Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inlay hints support #22896

Merged
merged 15 commits into from
Nov 4, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,7 @@ type
info*: TLineInfo
when defined(nimsuggest):
endInfo*: TLineInfo
hasUserSpecifiedType*: bool # used for determining whether to display inlay type hints
owner*: PSym
flags*: TSymFlags
ast*: PNode # syntax tree of proc, iterator, etc.:
Expand Down
1 change: 1 addition & 0 deletions compiler/modulegraphs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type
SymInfoPair* = object
sym*: PSym
info*: TLineInfo
isDecl*: bool

PipelinePass* = enum
NonePass
Expand Down
18 changes: 17 additions & 1 deletion compiler/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ type
IdeCmd* = enum
ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideChkFile, ideMod,
ideHighlight, ideOutline, ideKnown, ideMsg, ideProject, ideGlobalSymbols,
ideRecompile, ideChanged, ideType, ideDeclaration, ideExpand
ideRecompile, ideChanged, ideType, ideDeclaration, ideExpand, ideInlayHints

Feature* = enum ## experimental features; DO NOT RENAME THESE!
dotOperators,
Expand Down Expand Up @@ -287,9 +287,24 @@ type
version*: int
endLine*: uint16
endCol*: int
inlayHintInfo*: SuggestInlayHint

Suggestions* = seq[Suggest]

SuggestInlayHintKind* = enum
sihkType = "Type",
sihkParameter = "Parameter"

SuggestInlayHint* = ref object
kind*: SuggestInlayHintKind
line*: int # Starts at 1
column*: int # Starts at 0
label*: string
paddingLeft*: bool
paddingRight*: bool
allowInsert*: bool
tooltip*: string

ProfileInfo* = object
time*: float
count*: int
Expand Down Expand Up @@ -1070,6 +1085,7 @@ proc `$`*(c: IdeCmd): string =
of ideRecompile: "recompile"
of ideChanged: "changed"
of ideType: "type"
of ideInlayHints: "inlayHints"

proc floatInt64Align*(conf: ConfigRef): int16 =
## Returns either 4 or 8 depending on reasons.
Expand Down
4 changes: 4 additions & 0 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -672,9 +672,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
addToVarSection(c, result, b)
continue

var hasUserSpecifiedType = false
var typ: PType = nil
if a[^2].kind != nkEmpty:
typ = semTypeNode(c, a[^2], nil)
hasUserSpecifiedType = true

var typFlags: TTypeAllowedFlags = {}

Expand Down Expand Up @@ -746,6 +748,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
addToVarSection(c, result, n, a)
continue
var v = semIdentDef(c, a[j], symkind, false)
when defined(nimsuggest):
v.hasUserSpecifiedType = hasUserSpecifiedType
styleCheckDef(c, v)
onDef(a[j].info, v)
if sfGenSym notin v.flags:
Expand Down
113 changes: 72 additions & 41 deletions compiler/suggest.nim
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo): int
result = 0
elif ident[0] in linter.Letters and ident[^1] != '=':
result = identLen(line, column)
if cmpIgnoreStyle(line[column..column + result - 1], ident) != 0:
if cmpIgnoreStyle(line[column..column + result - 1], ident[0..min(result-1,len(ident)-1)]) != 0:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because ident may have extra stuff appended when part of a template. E.g. when a variable is part of a template, "`gensymXXX" is appended, where XXX are some numbers. In the previous version of the code, this would return a token length of 0. In turn, this causes the inlay hint to be put in the wrong place. Instead of:

template benchmark(benchmarkName: string, code: untyped) =
  block:
    debug "Started...", benchmark = benchmarkName
    let t0: float = epochTime()
    code
    let elapsed: float = epochTime() - t0
    let elapsedStr: string = elapsed.formatFloat(format = ffDecimal, precision = 3)
    debug "CPU Time", benchmark = benchmarkName, time = elapsedStr

You would get:

template benchmark(benchmarkName: string, code: untyped) =
  block:
    debug "Started...", benchmark = benchmarkName
    let : floatt0 = epochTime()
    code
    let : floatelapsed = epochTime() - t0
    let : stringelapsedStr = elapsed.formatFloat(format = ffDecimal, precision = 3)
    debug "CPU Time", benchmark = benchmarkName, time = elapsedStr

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sucks, but ok.

result = 0
else:
var sourceIdent: string = ""
Expand Down Expand Up @@ -175,58 +175,89 @@ proc symToSuggest*(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info
result.filePath = toFullPath(g.config, infox)
result.line = toLinenumber(infox)
result.column = toColumn(infox)
result.tokenLen = if section != ideHighlight:
result.tokenLen = if not (section in {ideHighlight, ideInlayHints}):
Araq marked this conversation as resolved.
Show resolved Hide resolved
s.name.s.len
else:
getTokenLenFromSource(g.config, s.name.s, infox)
result.version = g.config.suggestVersion
result.endLine = endLine
result.endCol = endCol

proc `$`*(suggest: Suggest): string =
result = $suggest.section
proc `$`*(suggest: SuggestInlayHint): string =
result = $suggest.kind
result.add(sep)
if suggest.section == ideHighlight:
if suggest.symkind.TSymKind == skVar and suggest.isGlobal:
result.add("skGlobalVar")
elif suggest.symkind.TSymKind == skLet and suggest.isGlobal:
result.add("skGlobalLet")
else:
result.add($suggest.symkind.TSymKind)
result.add(sep)
result.add($suggest.line)
result.add(sep)
result.add($suggest.column)
result.add(sep)
result.add($suggest.tokenLen)
result.add($suggest.line)
result.add(sep)
result.add($suggest.column)
result.add(sep)
result.add(suggest.label)
result.add(sep)
result.add($suggest.paddingLeft)
result.add(sep)
result.add($suggest.paddingRight)
result.add(sep)
result.add($suggest.allowInsert)
result.add(sep)
result.add(suggest.tooltip)

proc `$`*(suggest: Suggest): string =
if suggest.section == ideInlayHints:
result = $suggest.inlayHintInfo
else:
result.add($suggest.symkind.TSymKind)
result.add(sep)
if suggest.qualifiedPath.len != 0:
result.add(suggest.qualifiedPath.join("."))
result.add(sep)
result.add(suggest.forth)
result.add(sep)
result.add(suggest.filePath)
result.add(sep)
result.add($suggest.line)
result = $suggest.section
result.add(sep)
result.add($suggest.column)
result.add(sep)
when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
result.add(suggest.doc.escape)
if suggest.version == 0 or suggest.version == 3:
if suggest.section == ideHighlight:
if suggest.symkind.TSymKind == skVar and suggest.isGlobal:
result.add("skGlobalVar")
elif suggest.symkind.TSymKind == skLet and suggest.isGlobal:
result.add("skGlobalLet")
else:
result.add($suggest.symkind.TSymKind)
result.add(sep)
result.add($suggest.line)
result.add(sep)
result.add($suggest.column)
result.add(sep)
result.add($suggest.tokenLen)
else:
result.add($suggest.symkind.TSymKind)
result.add(sep)
if suggest.qualifiedPath.len != 0:
result.add(suggest.qualifiedPath.join("."))
result.add(sep)
result.add(suggest.forth)
result.add(sep)
result.add($suggest.quality)
if suggest.section == ideSug:
result.add(suggest.filePath)
result.add(sep)
result.add($suggest.line)
result.add(sep)
result.add($suggest.column)
result.add(sep)
when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
result.add(suggest.doc.escape)
if suggest.version == 0 or suggest.version == 3:
result.add(sep)
result.add($suggest.prefix)
result.add($suggest.quality)
if suggest.section == ideSug:
result.add(sep)
result.add($suggest.prefix)

if (suggest.version == 3 and suggest.section in {ideOutline, ideExpand}):
result.add(sep)
result.add($suggest.endLine)
result.add(sep)
result.add($suggest.endCol)
if (suggest.version == 3 and suggest.section in {ideOutline, ideExpand}):
result.add(sep)
result.add($suggest.endLine)
result.add(sep)
result.add($suggest.endCol)

proc suggestToSuggestInlayHint*(sug: Suggest): SuggestInlayHint =
new(result)
result.kind = sihkType
result.line = sug.line
result.column = sug.column + sug.tokenLen
result.label = ": " & sug.forth
result.paddingLeft = false
result.paddingRight = false
result.allowInsert = true
result.tooltip = ""
nickysn marked this conversation as resolved.
Show resolved Hide resolved

proc suggestResult*(conf: ConfigRef; s: Suggest) =
if not isNil(conf.suggestionResultHook):
Expand Down Expand Up @@ -535,7 +566,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)
g.suggestSymbols.mgetOrPut(info.fileIndex, @[]).add SymInfoPair(sym: s, info: info, isDecl: isDecl)

if conf.suggestVersion == 0:
if s.allUsages.len == 0:
Expand Down
55 changes: 53 additions & 2 deletions nimsuggest/nimsuggest.nim
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ proc listEpc(): SexpNode =
argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
result = newSList()
for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration"]:
for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration", "inlayHints"]:
let
cmd = sexp(command)
methodDesc = newSList()
Expand Down Expand Up @@ -506,6 +506,7 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
of "chkfile": conf.ideCmd = ideChkFile
of "recompile": conf.ideCmd = ideRecompile
of "type": conf.ideCmd = ideType
of "inlayhints": conf.ideCmd = ideInlayHints
else: err()
var dirtyfile = ""
var orig = ""
Expand Down Expand Up @@ -774,13 +775,33 @@ proc findSymData(graph: ModuleGraph, trackPos: TLineInfo):
result[] = s
break

func isInRange*(current, startPos, endPos: TLineInfo, tokenLen: int): bool =
result = current.fileIndex == startPos.fileIndex and
(current.line > startPos.line or (current.line == startPos.line and current.col>=startPos.col)) and
(current.line < endPos.line or (current.line == endPos.line and current.col <= endPos.col))

proc findSymDataInRange(graph: ModuleGraph, startPos, endPos: TLineInfo):
seq[SymInfoPair] =
result = newSeq[SymInfoPair]()
for s in graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair:
if isInRange(s.info, startPos, endPos, s.sym.name.s.len):
result.add(s)

proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int):
ref SymInfoPair =
let
fileIdx = fileInfoIdx(graph.config, file)
trackPos = newLineInfo(fileIdx, line, col)
result = findSymData(graph, trackPos)

proc findSymDataInRange(graph: ModuleGraph, file: AbsoluteFile; startLine, startCol, endLine, endCol: int):
seq[SymInfoPair] =
let
fileIdx = fileInfoIdx(graph.config, file)
startPos = newLineInfo(fileIdx, startLine, startCol)
endPos = newLineInfo(fileIdx, endLine, endCol)
result = findSymDataInRange(graph, startPos, endPos)

proc markDirtyIfNeeded(graph: ModuleGraph, file: string, originalFileIdx: FileIndex) =
let sha = $sha1.secureHashFile(file)
if graph.config.m.fileInfos[originalFileIdx.int32].hash != sha or graph.config.ideCmd == ideSug:
Expand All @@ -803,6 +824,23 @@ proc suggestResult(graph: ModuleGraph, sym: PSym, info: TLineInfo,
endLine = endLine, endCol = endCol)
suggestResult(graph.config, suggest)

proc suggestInlayHintResult(graph: ModuleGraph, sym: PSym, info: TLineInfo,
defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) =
let section = if defaultSection != ideNone:
defaultSection
elif sym.info.exactEquals(info):
ideDef
else:
ideUse
var suggestDef = symToSuggest(graph, sym, isLocal=false, section,
info, 100, PrefixMatch.None, false, 0, true,
endLine = endLine, endCol = endCol)
suggestDef.inlayHintInfo = suggestToSuggestInlayHint(suggestDef)
suggestDef.section = ideInlayHints
if sym.kind == skForVar:
suggestDef.inlayHintInfo.allowInsert = false
suggestResult(graph.config, suggestDef)

const
# kinds for ideOutline and ideGlobalSymbols
searchableSymKinds = {skField, skEnumField, skIterator, skMethod, skFunc, skProc, skConverter, skTemplate}
Expand Down Expand Up @@ -910,7 +948,7 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
graph.markDirtyIfNeeded(dirtyFile.string, fileInfoIdx(conf, file))

# these commands require fully compiled project
if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk} and graph.needsCompilation():
if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk, ideInlayHints} and graph.needsCompilation():
graph.recompilePartially()
# when doing incremental build for the project root we should make sure that
# everything is unmarked as no longer beeing dirty in case there is no
Expand Down Expand Up @@ -1066,6 +1104,19 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,

graph.markDirty fileIndex
graph.markClientsDirty fileIndex
of ideInlayHints:
myLog fmt "Executing inlayHints"
var endLine = 0
var endCol = -1
var i = 0
i += skipWhile(tag, seps, i)
i += parseInt(tag, endLine, i)
i += skipWhile(tag, seps, i)
i += parseInt(tag, endCol, i)
let s = graph.findSymDataInRange(file, line, col, endLine, endCol)
for q in s:
if q.sym.kind in [skLet, skVar, skForVar] and q.isDecl and not q.sym.hasUserSpecifiedType:
Araq marked this conversation as resolved.
Show resolved Hide resolved
graph.suggestInlayHintResult(q.sym, q.info, ideInlayHints)
else:
myLog fmt "Discarding {cmd}"

Expand Down
Loading