From a77ca1a4bf8a4a3a82a3a48c242c5de18d57e8a3 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolov Date: Wed, 8 Nov 2023 01:22:40 +0200 Subject: [PATCH] Inlay hints backport to Nim v1.6.x (#22920) --- compiler/ast.nim | 1 + compiler/modulegraphs.nim | 1 + compiler/options.nim | 18 +++++- compiler/semstmts.nim | 8 +++ compiler/suggest.nim | 119 ++++++++++++++++++++++++-------------- compiler/types.nim | 15 +++-- nimsuggest/nimsuggest.nim | 55 +++++++++++++++++- 7 files changed, 168 insertions(+), 49 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index a6a3c46a56..f3395a7d43 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -887,6 +887,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.: diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 77bc375cd3..98cb96100a 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -53,6 +53,7 @@ type SymInfoPair* = object sym*: PSym info*: TLineInfo + isDecl*: bool ModuleGraph* {.acyclic.} = ref object ifaces*: seq[Iface] ## indexed by int32 fileIdx diff --git a/compiler/options.nim b/compiler/options.nim index 8fb05d5e90..662f463e84 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -185,7 +185,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! implicitDeref, @@ -266,9 +266,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 @@ -1035,6 +1050,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. diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 906362e0b5..e74872bc23 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -569,9 +569,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config) checkMinSonsLen(a, 3, c.config) + var hasUserSpecifiedType = false var typ: PType = nil if a[^2].kind != nkEmpty: typ = semTypeNode(c, a[^2], nil) + hasUserSpecifiedType = true var typFlags: TTypeAllowedFlags @@ -644,6 +646,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: @@ -724,9 +728,11 @@ proc semConst(c: PContext, n: PNode): PNode = if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config) checkMinSonsLen(a, 3, c.config) + var hasUserSpecifiedType = false var typ: PType = nil if a[^2].kind != nkEmpty: typ = semTypeNode(c, a[^2], nil) + hasUserSpecifiedType = true var typFlags: TTypeAllowedFlags @@ -771,6 +777,8 @@ proc semConst(c: PContext, n: PNode): PNode = for j in 0.. 0: @@ -572,8 +576,11 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = if not isIntLit(t) or prefer == preferExported: result = typeToStr[t.kind] else: - if prefer == preferGenericArg: + case prefer: + of preferGenericArg: result = $t.n.intVal + of preferInlayHint: + result = "int" else: result = "int literal(" & $t.n.intVal & ")" of tyGenericInst, tyGenericInvocation: diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index 74bd9b704d..5d2c8cbb17 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -153,7 +153,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() @@ -478,6 +478,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 = "" @@ -747,6 +748,18 @@ 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 @@ -754,6 +767,14 @@ proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int): 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: @@ -776,6 +797,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} @@ -883,7 +921,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 @@ -1039,6 +1077,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, skConst} and q.isDecl and not q.sym.hasUserSpecifiedType: + graph.suggestInlayHintResult(q.sym, q.info, ideInlayHints) else: myLog fmt "Discarding {cmd}"