diff options
Diffstat (limited to 'nimsuggest')
68 files changed, 1751 insertions, 522 deletions
diff --git a/nimsuggest/config.nims b/nimsuggest/config.nims new file mode 100644 index 000000000..ee19f9893 --- /dev/null +++ b/nimsuggest/config.nims @@ -0,0 +1,2 @@ +# xxx not sure why this flag isn't needed: switch("processing", "filenames") +switch("filenames", "canonical") diff --git a/nimsuggest/crashtester.nim b/nimsuggest/crashtester.nim index 10fef2b23..ead97adec 100644 --- a/nimsuggest/crashtester.nim +++ b/nimsuggest/crashtester.nim @@ -32,7 +32,7 @@ proc callNimsuggest() = if a == DummyEof: break var line = 0 - for i in 0..< contents.len: + for i in 0 ..< contents.len: let slic = contents[0..i] writeFile("temp000.nim", slic) let (line, col) = getPosition(slic) diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index a48e9443d..04bae08c1 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -7,6 +7,24 @@ # distribution, for details about the copyright. # +import compiler/renderer +import compiler/types +import compiler/trees +import compiler/wordrecg +import compiler/sempass2 +import strformat +import algorithm +import tables +import times +import procmonitor + +template tryImport(module) = import module + +when compiles tryImport ../dist/checksums/src/checksums/sha1: + import ../dist/checksums/src/checksums/sha1 +else: + import checksums/sha1 + ## Nimsuggest is a tool that helps to give editors IDE like capabilities. when not defined(nimcore): @@ -16,17 +34,21 @@ import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp # Do NOT import suggest. It will lead to weird bugs with # suggestionResultHook, because suggest.nim is included by sigmatch. # So we import that one instead. -import compiler / [options, commands, modules, sem, +import compiler / [options, commands, modules, passes, passaux, msgs, sigmatch, ast, idents, modulegraphs, prefixmatches, lineinfos, cmdlinehelper, - pathutils] + pathutils, condsyms, syntaxes, suggestsymdb] + +when defined(nimPreviewSlimSystem): + import std/typedthreads when defined(windows): import winlean else: import posix +const HighestSuggestProtocolVersion = 4 const DummyEof = "!EOF!" const Usage = """ Nimsuggest - Tool to give every editor IDE like capabilities for Nim @@ -39,15 +61,25 @@ Options: --address:HOST binds to that address, by default "" --stdin read commands from stdin and write results to stdout instead of using sockets + --clientProcessId:PID shutdown nimsuggest in case this process dies --epc use emacs epc mode --debug enable debug output --log enable verbose logging to nimsuggest.log file --v1 use version 1 of the protocol; for backwards compatibility + --v2 use version 2(default) of the protocol + --v3 use version 3 of the protocol + --v4 use version 4 of the protocol + --info:X information + --info:nimVer return the Nim compiler version that nimsuggest uses internally + --info:protocolVer return the newest protocol version that is supported + --info:capabilities return the capabilities supported by nimsuggest --refresh perform automatic refreshes to keep the analysis precise --maxresults:N limit the number of suggestions to N --tester implies --stdin and outputs a line '""" & DummyEof & """' for the tester --find attempts to find the project file of the current project + --exceptionInlayHints:on|off + globally turn exception inlay hints on|off The server then listens to the connection and takes line-based commands. @@ -66,7 +98,7 @@ type var gPort = 6000.Port - gAddress = "" + gAddress = "127.0.0.1" gMode: Mode gEmitEof: bool # whether we write '!EOF!' dummy lines gLogging = defined(logging) @@ -76,6 +108,9 @@ var requests: Channel[string] results: Channel[Suggest] +proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string, + graph: ModuleGraph); + proc writelnToChannel(line: string) = results.send(Suggest(section: ideMsg, doc: line)) @@ -96,9 +131,12 @@ const "type 'quit' to quit\n" & "type 'debug' to toggle debug mode on/off\n" & "type 'terse' to toggle terse mode on/off" - -type - EUnexpectedCommand = object of Exception + #List of currently supported capabilities. So lang servers/ides can iterate over and check for what's enabled + Capabilities = [ + "con", #current NimSuggest supports the `con` commmand + "exceptionInlayHints", + "unknownFile", #current NimSuggest can handle unknown files + ] proc parseQuoted(cmd: string; outp: var string; start: int): int = var i = start @@ -128,6 +166,9 @@ proc sexp(s: Suggest): SexpNode = ]) if s.section == ideSug: result.add convertSexp(s.prefix) + if s.section in {ideOutline, ideExpand} and s.version == 3: + result.add convertSexp(s.endLine.int) + result.add convertSexp(s.endCol) proc sexp(s: seq[Suggest]): SexpNode = result = newSList() @@ -140,7 +181,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"]: + for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration", "inlayHints"]: let cmd = sexp(command) methodDesc = newSList() @@ -163,9 +204,51 @@ proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym = if m != nil and m.ast != nil: result = findNode(m.ast, trackPos) -proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; +template benchmark(benchmarkName: untyped, code: untyped) = + block: + myLog "Started [" & benchmarkName & "]..." + let t0 = epochTime() + code + let elapsed = epochTime() - t0 + let elapsedStr = elapsed.formatFloat(format = ffDecimal, precision = 3) + myLog "CPU Time [" & benchmarkName & "] " & elapsedStr & "s" + +proc clearInstCache(graph: ModuleGraph, projectFileIdx: FileIndex) = + if projectFileIdx == InvalidFileIdx: + graph.typeInstCache.clear() + graph.procInstCache.clear() + return + var typeIdsToDelete = newSeq[ItemId]() + for id in graph.typeInstCache.keys: + if id.module == projectFileIdx.int: + typeIdsToDelete.add id + for id in typeIdsToDelete: + graph.typeInstCache.del id + var procIdsToDelete = newSeq[ItemId]() + for id in graph.procInstCache.keys: + if id.module == projectFileIdx.int: + procIdsToDelete.add id + for id in procIdsToDelete: + graph.procInstCache.del id + + for tbl in mitems(graph.attachedOps): + var attachedOpsToDelete = newSeq[ItemId]() + for id in tbl.keys: + if id.module == projectFileIdx.int and sfOverridden in resolveAttachedOp(graph, tbl[id]).flags: + attachedOpsToDelete.add id + for id in attachedOpsToDelete: + tbl.del id + +proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, tag: string, graph: ModuleGraph) = let conf = graph.config + + if conf.suggestVersion >= 3: + let command = fmt "cmd = {cmd} {file}:{line}:{col}" + benchmark command: + executeNoHooksV3(cmd, file, dirtyfile, line, col, tag, graph) + return + myLog("cmd: " & $cmd & ", file: " & file.string & ", dirtyFile: " & dirtyfile.string & "[" & $line & ":" & $col & "]") @@ -184,6 +267,7 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; if conf.suggestVersion == 1: graph.usageSym = nil if not isKnownFile: + graph.clearInstCache(dirtyIdx) graph.compileProject(dirtyIdx) if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and dirtyfile.isEmpty: @@ -194,15 +278,19 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; graph.markClientsDirty dirtyIdx if conf.ideCmd != ideMod: if isKnownFile: + graph.clearInstCache(modIdx) graph.compileProject(modIdx) if conf.ideCmd in {ideUse, ideDus}: let u = if conf.suggestVersion != 1: graph.symFromInfo(conf.m.trackPos) else: graph.usageSym if u != nil: - listUsages(conf, u) + listUsages(graph, u) else: localError(conf, conf.m.trackPos, "found no symbol at this position " & (conf $ conf.m.trackPos)) -proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; +proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, graph: ModuleGraph) = + executeNoHooks(cmd, file, dirtyfile, line, col, "", graph) + +proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; tag: string, graph: ModuleGraph) = if cmd == ideChk: graph.config.structuredErrorHook = errorHook @@ -210,7 +298,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; else: graph.config.structuredErrorHook = nil graph.config.writelnHook = myLog - executeNoHooks(cmd, file, dirtyfile, line, col, graph) + executeNoHooks(cmd, file, dirtyfile, line, col, tag, graph) proc executeEpc(cmd: IdeCmd, args: SexpNode; graph: ModuleGraph) = @@ -221,7 +309,7 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode; var dirtyfile = AbsoluteFile"" if len(args) > 3: dirtyfile = AbsoluteFile args[3].getStr("") - execute(cmd, file, dirtyfile, int(line), int(column), graph) + execute(cmd, file, dirtyfile, int(line), int(column), args[3].getStr, graph) proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string, returnSymbol = "return") = @@ -322,7 +410,7 @@ proc replTcp(x: ThreadParams) {.thread.} = else: server.bindAddr(x.port, x.address) server.listen() - var inp = "".TaintedString + var inp = "" var stdoutSocket: Socket while true: accept(server, stdoutSocket) @@ -439,6 +527,18 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) = of "terse": toggle optIdeTerse of "known": conf.ideCmd = ideKnown of "project": conf.ideCmd = ideProject + of "changed": conf.ideCmd = ideChanged + of "globalsymbols": conf.ideCmd = ideGlobalSymbols + of "declaration": conf.ideCmd = ideDeclaration + of "expand": conf.ideCmd = ideExpand + of "chkfile": conf.ideCmd = ideChkFile + of "recompile": conf.ideCmd = ideRecompile + of "type": conf.ideCmd = ideType + of "inlayhints": + if conf.suggestVersion >= 4: + conf.ideCmd = ideInlayHints + else: + err() else: err() var dirtyfile = "" var orig = "" @@ -455,6 +555,7 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) = i += parseInt(cmd, line, i) i += skipWhile(cmd, seps, i) i += parseInt(cmd, col, i) + let tag = substr(cmd, i) if conf.ideCmd == ideKnown: results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, AbsoluteFile orig)))) @@ -463,23 +564,23 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) = else: if conf.ideCmd == ideChk: for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) - execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, graph) + execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, tag, graph) sentinel() proc recompileFullProject(graph: ModuleGraph) = - #echo "recompiling full project" - resetSystemArtifacts(graph) - graph.vm = nil - graph.resetAllModules() - GC_fullCollect() - compileProject(graph) - #echo GC_getStatistics() + benchmark "Recompilation(clean)": + graph.resetForBackend() + graph.resetSystemArtifacts() + graph.vm = nil + graph.resetAllModules() + GC_fullCollect() + graph.compileProject() proc mainThread(graph: ModuleGraph) = let conf = graph.config - if gLogging: - for it in conf.searchPaths: - log(it.string) + myLog "searchPaths: " + for it in conf.searchPaths: + myLog(" " & it.string) proc wrHook(line: string) {.closure.} = if gMode == mepc: @@ -502,7 +603,7 @@ proc mainThread(graph: ModuleGraph) = else: os.sleep 250 idle += 1 - if idle == 20 and gRefresh: + if idle == 20 and gRefresh and conf.suggestVersion < 3: # we use some nimsuggest activity to enable a lazy recompile: conf.ideCmd = ideChk conf.writelnHook = proc (s: string) = discard @@ -520,7 +621,8 @@ proc mainCommand(graph: ModuleGraph) = clearPasses(graph) registerPass graph, verbosePass registerPass graph, semPass - conf.cmd = cmdIdeTools + conf.setCmd cmdIdeTools + defineSymbol(conf.symbols, $conf.backend) wantMainModule(conf) if not fileExists(conf.projectFull): @@ -528,19 +630,27 @@ proc mainCommand(graph: ModuleGraph) = add(conf.searchPaths, conf.libpath) - # do not stop after the first error: - conf.errorMax = high(int) + conf.setErrorMaxHighMaybe # honor --errorMax even if it may not make sense here # do not print errors, but log them - conf.writelnHook = myLog - conf.structuredErrorHook = nil + conf.writelnHook = proc (msg: string) = discard + + if graph.config.suggestVersion >= 3: + graph.config.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = + let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info), + line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev) + graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest # compile the project before showing any input so that we already # can answer questions right away: - compileProject(graph) + benchmark "Initial compilation": + compileProject(graph) open(requests) open(results) + if graph.config.clientProcessId != 0: + hookProcMonitor(graph.config.clientProcessId) + case gMode of mstdin: createThread(inputThread, replStdin, (gPort, gAddress)) of mtcp: createThread(inputThread, replTcp, (gPort, gAddress)) @@ -588,8 +698,28 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = gMode = mepc conf.verbosity = 0 # Port number gotta be first. of "debug": incl(conf.globalOptions, optIdeDebug) - of "v2": conf.suggestVersion = 0 of "v1": conf.suggestVersion = 1 + of "v2": conf.suggestVersion = 0 + of "v3": conf.suggestVersion = 3 + of "v4": conf.suggestVersion = 4 + of "info": + case p.val.normalize + of "protocolver": + stdout.writeLine(HighestSuggestProtocolVersion) + quit 0 + of "nimver": + stdout.writeLine(system.NimVersion) + quit 0 + of "capabilities": + stdout.writeLine(Capabilities.toSeq.mapIt($it).join(" ")) + quit 0 + else: + processSwitch(pass, p, conf) + of "exceptioninlayhints": + case p.val.normalize + of "", "on": incl(conf.globalOptions, optIdeExceptionInlayHints) + of "off": excl(conf.globalOptions, optIdeExceptionInlayHints) + else: processSwitch(pass, p, conf) of "tester": gMode = mstdin gEmitEof = true @@ -604,6 +734,8 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = conf.suggestMaxResults = parseInt(p.val) of "find": findProject = true + of "clientprocessid": + conf.clientProcessId = parseInt(p.val) else: processSwitch(pass, p, conf) of cmdArgument: let a = unixToNativePath(p.key) @@ -623,8 +755,7 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = let self = NimProg( suggestMode: true, - processCmdLine: processCmdLine, - mainCommand: mainCommand + processCmdLine: processCmdLine ) self.initDefinesProg(conf, "nimsuggest") @@ -636,20 +767,520 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = if gMode != mstdin: conf.writelnHook = proc (msg: string) = discard - # Find Nim's prefix dir. - let binaryPath = findExe("nim") - if binaryPath == "": - raise newException(IOError, - "Cannot find Nim standard library: Nim compiler not in PATH") - conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir() - if not dirExists(conf.prefixDir / RelativeDir"lib"): - conf.prefixDir = AbsoluteDir"" - + conf.prefixDir = conf.getPrefixDir() #msgs.writelnHook = proc (line: string) = log(line) myLog("START " & conf.projectFull.string) - discard self.loadConfigsAndRunMainCommand(cache, conf) + var graph = newModuleGraph(cache, conf) + if self.loadConfigsAndProcessCmdLine(cache, conf, graph): + + if conf.selectedGC == gcUnselected and + conf.backend != backendJs: + initOrcDefines(conf) + mainCommand(graph) + +# v3 start + +proc recompilePartially(graph: ModuleGraph, projectFileIdx = InvalidFileIdx) = + if projectFileIdx == InvalidFileIdx: + myLog "Recompiling partially from root" + else: + myLog fmt "Recompiling partially starting from {graph.getModule(projectFileIdx)}" + + # inst caches are breaking incremental compilation when the cache caches stuff + # from dirty buffer + graph.clearInstCache(projectFileIdx) + + GC_fullCollect() + + try: + benchmark "Recompilation": + graph.compileProject(projectFileIdx) + except Exception as e: + myLog fmt "Failed to recompile partially with the following error:\n {e.msg} \n\n {e.getStackTrace()}" + try: + graph.recompileFullProject() + except Exception as e: + myLog fmt "Failed clean recompilation:\n {e.msg} \n\n {e.getStackTrace()}" + +func deduplicateSymInfoPair[SymInfoPair](xs: seq[SymInfoPair]): seq[SymInfoPair] = + # 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 + # for the same symbol (e. g. including/excluding the pragma) + result = newSeqOfCap[SymInfoPair](xs.len) + for itm in xs.reversed: + var found = false + for res in result: + if res.info.exactEquals(itm.info): + found = true + break + if not found: + result.add(itm) + result.reverse() + +func deduplicateSymInfoPair(xs: SuggestFileSymbolDatabase): 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 + # for the same symbol (e. g. including/excluding the pragma) + result = SuggestFileSymbolDatabase( + lineInfo: newSeqOfCap[TinyLineInfo](xs.lineInfo.len), + sym: newSeqOfCap[PSym](xs.sym.len), + isDecl: newPackedBoolArray(), + caughtExceptions: newSeqOfCap[seq[PType]](xs.caughtExceptions.len), + caughtExceptionsSet: newPackedBoolArray(), + fileIndex: xs.fileIndex, + trackCaughtExceptions: xs.trackCaughtExceptions, + isSorted: false + ) + var i = xs.lineInfo.high + while i >= 0: + let itm = xs.lineInfo[i] + var found = false + for res in result.lineInfo: + if res.exactEquals(itm): + found = true + break + if not found: + result.add(xs.getSymInfoPair(i)) + dec i + result.reverse() + +proc findSymData(graph: ModuleGraph, trackPos: TLineInfo): + ref SymInfoPair = + let db = graph.fileSymbols(trackPos.fileIndex).deduplicateSymInfoPair + 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): + var res = db.getSymInfoPair(i) + new(result) + result[] = res + break + +func isInRange*(current, startPos, endPos: TinyLineInfo, tokenLen: int): bool = + result = + (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]() + let db = graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair + 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): + 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 in {ideSug, ideCon}: + myLog fmt "{file} changed compared to last compilation" + graph.markDirty originalFileIdx + graph.markClientsDirty originalFileIdx + else: + myLog fmt "No changes in file {file} compared to last compilation" + +proc suggestResult(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 + let suggest = symToSuggest(graph, sym, isLocal=false, section, + info, 100, PrefixMatch.None, false, 0, + endLine = endLine, endCol = endCol) + suggestResult(graph.config, suggest) + +proc suggestInlayHintResultType(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 = suggestToSuggestInlayTypeHint(suggestDef) + suggestDef.section = ideInlayHints + if sym.kind == skForVar: + suggestDef.inlayHintInfo.allowInsert = false + suggestResult(graph.config, suggestDef) + +proc suggestInlayHintResultException(graph: ModuleGraph, sym: PSym, info: TLineInfo, + defaultSection = ideNone, caughtExceptions: seq[PType], caughtExceptionsSet: bool, endLine: uint16 = 0, endCol = 0) = + if not caughtExceptionsSet: + return + + if sym.kind == skParam and sfEffectsDelayed in sym.flags: + return + + var raisesList: seq[PType] = @[getEbase(graph, info)] + + let t = sym.typ + if not isNil(t) and not isNil(t.n) and t.n.len > 0 and t.n[0].len > exceptionEffects: + let effects = t.n[0] + 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) + + var propagatedExceptionList: seq[PType] = @[] + for re in raisesList: + var exceptionIsPropagated = true + for ce in caughtExceptions: + if isNil(ce) or safeInheritanceDiff(re, ce) <= 0: + exceptionIsPropagated = false + break + if exceptionIsPropagated: + propagatedExceptionList.add(re) + + if propagatedExceptionList.len == 0: + return + + 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 = suggestToSuggestInlayExceptionHintLeft(suggestDef, propagatedExceptionList) + suggestDef.section = ideInlayHints + suggestResult(graph.config, suggestDef) + suggestDef.inlayHintInfo = suggestToSuggestInlayExceptionHintRight(suggestDef, propagatedExceptionList) + suggestResult(graph.config, suggestDef) + +const + # kinds for ideOutline and ideGlobalSymbols + searchableSymKinds = {skField, skEnumField, skIterator, skMethod, skFunc, skProc, skConverter, skTemplate} + +proc symbolEqual(left, right: PSym): bool = + # More relaxed symbol comparison + return left.info.exactEquals(right.info) and left.name == right.name + +proc findDef(n: PNode, line: uint16, col: int16): PNode = + if n.kind in {nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkMacroDef}: + if n.info.line == line: + return n + else: + for i in 0 ..< safeLen(n): + let res = findDef(n[i], line, col) + if res != nil: return res + +proc findByTLineInfo(trackPos: TLineInfo, infoPairs: SuggestFileSymbolDatabase): + ref SymInfoPair = + result = nil + if infoPairs.fileIndex == trackPos.fileIndex: + for i in infoPairs.lineInfo.low..infoPairs.lineInfo.high: + let s = infoPairs.getSymInfoPair(i) + if s.info.exactEquals trackPos: + new(result) + result[] = s + break + +proc outlineNode(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: SuggestFileSymbolDatabase): bool = + proc checkSymbol(sym: PSym, info: TLineInfo): bool = + result = (sym.owner.kind in {skModule, skType} or sym.kind in {skProc, skMethod, skIterator, skTemplate, skType}) + + if n.kind == nkSym and n.sym.checkSymbol(n.info): + graph.suggestResult(n.sym, n.sym.info, ideOutline, endInfo.line, endInfo.col) + return true + elif n.kind == nkIdent: + let symData = findByTLineInfo(n.info, infoPairs) + if symData != nil and symData.sym.checkSymbol(symData.info): + let sym = symData.sym + graph.suggestResult(sym, sym.info, ideOutline, endInfo.line, endInfo.col) + return true + +proc handleIdentOrSym(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: SuggestFileSymbolDatabase): bool = + for child in n: + if child.kind in {nkIdent, nkSym}: + if graph.outlineNode(child, endInfo, infoPairs): + return true + elif child.kind == nkPostfix: + if graph.handleIdentOrSym(child, endInfo, infoPairs): + return true + +proc iterateOutlineNodes(graph: ModuleGraph, n: PNode, infoPairs: SuggestFileSymbolDatabase) = + var matched = true + if n.kind == nkIdent: + let symData = findByTLineInfo(n.info, infoPairs) + if symData != nil and symData.sym.kind == skEnumField and symData.info.exactEquals(symData.sym.info): + let sym = symData.sym + graph.suggestResult(sym, sym.info, ideOutline, n.endInfo.line, n.endInfo.col) + elif (n.kind in {nkFuncDef, nkProcDef, nkTypeDef, nkMacroDef, nkTemplateDef, nkConverterDef, nkEnumFieldDef, nkConstDef}): + matched = handleIdentOrSym(graph, n, n.endInfo, infoPairs) + else: + matched = false + + if n.kind != nkFormalParams: + for child in n: + graph.iterateOutlineNodes(child, infoPairs) + +proc calculateExpandRange(n: PNode, info: TLineInfo): TLineInfo = + if ((n.kind in {nkFuncDef, nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkConverterDef} and + n.info.exactEquals(info)) or + (n.kind in {nkCall, nkCommand} and n[0].info.exactEquals(info))): + result = n.endInfo + else: + for child in n: + result = child.calculateExpandRange(info) + if result != unknownLineInfo: + return result + result = unknownLineInfo + +proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string, + graph: ModuleGraph) = + let conf = graph.config + conf.writelnHook = proc (s: string) = discard + conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; + msg: string; sev: Severity) = + let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info), + line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev) + graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest + + conf.ideCmd = cmd + + myLog fmt "cmd: {cmd}, file: {file}[{line}:{col}], dirtyFile: {dirtyfile}, tag: {tag}" + + var fileIndex: FileIndex + + if not (cmd in {ideRecompile, ideGlobalSymbols}): + fileIndex = fileInfoIdx(conf, file) + msgs.setDirtyFile( + conf, + fileIndex, + if dirtyfile.isEmpty: AbsoluteFile"" else: dirtyfile) + + if not dirtyfile.isEmpty: + graph.markDirtyIfNeeded(dirtyFile.string, fileInfoIdx(conf, file)) + + # these commands require fully compiled project + 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 + # longer reference to a particular module. E. g. A depends on B, B is marked + # as dirty and A loses B import. + graph.unmarkAllDirty() + + # these commands require partially compiled project + elif cmd in {ideSug, ideCon, ideOutline, ideHighlight, ideDef, ideChkFile, ideType, ideDeclaration, ideExpand} and + (graph.needsCompilation(fileIndex) or cmd in {ideSug, ideCon}): + # for ideSug use v2 implementation + if cmd in {ideSug, ideCon}: + conf.m.trackPos = newLineInfo(fileIndex, line, col) + conf.m.trackPosAttached = false + else: + conf.m.trackPos = default(TLineInfo) + graph.recompilePartially(fileIndex) + + case cmd + of ideDef: + let s = graph.findSymData(file, line, col) + if not s.isNil: + graph.suggestResult(s.sym, s.sym.info) + of ideType: + let s = graph.findSymData(file, line, col) + if not s.isNil: + let typeSym = s.sym.typ.sym + if typeSym != nil: + graph.suggestResult(typeSym, typeSym.info, ideType) + elif s.sym.typ.len != 0: + let genericType = s.sym.typ[0].sym + graph.suggestResult(genericType, genericType.info, ideType) + of ideUse, ideDus: + let symbol = graph.findSymData(file, line, col) + if not symbol.isNil: + var res: seq[SymInfoPair] = @[] + for s in graph.suggestSymbolsIter: + if s.sym.symbolEqual(symbol.sym): + res.add(s) + for s in res.deduplicateSymInfoPair(): + graph.suggestResult(s.sym, s.info) + of ideHighlight: + let sym = graph.findSymData(file, line, col) + if not sym.isNil: + let fs = graph.fileSymbols(fileIndex) + var usages: seq[SymInfoPair] = @[] + for i in fs.lineInfo.low..fs.lineInfo.high: + if fs.sym[i] == sym.sym: + usages.add(fs.getSymInfoPair(i)) + myLog fmt "Found {usages.len} usages in {file.string}" + for s in usages: + graph.suggestResult(s.sym, s.info) + of ideRecompile: + graph.recompileFullProject() + of ideChanged: + graph.markDirtyIfNeeded(file.string, fileIndex) + of ideSug, ideCon: + # ideSug/ideCon performs partial build of the file, thus mark it dirty for the + # future calls. + graph.markDirtyIfNeeded(file.string, fileIndex) + graph.recompilePartially(fileIndex) + let m = graph.getModule fileIndex + incl m.flags, sfDirty + of ideOutline: + let n = parseFile(fileIndex, graph.cache, graph.config) + graph.iterateOutlineNodes(n, graph.fileSymbols(fileIndex).deduplicateSymInfoPair) + of ideChk: + myLog fmt "Reporting errors for {graph.suggestErrors.len} file(s)" + for sug in graph.suggestErrorsIter: + suggestResult(graph.config, sug) + of ideChkFile: + let errors = graph.suggestErrors.getOrDefault(fileIndex, @[]) + myLog fmt "Reporting {errors.len} error(s) for {file.string}" + for error in errors: + suggestResult(graph.config, error) + of ideGlobalSymbols: + var + counter = 0 + res: seq[SymInfoPair] = @[] + + for s in graph.suggestSymbolsIter: + if (sfGlobal in s.sym.flags or s.sym.kind in searchableSymKinds) and + s.sym.info == s.info: + if contains(s.sym.name.s, file.string): + inc counter + res = res.filterIt(not it.info.exactEquals(s.info)) + res.add s + # stop after first 1000 matches... + if counter > 1000: + break + + # ... then sort them by weight ... + res.sort() do (left, right: SymInfoPair) -> int: + let + leftString = left.sym.name.s + rightString = right.sym.name.s + leftIndex = leftString.find(file.string) + rightIndex = rightString.find(file.string) + + if leftIndex == rightIndex: + result = cmp(toLowerAscii(leftString), + toLowerAscii(rightString)) + else: + result = cmp(leftIndex, rightIndex) + + # ... and send first 100 results + if res.len > 0: + for i in 0 .. min(100, res.len - 1): + let s = res[i] + graph.suggestResult(s.sym, s.info) + + of ideDeclaration: + let s = graph.findSymData(file, line, col) + if not s.isNil: + # find first mention of the symbol in the file containing the definition. + # It is either the definition or the declaration. + var first: SymInfoPair + let db = graph.fileSymbols(s.sym.info.fileIndex).deduplicateSymInfoPair + for i in db.lineInfo.low..db.lineInfo.high: + if s.sym.symbolEqual(db.sym[i]): + first = db.getSymInfoPair(i) + break + + if s.info.exactEquals(first.info): + # we are on declaration, go to definition + graph.suggestResult(first.sym, first.sym.info, ideDeclaration) + else: + # we are on definition or usage, look for declaration + graph.suggestResult(first.sym, first.info, ideDeclaration) + of ideExpand: + var level: int = high(int) + let index = skipWhitespace(tag, 0); + let trimmed = substr(tag, index) + if not (trimmed == "" or trimmed == "all"): + discard parseInt(trimmed, level, 0) + + conf.expandPosition = newLineInfo(fileIndex, line, col) + conf.expandLevels = level + conf.expandProgress = false + conf.expandNodeResult = "" + + graph.markDirty fileIndex + graph.markClientsDirty fileIndex + graph.recompilePartially() + var suggest = Suggest() + suggest.section = ideExpand + suggest.version = 3 + suggest.line = line + suggest.column = col + suggest.doc = graph.config.expandNodeResult + if suggest.doc != "": + let + n = parseFile(fileIndex, graph.cache, graph.config) + endInfo = n.calculateExpandRange(conf.expandPosition) + + suggest.endLine = endInfo.line + suggest.endCol = endInfo.col + + suggestResult(graph.config, suggest) + + 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) + i += skipWhile(tag, seps, i) + var typeHints = true + var exceptionHints = false + while i <= tag.high: + var token: string + i += parseUntil(tag, token, seps, i) + i += skipWhile(tag, seps, i) + case token: + of "+typeHints": + typeHints = true + of "-typeHints": + typeHints = false + of "+exceptionHints": + exceptionHints = true + of "-exceptionHints": + exceptionHints = false + 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) + else: + myLog fmt "Discarding {cmd}" +# v3 end when isMainModule: handleCmdLine(newIdentCache(), newConfigRef()) else: @@ -666,10 +1297,12 @@ else: proc mockCommand(graph: ModuleGraph) = retval = graph let conf = graph.config + conf.setCmd cmdIdeTools + defineSymbol(conf.symbols, $conf.backend) clearPasses(graph) registerPass graph, verbosePass registerPass graph, semPass - conf.cmd = cmdIdeTools + wantMainModule(conf) if not fileExists(conf.projectFull): @@ -677,8 +1310,7 @@ else: add(conf.searchPaths, conf.libpath) - # do not stop after the first error: - conf.errorMax = high(int) + conf.setErrorMaxHighMaybe # do not print errors, but log them conf.writelnHook = myLog conf.structuredErrorHook = nil @@ -703,8 +1335,7 @@ else: conf = newConfigRef() self = NimProg( suggestMode: true, - processCmdLine: mockCmdLine, - mainCommand: mockCommand + processCmdLine: mockCmdLine ) self.initDefinesProg(conf, "nimsuggest") @@ -714,23 +1345,20 @@ else: conf.writelnHook = proc (msg: string) = discard # Find Nim's prefix dir. if nimPath == "": - let binaryPath = findExe("nim") - if binaryPath == "": - raise newException(IOError, - "Cannot find Nim standard library: Nim compiler not in PATH") - conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir() - if not dirExists(conf.prefixDir / RelativeDir"lib"): - conf.prefixDir = AbsoluteDir"" + conf.prefixDir = conf.getPrefixDir() else: conf.prefixDir = AbsoluteDir nimPath #msgs.writelnHook = proc (line: string) = log(line) myLog("START " & conf.projectFull.string) - discard self.loadConfigsAndRunMainCommand(cache, conf) + var graph = newModuleGraph(cache, conf) + if self.loadConfigsAndProcessCmdLine(cache, conf, graph): + mockCommand(graph) if gLogging: + myLog("Search paths:") for it in conf.searchPaths: - log(it.string) + myLog(" " & it.string) retval.doStopCompile = proc (): bool = false return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[]) diff --git a/nimsuggest/nimsuggest.nimble b/nimsuggest/nimsuggest.nimble index 3651e12bd..b3790e116 100644 --- a/nimsuggest/nimsuggest.nimble +++ b/nimsuggest/nimsuggest.nimble @@ -1,11 +1,8 @@ -[Package] -name = "nimsuggest" -version = "0.1.0" -author = "Andreas Rumpf" -description = "Tool for providing auto completion data for Nim source code." -license = "MIT" +include "../lib/system/compilation.nim" +version = $NimMajor & "." & $NimMinor & "." & $NimPatch +author = "Andreas Rumpf" +description = "Tool for providing auto completion data for Nim source code." +license = "MIT" +bin = @["nimsuggest"] -bin = "nimsuggest" - -[Deps] -Requires: "nim >= 0.11.2, compiler#head" +requires "compiler >= 1.9.0" , "checksums" diff --git a/nimsuggest/procmonitor.nim b/nimsuggest/procmonitor.nim new file mode 100644 index 000000000..0f1ba1e0d --- /dev/null +++ b/nimsuggest/procmonitor.nim @@ -0,0 +1,34 @@ +# Monitor a client process and shutdown the current process, if the client +# process is found to be dead + +import os + +when defined(posix): + import posix_utils + import posix + +when defined(windows): + import winlean + +when defined(posix): + proc monitorClientProcessIdThreadProc(pid: int) {.thread.} = + while true: + sleep(1000) + try: + sendSignal(Pid(pid), 0) + except: + discard kill(Pid(getCurrentProcessId()), cint(SIGTERM)) + +when defined(windows): + proc monitorClientProcessIdThreadProc(pid: int) {.thread.} = + var process = openProcess(SYNCHRONIZE, 0, DWORD(pid)) + if process != 0: + discard waitForSingleObject(process, INFINITE) + discard closeHandle(process) + quit(0) + +var tid: Thread[int] + +proc hookProcMonitor*(pid: int) = + when defined(posix) or defined(windows): + createThread(tid, monitorClientProcessIdThreadProc, pid) diff --git a/nimsuggest/sexp.nim b/nimsuggest/sexp.nim index 654640309..03369ccb7 100644 --- a/nimsuggest/sexp.nim +++ b/nimsuggest/sexp.nim @@ -12,6 +12,11 @@ import hashes, strutils, lexbase, streams, unicode, macros +import std/private/decode_helpers + +when defined(nimPreviewSlimSystem): + import std/[assertions, formatfloat] + type SexpEventKind* = enum ## enumeration of all events that may occur when parsing sexpError, ## an error occurred during parsing @@ -113,14 +118,6 @@ proc errorMsgExpected*(my: SexpParser, e: string): string = ## other error messages result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"] -proc handleHexChar(c: char, x: var int): bool = - result = true # Success - case c - of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) - of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) - of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) - else: result = false # error - proc parseString(my: var SexpParser): TTokKind = result = tkString var pos = my.bufpos + 1 @@ -290,41 +287,33 @@ proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} = ## raises an `ESexpParsingError` exception. raise newException(SexpParsingError, errorMsgExpected(p, msg)) -proc newSString*(s: string): SexpNode {.procvar.}= +proc newSString*(s: string): SexpNode = ## Creates a new `SString SexpNode`. result = SexpNode(kind: SString, str: s) -proc newSStringMove(s: string): SexpNode = - result = SexpNode(kind: SString) - shallowCopy(result.str, s) - -proc newSInt*(n: BiggestInt): SexpNode {.procvar.} = +proc newSInt*(n: BiggestInt): SexpNode = ## Creates a new `SInt SexpNode`. result = SexpNode(kind: SInt, num: n) -proc newSFloat*(n: float): SexpNode {.procvar.} = +proc newSFloat*(n: float): SexpNode = ## Creates a new `SFloat SexpNode`. result = SexpNode(kind: SFloat, fnum: n) -proc newSNil*(): SexpNode {.procvar.} = +proc newSNil*(): SexpNode = ## Creates a new `SNil SexpNode`. result = SexpNode(kind: SNil) -proc newSCons*(car, cdr: SexpNode): SexpNode {.procvar.} = +proc newSCons*(car, cdr: SexpNode): SexpNode = ## Creates a new `SCons SexpNode` result = SexpNode(kind: SCons, car: car, cdr: cdr) -proc newSList*(): SexpNode {.procvar.} = +proc newSList*(): SexpNode = ## Creates a new `SList SexpNode` result = SexpNode(kind: SList, elems: @[]) -proc newSSymbol*(s: string): SexpNode {.procvar.} = +proc newSSymbol*(s: string): SexpNode = result = SexpNode(kind: SSymbol, symbol: s) -proc newSSymbolMove(s: string): SexpNode = - result = SexpNode(kind: SSymbol) - shallowCopy(result.symbol, s) - proc getStr*(n: SexpNode, default: string = ""): string = ## Retrieves the string value of a `SString SexpNode`. ## @@ -415,7 +404,7 @@ macro convertSexp*(x: untyped): untyped = ## `%` for every element. result = toSexp(x) -proc `==`* (a,b: SexpNode): bool = +func `==`* (a, b: SexpNode): bool = ## Check two nodes for equality if a.isNil: if b.isNil: return true @@ -602,8 +591,7 @@ proc parseSexp(p: var SexpParser): SexpNode = case p.tok of tkString: # we capture 'p.a' here, so we need to give it a fresh buffer afterwards: - result = newSStringMove(p.a) - p.a = "" + result = SexpNode(kind: SString, str: move p.a) discard getTok(p) of tkInt: result = newSInt(parseBiggestInt(p.a)) @@ -615,8 +603,7 @@ proc parseSexp(p: var SexpParser): SexpNode = result = newSNil() discard getTok(p) of tkSymbol: - result = newSSymbolMove(p.a) - p.a = "" + result = SexpNode(kind: SSymbol, symbol: move p.a) discard getTok(p) of tkParensLe: result = newSList() diff --git a/nimsuggest/tester.nim b/nimsuggest/tester.nim index d0870347e..9b9488348 100644 --- a/nimsuggest/tester.nim +++ b/nimsuggest/tester.nim @@ -2,8 +2,11 @@ # Every test file can have a #[!]# comment that is deleted from the input # before 'nimsuggest' is invoked to ensure this token doesn't make a # crucial difference for Nim's parser. +# When debugging, to run a single test, use for e.g.: +# `nim r nimsuggest/tester.nim nimsuggest/tests/tsug_accquote.nim` import os, osproc, strutils, streams, re, sexp, net +from sequtils import toSeq type Test = object @@ -13,15 +16,17 @@ type disabled: bool const - curDir = when defined(windows): "" else: "" DummyEof = "!EOF!" + tpath = "nimsuggest/tests" + # we could also use `stdtest/specialpaths` -template tpath(): untyped = getAppDir() / "tests" +import std/compilesettings proc parseTest(filename: string; epcMode=false): Test = const cursorMarker = "#[!]#" - let nimsug = curDir & addFileExt("nimsuggest", ExeExt) - let libpath = findExe("nim").splitFile().dir /../ "lib" + let nimsug = "bin" / addFileExt("nimsuggest_testing", ExeExt) + doAssert nimsug.fileExists, nimsug + const libpath = querySetting(libPath) result.filename = filename result.dest = getTempDir() / extractFilename(filename) result.cmd = nimsug & " --tester " & result.dest @@ -61,7 +66,7 @@ proc parseTest(filename: string; epcMode=false): Test = elif x.startsWith(">"): # since 'markers' here are not complete yet, we do the $substitutions # afterwards - result.script.add((x.substr(1).replaceWord("$path", tpath()), "")) + result.script.add((x.substr(1).replaceWord("$path", tpath).replaceWord("$file", filename), "")) elif x.len > 0: # expected output line: let x = x % ["file", filename, "lib", libpath] @@ -102,7 +107,7 @@ proc parseCmd(c: string): seq[string] = proc edit(tmpfile: string; x: seq[string]) = if x.len != 3 and x.len != 4: quit "!edit takes two or three arguments" - let f = if x.len >= 4: tpath() / x[3] else: tmpfile + let f = if x.len >= 4: tpath / x[3] else: tmpfile try: let content = readFile(f) let newcontent = content.replace(x[1], x[2]) @@ -119,12 +124,12 @@ proc exec(x: seq[string]) = proc copy(x: seq[string]) = if x.len != 3: quit "!copy takes two arguments" - let rel = tpath() + let rel = tpath copyFile(rel / x[1], rel / x[2]) proc del(x: seq[string]) = if x.len != 2: quit "!del takes one argument" - removeFile(tpath() / x[1]) + removeFile(tpath / x[1]) proc runCmd(cmd, dest: string): bool = result = cmd[0] == '!' @@ -206,14 +211,19 @@ proc sexpToAnswer(s: SexpNode): string = result.add '\t' result.add file result.add '\t' - result.add line + result.addInt line result.add '\t' - result.add col + result.addInt col result.add '\t' result.add doc result.add '\t' - result.add a[8].getNum - if a.len >= 10: + result.addInt a[8].getNum + if a.len >= 11: + result.add '\t' + result.addInt a[9].getNum + result.add '\t' + result.addInt a[10].getNum + elif a.len >= 10: result.add '\t' result.add a[9].getStr result.add '\L' @@ -224,8 +234,8 @@ proc doReport(filename, answer, resp: string; report: var string) = var hasDiff = false for i in 0..min(resp.len-1, answer.len-1): if resp[i] != answer[i]: - report.add "\n Expected: " & resp.substr(i, i+200) - report.add "\n But got: " & answer.substr(i, i+200) + report.add "\n Expected:\n" & resp + report.add "\n But got:\n" & answer hasDiff = true break if not hasDiff: @@ -247,13 +257,15 @@ proc runEpcTest(filename: string): int = for cmd in s.startup: if not runCmd(cmd, s.dest): quit "invalid command: " & cmd - let epccmd = s.cmd.replace("--tester", "--epc --v2 --log") + let epccmd = if s.cmd.contains("--v3"): + s.cmd.replace("--tester", "--epc --log") + else: + s.cmd.replace("--tester", "--epc --v2 --log") let cl = parseCmdLine(epccmd) var p = startProcess(command=cl[0], args=cl[1 .. ^1], options={poStdErrToStdOut, poUsePath, poInteractive, poDaemon}) let outp = p.outputStream - let inp = p.inputStream var report = "" var socket = newSocket() try: @@ -267,17 +279,28 @@ proc runEpcTest(filename: string): int = os.sleep(50) inc i let a = outp.readAll().strip() - let port = parseInt(a) + var port: int + try: + port = parseInt(a) + except ValueError: + echo "Error parsing port number: " & a + echo outp.readAll() + quit 1 socket.connect("localhost", Port(port)) + for req, resp in items(s.script): if not runCmd(req, s.dest): socket.sendEpcStr(req) let sx = parseSexp(socket.recvEpc()) if not req.startsWith("mod "): - let answer = sexpToAnswer(sx) + let answer = if sx[2].kind == SNil: "" else: sexpToAnswer(sx) doReport(filename, answer, resp, report) - finally: + socket.sendEpcStr "return arg" + # bugfix: this was in `finally` block, causing the original error to be + # potentially masked by another one in case `socket.sendEpcStr` raises + # (e.g. if socket couldn't connect in the 1st place) + finally: close(p) if report.len > 0: echo "==== EPC ========================================" @@ -313,8 +336,12 @@ proc runTest(filename: string): int = answer.add '\L' doReport(filename, answer, resp, report) finally: - inp.writeLine("quit") - inp.flush() + try: + inp.writeLine("quit") + inp.flush() + except IOError, OSError: + # assume it's SIGPIPE, ie, the child already died + discard close(p) if report.len > 0: echo "==== STDIN ======================================" @@ -326,11 +353,16 @@ proc main() = if os.paramCount() > 0: let x = os.paramStr(1) let xx = expandFilename x + # run only stdio when running single test failures += runTest(xx) - failures += runEpcTest(xx) else: - for x in walkFiles(getAppDir() / "tests/t*.nim"): - echo "Test ", x + let files = toSeq(walkFiles(tpath / "t*.nim")) + for i, x in files: + echo "$#/$# test: $#" % [$i, $files.len, x] + when defined(i386): + if x == "nimsuggest/tests/tmacro_highlight.nim": + echo "skipping" # workaround bug #17945 + continue let xx = expandFilename x when not defined(windows): # XXX Windows IO redirection seems bonkers: diff --git a/nimsuggest/tests/fixtures/mclass_macro.nim b/nimsuggest/tests/fixtures/mclass_macro.nim new file mode 100644 index 000000000..cfca0bf3f --- /dev/null +++ b/nimsuggest/tests/fixtures/mclass_macro.nim @@ -0,0 +1,164 @@ + +import macros + +macro class*(head, body: untyped): untyped = + # The macro is immediate, since all its parameters are untyped. + # This means, it doesn't resolve identifiers passed to it. + + var typeName, baseName: NimNode + + # flag if object should be exported + var exported: bool + + if head.kind == nnkInfix and head[0].kind == nnkIdent and $head[0] == "of": + # `head` is expression `typeName of baseClass` + # echo head.treeRepr + # -------------------- + # Infix + # Ident !"of" + # Ident !"Animal" + # Ident !"RootObj" + typeName = head[1] + baseName = head[2] + + elif head.kind == nnkInfix and head[0].kind == nnkIdent and + $head[0] == "*" and head[2].kind == nnkPrefix and + head[2][0].kind == nnkIdent and $head[2][0] == "of": + # `head` is expression `typeName* of baseClass` + # echo head.treeRepr + # -------------------- + # Infix + # Ident !"*" + # Ident !"Animal" + # Prefix + # Ident !"of" + # Ident !"RootObj" + typeName = head[1] + baseName = head[2][1] + exported = true + + else: + quit "Invalid node: " & head.lispRepr + + # The following prints out the AST structure: + # + # import macros + # dumptree: + # type X = ref object of Y + # z: int + # -------------------- + # StmtList + # TypeSection + # TypeDef + # Ident !"X" + # Empty + # RefTy + # ObjectTy + # Empty + # OfInherit + # Ident !"Y" + # RecList + # IdentDefs + # Ident !"z" + # Ident !"int" + # Empty + + # create a type section in the result + result = newNimNode(nnkStmtList) + result.add( + if exported: + # mark `typeName` with an asterisk + quote do: + type `typeName`* = ref object of `baseName` + else: + quote do: + type `typeName` = ref object of `baseName` + ) + + # echo treeRepr(body) + # -------------------- + # StmtList + # VarSection + # IdentDefs + # Ident !"name" + # Ident !"string" + # Empty + # IdentDefs + # Ident !"age" + # Ident !"int" + # Empty + # MethodDef + # Ident !"vocalize" + # Empty + # Empty + # FormalParams + # Ident !"string" + # Empty + # Empty + # StmtList + # StrLit ... + # MethodDef + # Ident !"age_human_yrs" + # Empty + # Empty + # FormalParams + # Ident !"int" + # Empty + # Empty + # StmtList + # DotExpr + # Ident !"this" + # Ident !"age" + + # var declarations will be turned into object fields + var recList = newNimNode(nnkRecList) + + # expected name of constructor + let ctorName = newIdentNode("new" & $typeName) + + # Iterate over the statements, adding `this: T` + # to the parameters of functions, unless the + # function is a constructor + for node in body.children: + case node.kind: + + of nnkMethodDef, nnkProcDef: + # check if it is the ctor proc + if node.name.kind != nnkAccQuoted and node.name.basename == ctorName: + # specify the return type of the ctor proc + node.params[0] = typeName + else: + # inject `self: T` into the arguments + node.params.insert(1, newIdentDefs(ident("self"), typeName)) + result.add(node) + + of nnkVarSection: + # variables get turned into fields of the type. + for n in node.children: + recList.add(n) + + else: + result.add(node) + + # Inspect the tree structure: + # + # echo result.treeRepr + # -------------------- + # StmtList + # TypeSection + # TypeDef + # Ident !"Animal" + # Empty + # RefTy + # ObjectTy + # Empty + # OfInherit + # Ident !"RootObj" + # Empty <= We want to replace this + # MethodDef + # ... + + result[0][0][2][0][2] = recList + + # Lets inspect the human-readable version of the output + #echo repr(result) diff --git a/nimsuggest/tests/dep_v1.nim b/nimsuggest/tests/fixtures/mdep_v1.nim index eae230e85..eae230e85 100644 --- a/nimsuggest/tests/dep_v1.nim +++ b/nimsuggest/tests/fixtures/mdep_v1.nim diff --git a/nimsuggest/tests/dep_v2.nim b/nimsuggest/tests/fixtures/mdep_v2.nim index ab39721c4..ab39721c4 100644 --- a/nimsuggest/tests/dep_v2.nim +++ b/nimsuggest/tests/fixtures/mdep_v2.nim diff --git a/nimsuggest/tests/fixtures/mfakeassert.nim b/nimsuggest/tests/fixtures/mfakeassert.nim new file mode 100644 index 000000000..765831ba7 --- /dev/null +++ b/nimsuggest/tests/fixtures/mfakeassert.nim @@ -0,0 +1,5 @@ +# Template for testing defs + +template fakeAssert*(cond: untyped, msg: string = "") = + ## template to allow def lookup testing + if not cond: quit(1) diff --git a/nimsuggest/tests/fixtures/minclude_import.nim b/nimsuggest/tests/fixtures/minclude_import.nim new file mode 100644 index 000000000..5fa9e5142 --- /dev/null +++ b/nimsuggest/tests/fixtures/minclude_import.nim @@ -0,0 +1,15 @@ +# Creates an awkward set of dependencies between this, import, and include. +# This pattern appears in the compiler, compiler/(sem|ast|semexprs).nim. + +import mfakeassert +import minclude_types + +proc say*(g: Greet): string = + fakeAssert(true, "always works") + g.greeting & ", " & g.subject & "!" + +include minclude_include + +proc say*(): string = + fakeAssert(1 + 1 == 2, "math works") + say(create()) diff --git a/nimsuggest/tests/fixtures/minclude_include.nim b/nimsuggest/tests/fixtures/minclude_include.nim new file mode 100644 index 000000000..23f9892cc --- /dev/null +++ b/nimsuggest/tests/fixtures/minclude_include.nim @@ -0,0 +1,4 @@ +# this file is included and relies on imports within the include + +proc create*(greeting: string = "Hello", subject: string = "World"): Greet = + Greet(greeting: greeting, subject: subject) diff --git a/nimsuggest/tests/fixtures/minclude_types.nim b/nimsuggest/tests/fixtures/minclude_types.nim new file mode 100644 index 000000000..3e85ee540 --- /dev/null +++ b/nimsuggest/tests/fixtures/minclude_types.nim @@ -0,0 +1,6 @@ +# types used by minclude_* (import or include), to find with def in include + +type + Greet* = object + greeting*: string + subject*: string \ No newline at end of file diff --git a/nimsuggest/tests/fixtures/mstrutils.nim b/nimsuggest/tests/fixtures/mstrutils.nim new file mode 100644 index 000000000..d6f25571b --- /dev/null +++ b/nimsuggest/tests/fixtures/mstrutils.nim @@ -0,0 +1,19 @@ +import mfakeassert + +func rereplace*(s, sub: string; by: string = ""): string {.used.} = + ## competes for priority in suggestion, here first, but never used in test + + fakeAssert(true, "always works") + result = by + +func replace*(s, sub: string; by: string = ""): string = + ## this is a test version of strutils.replace, it simply returns `by` + + fakeAssert("".len == 0, "empty string is empty") + result = by + +func rerereplace*(s, sub: string; by: string = ""): string {.used.} = + ## isn't used and appears last, lowest priority + + fakeAssert(false, "never works") + result = by diff --git a/nimsuggest/tests/module_20265.nim b/nimsuggest/tests/module_20265.nim new file mode 100644 index 000000000..24b7d10c9 --- /dev/null +++ b/nimsuggest/tests/module_20265.nim @@ -0,0 +1,6 @@ +type A* = tuple + a: int + b: int + +var x*: A = (a: 2, b: 10) +var y* = (a: 2, b: 10) diff --git a/nimsuggest/tests/t20265_1.nim b/nimsuggest/tests/t20265_1.nim new file mode 100644 index 000000000..553b3d545 --- /dev/null +++ b/nimsuggest/tests/t20265_1.nim @@ -0,0 +1,8 @@ +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skField;;a;;int;;*module_20265.nim;;6;;10;;"";;100;;None +sug;;skField;;b;;int;;*module_20265.nim;;6;;16;;"";;100;;None +""" +import module_20265 +y.#[!]# diff --git a/nimsuggest/tests/t20265_2.nim b/nimsuggest/tests/t20265_2.nim new file mode 100644 index 000000000..33edf2d9a --- /dev/null +++ b/nimsuggest/tests/t20265_2.nim @@ -0,0 +1,8 @@ +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skField;;a;;int;;*module_20265.nim;;2;;2;;"";;100;;None +sug;;skField;;b;;int;;*module_20265.nim;;3;;2;;"";;100;;None +""" +import module_20265 +x.#[!]# diff --git a/nimsuggest/tests/t20440.nim b/nimsuggest/tests/t20440.nim new file mode 100644 index 000000000..0456aa074 --- /dev/null +++ b/nimsuggest/tests/t20440.nim @@ -0,0 +1,7 @@ +when not defined(js): + {.fatal: "Crash".} +echo 4 + +discard """ +$nimsuggest --v3 --tester $file +""" diff --git a/nimsuggest/tests/t20440.nims b/nimsuggest/tests/t20440.nims new file mode 100644 index 000000000..1336be3d4 --- /dev/null +++ b/nimsuggest/tests/t20440.nims @@ -0,0 +1 @@ +switch("backend", "js") diff --git a/nimsuggest/tests/t21185.nim b/nimsuggest/tests/t21185.nim new file mode 100644 index 000000000..bf5a0e3cc --- /dev/null +++ b/nimsuggest/tests/t21185.nim @@ -0,0 +1,18 @@ + +# Reduced case of 21185. Issue was first parameter being static +proc foo(x: static[int]) = discard + +type + Person = object + name: string + age: int + +let p = Person() +p.#[!]# + +discard """ +$nimsuggest --tester --v3 --maxresults:2 $file +>sug $1 +sug;;skField;;age;;int;;$file;;8;;4;;"";;100;;None +sug;;skField;;name;;string;;$file;;7;;4;;"";;100;;None +""" diff --git a/nimsuggest/tests/t22448.nim b/nimsuggest/tests/t22448.nim new file mode 100644 index 000000000..8664bbbc3 --- /dev/null +++ b/nimsuggest/tests/t22448.nim @@ -0,0 +1,11 @@ +proc fn(a: static float) = discard +proc fn(a: int) = discard + +let x = 1 +fn(x) + +discard """ +$nimsuggest --tester --v3 $file +>chk $file +chk;;skUnknown;;;;Hint;;* +""" diff --git a/nimsuggest/tests/tarrowcrash.nim b/nimsuggest/tests/tarrowcrash.nim new file mode 100644 index 000000000..a303e88f5 --- /dev/null +++ b/nimsuggest/tests/tarrowcrash.nim @@ -0,0 +1,20 @@ +# issue #24179 + +import sugar + +type + Parser[T] = object + +proc eatWhile[T](p: Parser[T], predicate: T -> bool): seq[T] = + return @[] + +proc skipWs(p: Parser[char]) = + discard p.eatWhile((c: char) => c == 'a') +#[!]# + +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tarrowcrash.nim [Processing]";;0 +chk;;skUnknown;;;;Hint;;$file;;11;;5;;"\'skipWs\' is declared but not used [XDeclaredButNotUsed]";;0 +""" diff --git a/nimsuggest/tests/tchk1.nim b/nimsuggest/tests/tchk1.nim index 2b60ed094..be6115c1c 100644 --- a/nimsuggest/tests/tchk1.nim +++ b/nimsuggest/tests/tchk1.nim @@ -15,14 +15,13 @@ proc main = #[!]# discard """ -disabled:true $nimsuggest --tester $file >chk $1 -chk;;skUnknown;;;;Hint;;???;;-1;;-1;;"tchk1 [Processing]";;0 -chk;;skUnknown;;;;Error;;$file;;12;;0;;"identifier expected, but found \'keyword template\'";;0 -chk;;skUnknown;;;;Error;;$file;;14;;0;;"complex statement requires indentation";;0 -chk;;skUnknown;;;;Error;;$file;;12;;0;;"implementation of \'foo\' expected";;0 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk1.nim [Processing]";;0 +chk;;skUnknown;;;;Error;;$file;;12;;0;;"identifier expected, but got \'keyword template\'";;0 +chk;;skUnknown;;;;Error;;$file;;14;;0;;"nestable statement requires indentation";;0 chk;;skUnknown;;;;Error;;$file;;17;;0;;"invalid indentation";;0 +chk;;skUnknown;;;;Error;;$file;;12;;0;;"implementation of \'foo\' expected";;0 chk;;skUnknown;;;;Hint;;$file;;12;;9;;"\'foo\' is declared but not used [XDeclaredButNotUsed]";;0 -chk;;skUnknown;;;;Hint;;$file;;14;;5;;"\'tchk1.main()[declared in tchk1.nim(14, 5)]\' is declared but not used [XDeclaredButNotUsed]";;0 +chk;;skUnknown;;;;Hint;;$file;;14;;5;;"\'main\' is declared but not used [XDeclaredButNotUsed]";;0 """ diff --git a/nimsuggest/tests/tchk2.nim b/nimsuggest/tests/tchk2.nim new file mode 100644 index 000000000..f5404368d --- /dev/null +++ b/nimsuggest/tests/tchk2.nim @@ -0,0 +1,35 @@ +# bug #22794 +type O = object + +proc `=destroy`(x: O) = discard +proc `=trace`(x: var O; env: pointer) = discard +proc `=copy`(a: var O; b: O) = discard +proc `=dup`(a: O): O {.nodestroy.} = a +proc `=sink`(a: var O; b: O) = discard + + +# bug #23316 +type SomeSturct = object + +proc `=destroy`(x: SomeSturct) = + echo "SomeSturct destroyed" + +# bug #23867 +type ObjStr = object + s: string + +let ostr = ObjStr() # <-- nimsuggest crashes +discard ostr + +type ObjSeq = object + s: seq[int] + +let oseq = ObjSeq() # <-- nimsuggest crashes +discard oseq + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk2.nim [Processing]";;0 +""" diff --git a/nimsuggest/tests/tchk_compiles.nim b/nimsuggest/tests/tchk_compiles.nim new file mode 100644 index 000000000..c8a3daac4 --- /dev/null +++ b/nimsuggest/tests/tchk_compiles.nim @@ -0,0 +1,8 @@ +discard compiles(2 + "hello") + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk_compiles.nim [Processing]";;0 +""" diff --git a/nimsuggest/tests/tcon1.nim b/nimsuggest/tests/tcon1.nim index 262dd5151..627e7f400 100644 --- a/nimsuggest/tests/tcon1.nim +++ b/nimsuggest/tests/tcon1.nim @@ -1,13 +1,43 @@ +## Test Invocation `con`text in various situations + +## various of this proc are used as the basis for these tests proc test(s: string; a: int) = discard + +## This overload should be used to ensure the lower airity `test` doesn't match +proc test(s: string; a: string, b: int) = discard + +## similar signature but different name to ensure `con` doesn't get greedy proc testB(a, b: string) = discard + +# with a param already specified test("hello here", #[!]#) + +# as first param testB(#[!]# +# dot expressions +"from behind".test(#[!]# + +# two params matched, so disqualify the lower airity `test` +# TODO: this doesn't work, because dot exprs, overloads, etc aren't currently +# handled by suggest.suggestCall. sigmatch.partialMatch by way of +# sigmatch.matchesAux. Doesn't use the operand before the dot as part of +# the formal parameters. Changing this is tricky because it's used by +# the proper compilation sem pass and that's a big change all in one go. +"and again".test("more", #[!]# + discard """ $nimsuggest --tester $file >con $1 -con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;1;;5;;"";;100 +con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100 +con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100 >con $2 -con;;skProc;;tcon1.testB;;proc (a: string, b: string);;$file;;2;;5;;"";;100 +con;;skProc;;tcon1.testB;;proc (a: string, b: string);;$file;;10;;5;;"";;100 +>con $3 +con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100 +con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100 +>con $4 +con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100 +con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100 """ diff --git a/nimsuggest/tests/tconcept1.nim b/nimsuggest/tests/tconcept1.nim new file mode 100644 index 000000000..d81cd8120 --- /dev/null +++ b/nimsuggest/tests/tconcept1.nim @@ -0,0 +1,12 @@ +SomeNumber = concept + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tconcept1.nim [Processing]";;0 +chk;;skUnknown;;;;Error;;$file;;1;;13;;"the \'concept\' keyword is only valid in \'type\' sections";;0 +chk;;skUnknown;;;;Error;;$file;;1;;13;;"invalid indentation";;0 +chk;;skUnknown;;;;Error;;$file;;1;;13;;"expression expected, but found \'keyword concept\'";;0 +chk;;skUnknown;;;;Error;;$file;;1;;0;;"\'SomeNumber\' cannot be assigned to";;0 +""" diff --git a/nimsuggest/tests/tconcept2.nim b/nimsuggest/tests/tconcept2.nim new file mode 100644 index 000000000..7f7d147f5 --- /dev/null +++ b/nimsuggest/tests/tconcept2.nim @@ -0,0 +1,15 @@ + SomeNumber = concept a, type T + a.int is int + int.to(T) is type(a) + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tconcept2.nim [Processing]";;0 +chk;;skUnknown;;;;Error;;$file;;1;;2;;"invalid indentation";;0 +chk;;skUnknown;;;;Error;;$file;;1;;15;;"the \'concept\' keyword is only valid in \'type\' sections";;0 +chk;;skUnknown;;;;Error;;$file;;1;;15;;"invalid indentation";;0 +chk;;skUnknown;;;;Error;;$file;;1;;15;;"expression expected, but found \'keyword concept\'";;0 +chk;;skUnknown;;;;Error;;$file;;1;;2;;"\'SomeNumber\' cannot be assigned to";;0 +""" diff --git a/nimsuggest/tests/tdef1.nim b/nimsuggest/tests/tdef1.nim index 2cd040ea1..49265bbc1 100644 --- a/nimsuggest/tests/tdef1.nim +++ b/nimsuggest/tests/tdef1.nim @@ -1,12 +1,14 @@ discard """ $nimsuggest --tester $file >def $1 -def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"Return hello";;100 ->def $1 -def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"Return hello";;100 +def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100 +>def $2 +def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100 +>def $2 +def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100 """ -proc hello(): string = +proc hel#[!]#lo(): string = ## Return hello "Hello" diff --git a/nimsuggest/tests/tdef2.nim b/nimsuggest/tests/tdef2.nim new file mode 100644 index 000000000..299b83a3d --- /dev/null +++ b/nimsuggest/tests/tdef2.nim @@ -0,0 +1,13 @@ +# Test def with template and boundaries for the cursor + +import fixtures/mstrutils + +discard """ +$nimsuggest --tester $file +>def $path/fixtures/mstrutils.nim:6:4 +def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100 +>def $path/fixtures/mstrutils.nim:12:3 +def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100 +>def $path/fixtures/mstrutils.nim:18:11 +def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100 +""" diff --git a/nimsuggest/tests/tdef_forward.nim b/nimsuggest/tests/tdef_forward.nim new file mode 100644 index 000000000..9bdd8b21d --- /dev/null +++ b/nimsuggest/tests/tdef_forward.nim @@ -0,0 +1,13 @@ +discard """ +$nimsuggest --tester $file +>def $1 +def;;skProc;;tdef_forward.hello;;proc (): string;;$file;;8;;5;;"";;100 +def;;skProc;;tdef_forward.hello;;proc (): string;;$file;;12;;5;;"";;100 +""" + +proc hello(): string + +hel#[!]#lo() + +proc hello(): string = + "Hello" diff --git a/nimsuggest/tests/tdef_let.nim b/nimsuggest/tests/tdef_let.nim new file mode 100644 index 000000000..3e9456d2f --- /dev/null +++ b/nimsuggest/tests/tdef_let.nim @@ -0,0 +1,7 @@ +discard """ +$nimsuggest --tester $file +>def $1 +def;;skLet;;tdef_let.intVar;;int;;$file;;7;;4;;"";;100 +""" + +let int#[!]#Var = 10 diff --git a/nimsuggest/tests/tdot3.nim b/nimsuggest/tests/tdot3.nim index 15fc1cd1c..30dd60591 100644 --- a/nimsuggest/tests/tdot3.nim +++ b/nimsuggest/tests/tdot3.nim @@ -9,14 +9,14 @@ proc main(f: Foo) = # this way, the line numbers more often stay the same discard """ -!copy dep_v1.nim dep.nim +!copy fixtures/mdep_v1.nim dep.nim $nimsuggest --tester $file >sug $1 sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100;;None sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100;;None sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100;;None -!copy dep_v2.nim dep.nim +!copy fixtures/mdep_v2.nim dep.nim >mod $path/dep.nim >sug $1 sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100;;None diff --git a/nimsuggest/tests/tdot4.nim b/nimsuggest/tests/tdot4.nim index 762534310..f2c6c765f 100644 --- a/nimsuggest/tests/tdot4.nim +++ b/nimsuggest/tests/tdot4.nim @@ -1,17 +1,21 @@ -discard """ -disabled:true -$nimsuggest --tester --maxresults:2 $file ->sug $1 -sug;;skProc;;tdot4.main;;proc (inp: string): string;;$file;;10;;5;;"";;100;;None -sug;;skProc;;strutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, locks: 0.};;$lib/pure/strutils.nim;;1506;;5;;"Replaces `sub` in `s` by the string `by`.";;100;;None -""" +# Test that already used suggestions are prioritized -import strutils +from system import string, echo +import fixtures/mstrutils proc main(inp: string): string = # use replace here and see if it occurs in the result, it should gain # priority: result = inp.replace(" ", "a").replace("b", "c") - echo "string literal here".#[!]# + +# priority still tested, but limit results to avoid failures from other output +discard """ +$nimsuggest --tester --maxresults:2 $file +>sug $1 +sug;;skProc;;tdot4.main;;proc (inp: string): string;;$file;;6;;5;;"";;100;;None +sug;;skFunc;;mstrutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, raises: <inferred> [].};;*fixtures/mstrutils.nim;;9;;5;;"this is a test version of strutils.replace, it simply returns `by`";;100;;None +""" + +# TODO - determine appropriate behaviour for further suggest output and test it diff --git a/nimsuggest/tests/tenum_field.nim b/nimsuggest/tests/tenum_field.nim new file mode 100644 index 000000000..4ceb3e021 --- /dev/null +++ b/nimsuggest/tests/tenum_field.nim @@ -0,0 +1,17 @@ +discard """ +$nimsuggest --tester $file +>sug $1 +>sug $2 +sug;;skConst;;tenum_field.BarFoo;;int literal(1);;$file;;10;;6;;"";;100;;Prefix +""" + +proc something() = discard + +const BarFoo = 1 + +type + Foo = enum + # Test that typing the name doesn't give suggestions + somethi#[!]# + # Test that the right hand side still gets suggestions + another = BarFo#[!]# diff --git a/nimsuggest/tests/tfatal1.nim b/nimsuggest/tests/tfatal1.nim new file mode 100644 index 000000000..19778f22e --- /dev/null +++ b/nimsuggest/tests/tfatal1.nim @@ -0,0 +1,15 @@ +{.warning: "I'm a warning!".} +{.error: "I'm an error!".} +{.fatal: "I'm a fatal error!".} +{.error: "I'm an error after fatal error!".} + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tfatal1.nim [Processing]";;0 +chk;;skUnknown;;;;Warning;;$file;;1;;9;;"I\'m a warning! [User]";;0 +chk;;skUnknown;;;;Error;;$file;;2;;7;;"I\'m an error!";;0 +chk;;skUnknown;;;;Error;;$file;;3;;7;;"fatal error: I\'m a fatal error!";;0 +chk;;skUnknown;;;;Error;;$file;;4;;7;;"I\'m an error after fatal error!";;0 +""" diff --git a/nimsuggest/tests/tgeneric_highlight.nim b/nimsuggest/tests/tgeneric_highlight.nim index 334323613..c7291d08b 100644 --- a/nimsuggest/tests/tgeneric_highlight.nim +++ b/nimsuggest/tests/tgeneric_highlight.nim @@ -7,14 +7,7 @@ $nimsuggest --tester $file >highlight $1 highlight;;skType;;1;;7;;3 highlight;;skProc;;1;;0;;6 -highlight;;skProc;;1;;0;;6 -highlight;;skType;;1;;7;;3 -highlight;;skProc;;1;;0;;6 -highlight;;skType;;2;;14;;3 -highlight;;skProc;;2;;7;;6 -highlight;;skProc;;2;;7;;6 highlight;;skType;;2;;14;;3 highlight;;skProc;;2;;7;;6 -highlight;;skTemplate;;3;;0;;8 highlight;;skType;;3;;9;;3 """ diff --git a/nimsuggest/tests/tgenerics.nim b/nimsuggest/tests/tgenerics.nim new file mode 100644 index 000000000..7f490321c --- /dev/null +++ b/nimsuggest/tests/tgenerics.nim @@ -0,0 +1,18 @@ +type + Hello[T] = object + value: T + +proc printHelloValue[T](hello: Hello[T]) = + echo hello.value + +proc main() = + let a = Hello[float]() + p#[!]#rintHelloValue(a) + +main() + +discard """ +$nimsuggest --tester $file +>def $1 +def;;skProc;;tgenerics.printHelloValue;;proc (hello: Hello[printHelloValue.T]);;$file;;5;;5;;"";;100 +""" diff --git a/nimsuggest/tests/tic.nim b/nimsuggest/tests/tic.nim new file mode 100644 index 000000000..26e644f83 --- /dev/null +++ b/nimsuggest/tests/tic.nim @@ -0,0 +1,20 @@ +import std/[appdirs, assertions, cmdline, compilesettings, decls, + dirs, editdistance, effecttraits, enumerate, enumutils, envvars, + exitprocs, files, formatfloat, genasts, importutils, + isolation, jsonutils, logic, monotimes, objectdollar, + oserrors, outparams, packedsets, paths, private, setutils, sha1, + socketstreams, stackframes, staticos, strbasics, symlinks, syncio, + sysatomics, sysrand, tasks, tempfiles, time_t, typedthreads, varints, + vmutils, widestrs, with, wordwrap, wrapnils] + +proc test(a: string, b:string) = discard +proc test(a: int) = discard + +test(#[!]# + +discard """ +$nimsuggest --v3 --ic:off --tester $file +>con $1 +con;;skProc;;tic.test;;proc (a: string, b: string);;$file;;10;;5;;"";;100 +con;;skProc;;tic.test;;proc (a: int);;$file;;11;;5;;"";;100 +""" \ No newline at end of file diff --git a/nimsuggest/tests/timport_highlight.nim b/nimsuggest/tests/timport_highlight.nim new file mode 100644 index 000000000..043f87d98 --- /dev/null +++ b/nimsuggest/tests/timport_highlight.nim @@ -0,0 +1,12 @@ +import std/paths +import json as J +import std/[os,streams]#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skModule;;1;;11;;5 +highlight;;skModule;;2;;7;;4 +highlight;;skModule;;3;;12;;2 +highlight;;skModule;;3;;15;;7 +""" diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim index 0fda43911..f5cbabf05 100644 --- a/nimsuggest/tests/tinclude.nim +++ b/nimsuggest/tests/tinclude.nim @@ -1,8 +1,25 @@ +# import that has an include: +# * def calls must work into and out of includes +# * outline calls on the import must show included members +import fixtures/minclude_import + +proc go() = + discard create().say() + +go() + discard """ -disabled:true -$nimsuggest --tester compiler/nim.nim ->def compiler/semexprs.nim:25:50 -def;;skType;;ast.PSym;;PSym;;*ast.nim;;707;;2;;"";;100 ->def compiler/semexprs.nim:25:50 -def;;skType;;ast.PSym;;PSym;;*ast.nim;;707;;2;;"";;100 +$nimsuggest --tester $file +>def $path/tinclude.nim:7:14 +def;;skProc;;minclude_import.create;;proc (greeting: string, subject: string): Greet{.noSideEffect, gcsafe, raises: <inferred> [].};;*fixtures/minclude_include.nim;;3;;5;;"";;100 +>def $path/fixtures/minclude_include.nim:3:71 +def;;skType;;minclude_types.Greet;;Greet;;*fixtures/minclude_types.nim;;4;;2;;"";;100 +>def $path/fixtures/minclude_include.nim:3:71 +def;;skType;;minclude_types.Greet;;Greet;;*fixtures/minclude_types.nim;;4;;2;;"";;100 +>outline $path/fixtures/minclude_import.nim +outline;;skProc;;minclude_import.say;;*fixtures/minclude_import.nim;;7;;5;;"";;100 +outline;;skProc;;minclude_import.create;;*fixtures/minclude_include.nim;;3;;5;;"";;100 +outline;;skProc;;minclude_import.say;;*fixtures/minclude_import.nim;;13;;5;;"";;100 """ + +# TODO test/fix if the first `def` is not first or repeated we get no results diff --git a/nimsuggest/tests/top_highlight.nim b/nimsuggest/tests/top_highlight.nim new file mode 100644 index 000000000..c3f6bb61d --- /dev/null +++ b/nimsuggest/tests/top_highlight.nim @@ -0,0 +1,11 @@ +import json + +%*{}#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skModule;;1;;7;;4 +highlight;;skMacro;;3;;0;;2 +highlight;;skMacro;;3;;0;;2 +""" diff --git a/nimsuggest/tests/tqualified_highlight.nim b/nimsuggest/tests/tqualified_highlight.nim index 3b521ecc1..b83669e72 100644 --- a/nimsuggest/tests/tqualified_highlight.nim +++ b/nimsuggest/tests/tqualified_highlight.nim @@ -10,5 +10,5 @@ highlight;;skProc;;1;;7;;4 highlight;;skTemplate;;2;;7;;4 highlight;;skTemplate;;2;;7;;4 highlight;;skTemplate;;2;;7;;4 -highlight;;skProc;;3;;8;;1 +highlight;;skFunc;;3;;8;;1 """ diff --git a/nimsuggest/tests/tstrutils.nim b/nimsuggest/tests/tstrutils.nim deleted file mode 100644 index 9462c3d99..000000000 --- a/nimsuggest/tests/tstrutils.nim +++ /dev/null @@ -1,10 +0,0 @@ -discard """ -disabled:true -$nimsuggest --tester lib/pure/strutils.nim ->def lib/pure/strutils.nim:2529:6 -def;;skTemplate;;system.doAssert;;proc (cond: bool, msg: string): typed;;*/lib/system.nim;;*;;9;;"same as `assert` but is always turned on and not affected by the\x0A``--assertions`` command line switch.";;100 -""" - -# Line 2529 in strutils.nim is doAssert and this is unlikely to change -# soon since there are a whole lot of doAsserts there. - diff --git a/nimsuggest/tests/tsug_enum.nim b/nimsuggest/tests/tsug_enum.nim new file mode 100644 index 000000000..97a225f16 --- /dev/null +++ b/nimsuggest/tests/tsug_enum.nim @@ -0,0 +1,18 @@ +## suggestions for enums + +type + LogLevel {.pure.} = enum + debug, log, warn, error + + FooBar = enum + fbFoo, fbBar + +echo fbFoo, fbBar + +echo LogLevel.deb#[!]# + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skEnumField;;debug;;LogLevel;;*nimsuggest/tests/tsug_enum.nim;;5;;4;;"";;100;;Prefix +""" \ No newline at end of file diff --git a/nimsuggest/tests/tsug_pragmas.nim b/nimsuggest/tests/tsug_pragmas.nim new file mode 100644 index 000000000..ce9c4e8f8 --- /dev/null +++ b/nimsuggest/tests/tsug_pragmas.nim @@ -0,0 +1,40 @@ +template fooBar1() {.pragma.} +proc fooBar2() = discard +macro fooBar3(x: untyped) = discard +{.pragma: fooBar4 fooBar3.} + +proc test1() {.fooBar#[!]#.} = discard + +var test2 {.fooBar#[!]#.} = 9 + +type + Person {.fooBar#[!]#.} = object + hello {.fooBar#[!]#.}: string + Callback = proc () {.fooBar#[!]#.} + +# Check only macros/templates/pragmas are suggested +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +>sug $2 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +>sug $3 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +>sug $4 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +>sug $5 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +""" + + diff --git a/nimsuggest/tests/tsug_recursive.nim b/nimsuggest/tests/tsug_recursive.nim new file mode 100644 index 000000000..97ee5ca01 --- /dev/null +++ b/nimsuggest/tests/tsug_recursive.nim @@ -0,0 +1,8 @@ +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skProc;;tsug_recursive.fooBar;;proc ();;$file;;7;;5;;"";;100;;Prefix +""" + +proc fooBar() = + fooBa#[!]# diff --git a/nimsuggest/tests/tsug_regression.nim b/nimsuggest/tests/tsug_regression.nim index 1b23da806..2aff3fe94 100644 --- a/nimsuggest/tests/tsug_regression.nim +++ b/nimsuggest/tests/tsug_regression.nim @@ -7,23 +7,28 @@ type X = object proc main = # bug #52 var - set0 = initSet[int]() - set1 = initSet[X]() - set2 = initSet[ref int]() + set0 = initHashSet[int]() + set1 = initHashSet[X]() + set2 = initHashSet[ref int]() map0 = initTable[int, int]() map1 = initOrderedTable[string, int]() cfg = loadConfig("file") map0.#[!]# +# the maxresults are limited as it seems there is sort or some other +# instability that causes the suggestions to slightly differ between 32 bit +# and 64 bit versions of nimsuggest + discard """ disabled:true -$nimsuggest --tester $file +$nimsuggest --tester --maxresults:4 $file >sug $1 -sug;;skProc;;tables.getOrDefault;;proc (t: Table[getOrDefault.A, getOrDefault.B], key: A): B;;$lib/pure/collections/tables.nim;;178;;5;;"";;100;;None -sug;;skProc;;tables.hasKey;;proc (t: Table[hasKey.A, hasKey.B], key: A): bool;;$lib/pure/collections/tables.nim;;233;;5;;"returns true iff `key` is in the table `t`.";;100;;None -sug;;skProc;;tables.add;;proc (t: var Table[add.A, add.B], key: A, val: B);;$lib/pure/collections/tables.nim;;309;;5;;"puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.";;100;;None -sug;;skIterator;;tables.allValues;;iterator (t: Table[allValues.A, allValues.B], key: A): B{.inline.};;$lib/pure/collections/tables.nim;;225;;9;;"iterates over any value in the table `t` that belongs to the given `key`.";;100;;None -sug;;skProc;;tables.clear;;proc (t: var Table[clear.A, clear.B]);;$lib/pure/collections/tables.nim;;121;;5;;"Resets the table so that it is empty.";;100;;None -* +sug;;skProc;;tables.hasKey;;proc (t: Table[hasKey.A, hasKey.B], key: A): bool;;*/lib/pure/collections/tables.nim;;374;;5;;"Returns true *";;100;;None +sug;;skProc;;tables.clear;;proc (t: var Table[clear.A, clear.B]);;*/lib/pure/collections/tables.nim;;567;;5;;"Resets the table so that it is empty*";;100;;None +sug;;skProc;;tables.contains;;proc (t: Table[contains.A, contains.B], key: A): bool;;*/lib/pure/collections/tables.nim;;*;;5;;"Alias of *";;100;;None +sug;;skProc;;tables.del;;proc (t: var Table[del.A, del.B], key: A);;*/lib/pure/collections/tables.nim;;*;;5;;"*";;100;;None """ + +# TODO enable the tests +# TODO: test/fix suggestion sorting - deprecated suggestions should rank lower diff --git a/nimsuggest/tests/tsug_template.nim b/nimsuggest/tests/tsug_template.nim index 0f258fcc6..da494d279 100644 --- a/nimsuggest/tests/tsug_template.nim +++ b/nimsuggest/tests/tsug_template.nim @@ -6,7 +6,7 @@ tmp#[!]# discard """ $nimsuggest --tester $file >sug $1 -sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, locks: 0.};;$file;;2;;6;;"";;0;;Prefix -sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;0;;Prefix -sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;0;;Prefix +sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;2;;6;;"";;100;;Prefix +sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;100;;Prefix +sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;100;;Prefix """ diff --git a/nimsuggest/tests/tsug_typedecl.nim b/nimsuggest/tests/tsug_typedecl.nim new file mode 100644 index 000000000..2a510929d --- /dev/null +++ b/nimsuggest/tests/tsug_typedecl.nim @@ -0,0 +1,26 @@ +# suggestions for type declarations + +from system import string, int, bool + +type + super = int + someType = bool + +let str = "hello" + +proc main() = + let a: s#[!]# + +# This output show seq, even though that's not imported. This is due to the +# entire symbol table, regardless of import visibility is currently being +# scanned. This is hardly ideal, but changing it with the current level of test +# coverage is unwise as it might break more than it fixes. + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skType;;tsug_typedecl.someType;;someType;;*nimsuggest/tests/tsug_typedecl.nim;;7;;2;;"";;100;;Prefix +sug;;skType;;tsug_typedecl.super;;super;;*nimsuggest/tests/tsug_typedecl.nim;;6;;2;;"";;100;;Prefix +sug;;skType;;system.string;;string;;*lib/system/basic_types.nim;;*;;*;;*;;100;;Prefix +sug;;skType;;system.seq;;seq;;*lib/system.nim;;*;;*;;*;;100;;Prefix +""" diff --git a/nimsuggest/tests/ttempl_inst.nim b/nimsuggest/tests/ttempl_inst.nim new file mode 100644 index 000000000..5f5b10fe9 --- /dev/null +++ b/nimsuggest/tests/ttempl_inst.nim @@ -0,0 +1,13 @@ +template foo() = + {.warning: "foo".} + +foo() + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/ttempl_inst.nim [Processing]";;0 +chk;;skUnknown;;;;Hint;;$file;;4;;3;;"template/generic instantiation from here";;0 +chk;;skUnknown;;;;Warning;;$file;;2;;11;;"foo [User]";;0 +""" diff --git a/nimsuggest/tests/disabled_ttemplate_highlight.nim b/nimsuggest/tests/ttemplate_highlight.nim index 2cbac3be5..2cbac3be5 100644 --- a/nimsuggest/tests/disabled_ttemplate_highlight.nim +++ b/nimsuggest/tests/ttemplate_highlight.nim diff --git a/nimsuggest/tests/ttype_decl.nim b/nimsuggest/tests/ttype_decl.nim index e66d39a1c..61d8c26cd 100644 --- a/nimsuggest/tests/ttype_decl.nim +++ b/nimsuggest/tests/ttype_decl.nim @@ -1,15 +1,16 @@ discard """ -disabled:true $nimsuggest --tester --maxresults:3 $file >sug $1 -sug;;skType;;ttype_decl.Other;;Other;;$file;;10;;2;;"";;0;;None -sug;;skType;;system.int;;int;;$lib/system.nim;;25;;2;;"";;0;;None -sug;;skType;;system.string;;string;;$lib/system.nim;;48;;2;;"";;0;;None +sug;;skType;;ttype_decl.Other;;Other;;$file;;10;;2;;"";;100;;None +sug;;skType;;system.int;;int;;*lib/system/basic_types.nim;;2;;2;;"";;100;;None +sug;;skType;;system.string;;string;;*lib/system/basic_types.nim;;23;;2;;"";;100;;None """ import strutils type Other = object ## My other object. Foo = #[!]# + OldOne {.deprecated.} = object + x: int proc main(f: Foo) = diff --git a/nimsuggest/tests/tuse.nim b/nimsuggest/tests/tuse.nim new file mode 100644 index 000000000..7c1d1ad0c --- /dev/null +++ b/nimsuggest/tests/tuse.nim @@ -0,0 +1,22 @@ +# basic tests for use + +# bug #58 +proc someOtherProc() = + discard + +someOtherProc() + +proc #[!]#someProc*() = + discard + +#[!]#someProc() + +discard """ +$nimsuggest --tester $file +>use $1 +def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;9;;5;;"";;100 +use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;12;;0;;"";;100 +>use $2 +def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;9;;5;;"";;100 +use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;12;;0;;"";;100 +""" diff --git a/nimsuggest/tests/tuse_enum.nim b/nimsuggest/tests/tuse_enum.nim new file mode 100644 index 000000000..8a40a8348 --- /dev/null +++ b/nimsuggest/tests/tuse_enum.nim @@ -0,0 +1,15 @@ +discard """ +$nimsuggest --tester $file +>use $1 +def;;skEnumField;;tuse_enum.Colour.Red;;Colour;;$file;;10;;4;;"";;100 +use;;skEnumField;;tuse_enum.Colour.Red;;Colour;;$file;;14;;8;;"";;100 +""" + +type + Colour = enum + Red + Green + Blue + +discard #[!]#Red + diff --git a/nimsuggest/tests/tuse_structure.nim b/nimsuggest/tests/tuse_structure.nim new file mode 100644 index 000000000..f65ab9060 --- /dev/null +++ b/nimsuggest/tests/tuse_structure.nim @@ -0,0 +1,15 @@ +# tests for use and structures + +type + Foo* = ref object of RootObj + bar*: string + +proc test(f: Foo) = + echo f.#[!]#bar + +discard """ +$nimsuggest --tester $file +>use $1 +def skField tuse_structure.Foo.bar string $file 5 4 "" 100 +use skField tuse_structure.Foo.bar string $file 8 9 "" 100 +""" diff --git a/nimsuggest/tests/tv3.nim b/nimsuggest/tests/tv3.nim new file mode 100644 index 000000000..80e51e364 --- /dev/null +++ b/nimsuggest/tests/tv3.nim @@ -0,0 +1,27 @@ +# tests v3 + +type + Foo* = ref object of RootObj + bar*: string + +proc test(f: Foo) = + echo f.ba#[!]#r + +#[!]# + +discard """ +$nimsuggest --v3 --tester $file +>use $1 +def skField tv3.Foo.bar string $file 5 4 "" 100 +use skField tv3.Foo.bar string $file 8 9 "" 100 +>def $1 +def skField tv3.Foo.bar string $file 5 4 "" 100 +>sug $1 +sug skField bar string $file 5 4 "" 100 Prefix +>globalSymbols test +def skProc tv3.test proc (f: Foo){.gcsafe, raises: <inferred> [].} $file 7 5 "" 100 +>globalSymbols Foo +def skType tv3.Foo Foo $file 4 2 "" 100 +>def $2 +>use $2 +""" diff --git a/nimsuggest/tests/tv3_con.nim b/nimsuggest/tests/tv3_con.nim new file mode 100644 index 000000000..4714c366b --- /dev/null +++ b/nimsuggest/tests/tv3_con.nim @@ -0,0 +1,13 @@ +# tests v3 + +proc test(a: string, b:string) = discard +proc test(a: int) = discard + +test(#[!]# + +discard """ +$nimsuggest --v3 --tester $file +>con $1 +con;;skProc;;tv3_con.test;;proc (a: string, b: string);;$file;;3;;5;;"";;100 +con;;skProc;;tv3_con.test;;proc (a: int);;$file;;4;;5;;"";;100 +""" diff --git a/nimsuggest/tests/tv3_definition.nim b/nimsuggest/tests/tv3_definition.nim new file mode 100644 index 000000000..03684b7cd --- /dev/null +++ b/nimsuggest/tests/tv3_definition.nim @@ -0,0 +1,9 @@ + +let foo = 30 +let bar = foo + fo#[!]#o + foo + +discard """ +$nimsuggest --v3 --tester $file +>def $1 +def skLet tv3_definition.foo int $file 2 4 "" 100 +""" diff --git a/nimsuggest/tests/tv3_forward_definition.nim b/nimsuggest/tests/tv3_forward_definition.nim new file mode 100644 index 000000000..7a16ea331 --- /dev/null +++ b/nimsuggest/tests/tv3_forward_definition.nim @@ -0,0 +1,23 @@ +proc de#[!]#mo(): int + +proc de#[!]#mo(): int = 5 + +let a = de#[!]#mo() + +discard """ +$nimsuggest --v3 --tester $file +>use $1 +use skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 1 5 "" 100 +def skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 3 5 "" 100 +use skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 5 8 "" 100 +>use $2 +use skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 1 5 "" 100 +def skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 3 5 "" 100 +use skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 5 8 "" 100 +>declaration $1 +declaration skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 3 5 "" 100 +>declaration $2 +declaration skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 1 5 "" 100 +>declaration $3 +declaration skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 1 5 "" 100 +""" diff --git a/nimsuggest/tests/tv3_generics.nim b/nimsuggest/tests/tv3_generics.nim new file mode 100644 index 000000000..2bfb2ca1d --- /dev/null +++ b/nimsuggest/tests/tv3_generics.nim @@ -0,0 +1,18 @@ +type + Hello[T] = object + value: T + +proc printHelloValue[T](hello: Hello[T]) = + echo hello.value + +proc main() = + let a = Hello[float]() + p#[!]#rintHelloValue(a) + +main() + +discard """ +$nimsuggest --v3 --tester $file +>def $1 +def;;skProc;;tv3_generics.printHelloValue;;proc (hello: Hello[printHelloValue.T]);;$file;;5;;5;;"";;100 +""" diff --git a/nimsuggest/tests/tv3_globalSymbols.nim b/nimsuggest/tests/tv3_globalSymbols.nim new file mode 100644 index 000000000..c3bb9933b --- /dev/null +++ b/nimsuggest/tests/tv3_globalSymbols.nim @@ -0,0 +1,14 @@ +# Tests the order of the matches +proc Btoken(): int = 5 +proc tokenA(): int = 5 +proc token(): int = 5 +proc BBtokenA(): int = 5 + +discard """ +$nimsuggest --v3 --tester $file +>globalSymbols token +def skProc tv3_globalSymbols.token proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 4 5 "" 100 +def skProc tv3_globalSymbols.tokenA proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 3 5 "" 100 +def skProc tv3_globalSymbols.Btoken proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 2 5 "" 100 +def skProc tv3_globalSymbols.BBtokenA proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 5 5 "" 100 +""" diff --git a/nimsuggest/tests/tv3_import.nim b/nimsuggest/tests/tv3_import.nim new file mode 100644 index 000000000..3c128f85b --- /dev/null +++ b/nimsuggest/tests/tv3_import.nim @@ -0,0 +1,7 @@ +import tv#[!]#3 + +discard """ +$nimsuggest --v3 --tester $file +>def $1 +def skModule tv3 */tv3.nim 1 0 "" 100 +""" diff --git a/nimsuggest/tests/tv3_outline.nim b/nimsuggest/tests/tv3_outline.nim new file mode 100644 index 000000000..518620c87 --- /dev/null +++ b/nimsuggest/tests/tv3_outline.nim @@ -0,0 +1,45 @@ +# tests v3 outline + +type + Foo* = ref object of RootObj + bar*: string + FooEnum = enum value1, value2 + FooPrivate = ref object of RootObj + barPrivate: string + +macro m(arg: untyped): untyped = discard +template t(arg: untyped): untyped = discard +proc p(): void = discard +iterator i(): int = discard +converter c(s: string): int = discard +method m(f: Foo): void = discard +func f(): void = discard + +let a = 1 +var b = 2 +const con = 2 + +proc outer(): void = + proc inner() = discard + +proc procWithLocal(): void = + let local = 10 + +discard """ +$nimsuggest --v3 --tester $file +>outline $file +outline skType tv3_outline.Foo Foo $file 4 2 "" 100 5 16 +outline skType tv3_outline.FooEnum FooEnum $file 6 2 "" 100 6 31 +outline skEnumField tv3_outline.FooEnum.value1 FooEnum $file 6 17 "" 100 6 23 +outline skEnumField tv3_outline.FooEnum.value2 FooEnum $file 6 25 "" 100 6 31 +outline skType tv3_outline.FooPrivate FooPrivate $file 7 2 "" 100 8 22 +outline skMacro tv3_outline.m macro (arg: untyped): untyped{.noSideEffect, gcsafe, raises: <inferred> [].} $file 10 6 "" 100 10 40 +outline skTemplate tv3_outline.t template (arg: untyped): untyped $file 11 9 "" 100 11 43 +outline skProc tv3_outline.p proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 12 5 "" 100 12 24 +outline skConverter tv3_outline.c converter (s: string): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 14 10 "" 100 14 37 +outline skFunc tv3_outline.f proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 16 5 "" 100 16 24 +outline skConst tv3_outline.con int literal(2) $file 20 6 "" 100 20 13 +outline skProc tv3_outline.outer proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 22 5 "" 100 23 24 +outline skProc tv3_outline.outer.inner proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 23 7 "" 100 23 24 +outline skProc tv3_outline.procWithLocal proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 25 5 "" 100 26 16 +""" diff --git a/nimsuggest/tests/tv3_typeDefinition.nim b/nimsuggest/tests/tv3_typeDefinition.nim new file mode 100644 index 000000000..f86d12cc6 --- /dev/null +++ b/nimsuggest/tests/tv3_typeDefinition.nim @@ -0,0 +1,32 @@ +# tests v3 + +type + Foo* = ref object of RootObj + bar*: string + +proc test(ff: Foo) = + echo f#[!]#f.bar + +type + Fo#[!]#o2* = ref object of RootObj + +type + FooGeneric[T] = ref object of RootObj + bar*: T + +let fooGeneric = FooGeneric[string]() +echo fo#[!]#oGeneric.bar + +# bad type +echo unde#[!]#fined + +discard """ +$nimsuggest --v3 --tester $file +>type $1 +type skType tv3_typeDefinition.Foo Foo $file 4 2 "" 100 +>type $2 +type skType tv3_typeDefinition.Foo2 Foo2 $file 11 2 "" 100 +>type $3 +type skType tv3_typeDefinition.FooGeneric FooGeneric $file 14 2 "" 100 +>type $4 +""" diff --git a/nimsuggest/tests/twithin_macro.nim b/nimsuggest/tests/twithin_macro.nim index 9c36ffd0f..98d58381f 100644 --- a/nimsuggest/tests/twithin_macro.nim +++ b/nimsuggest/tests/twithin_macro.nim @@ -1,166 +1,5 @@ - -import macros - -macro class*(head, body: untyped): untyped = - # The macro is immediate, since all its parameters are untyped. - # This means, it doesn't resolve identifiers passed to it. - - var typeName, baseName: NimNode - - # flag if object should be exported - var exported: bool - - if head.kind == nnkInfix and head[0].ident == !"of": - # `head` is expression `typeName of baseClass` - # echo head.treeRepr - # -------------------- - # Infix - # Ident !"of" - # Ident !"Animal" - # Ident !"RootObj" - typeName = head[1] - baseName = head[2] - - elif head.kind == nnkInfix and head[0].ident == !"*" and - head[2].kind == nnkPrefix and head[2][0].ident == !"of": - # `head` is expression `typeName* of baseClass` - # echo head.treeRepr - # -------------------- - # Infix - # Ident !"*" - # Ident !"Animal" - # Prefix - # Ident !"of" - # Ident !"RootObj" - typeName = head[1] - baseName = head[2][1] - exported = true - - else: - quit "Invalid node: " & head.lispRepr - - # The following prints out the AST structure: - # - # import macros - # dumptree: - # type X = ref object of Y - # z: int - # -------------------- - # StmtList - # TypeSection - # TypeDef - # Ident !"X" - # Empty - # RefTy - # ObjectTy - # Empty - # OfInherit - # Ident !"Y" - # RecList - # IdentDefs - # Ident !"z" - # Ident !"int" - # Empty - - # create a type section in the result - result = - if exported: - # mark `typeName` with an asterisk - quote do: - type `typeName`* = ref object of `baseName` - else: - quote do: - type `typeName` = ref object of `baseName` - - # echo treeRepr(body) - # -------------------- - # StmtList - # VarSection - # IdentDefs - # Ident !"name" - # Ident !"string" - # Empty - # IdentDefs - # Ident !"age" - # Ident !"int" - # Empty - # MethodDef - # Ident !"vocalize" - # Empty - # Empty - # FormalParams - # Ident !"string" - # Empty - # Empty - # StmtList - # StrLit ... - # MethodDef - # Ident !"age_human_yrs" - # Empty - # Empty - # FormalParams - # Ident !"int" - # Empty - # Empty - # StmtList - # DotExpr - # Ident !"this" - # Ident !"age" - - # var declarations will be turned into object fields - var recList = newNimNode(nnkRecList) - - # expected name of constructor - let ctorName = newIdentNode("new" & $typeName) - - # Iterate over the statements, adding `this: T` - # to the parameters of functions, unless the - # function is a constructor - for node in body.children: - case node.kind: - - of nnkMethodDef, nnkProcDef: - # check if it is the ctor proc - if node.name.kind != nnkAccQuoted and node.name.basename == ctorName: - # specify the return type of the ctor proc - node.params[0] = typeName - else: - # inject `self: T` into the arguments - node.params.insert(1, newIdentDefs(ident("self"), typeName)) - result.add(node) - - of nnkVarSection: - # variables get turned into fields of the type. - for n in node.children: - recList.add(n) - - else: - result.add(node) - - # Inspect the tree structure: - # - # echo result.treeRepr - # -------------------- - # StmtList - # TypeSection - # TypeDef - # Ident !"Animal" - # Empty - # RefTy - # ObjectTy - # Empty - # OfInherit - # Ident !"RootObj" - # Empty <= We want to replace this - # MethodDef - # ... - - result[0][0][2][0][2] = recList - - # Lets inspect the human-readable version of the output - #echo repr(result) - -# --- +from system import string, int, seq, `&`, `$`, `*`, `@`, echo, add, items, RootObj +import fixtures/mclass_macro class Animal of RootObj: var name: string @@ -202,13 +41,11 @@ echo r.age_human_yrs() echo r discard """ -disabled:true -$nimsuggest --tester $file +$nimsuggest --tester --maxresults:5 $file >sug $1 -sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;None -sug;;skField;;name;;string;;$file;;166;;6;;"";;100;;None -sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;None -sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100;;None -sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100;;None -sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"";;50;;None* +sug;;skField;;age;;int;;$file;;6;;6;;"";;100;;None +sug;;skField;;name;;string;;$file;;5;;6;;"";;100;;None +sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int{.raises: <inferred> [].};;$file;;8;;9;;"";;100;;None +sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string{.raises: <inferred> [].};;$file;;7;;9;;"";;100;;None +sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;23;;9;;"";;100;;None """ diff --git a/nimsuggest/tests/twithin_macro_prefix.nim b/nimsuggest/tests/twithin_macro_prefix.nim index 1402b762a..e89c8b942 100644 --- a/nimsuggest/tests/twithin_macro_prefix.nim +++ b/nimsuggest/tests/twithin_macro_prefix.nim @@ -1,166 +1,5 @@ - -import macros - -macro class*(head, body: untyped): untyped = - # The macro is immediate, since all its parameters are untyped. - # This means, it doesn't resolve identifiers passed to it. - - var typeName, baseName: NimNode - - # flag if object should be exported - var exported: bool - - if head.kind == nnkInfix and head[0].ident == !"of": - # `head` is expression `typeName of baseClass` - # echo head.treeRepr - # -------------------- - # Infix - # Ident !"of" - # Ident !"Animal" - # Ident !"RootObj" - typeName = head[1] - baseName = head[2] - - elif head.kind == nnkInfix and head[0].ident == !"*" and - head[2].kind == nnkPrefix and head[2][0].ident == !"of": - # `head` is expression `typeName* of baseClass` - # echo head.treeRepr - # -------------------- - # Infix - # Ident !"*" - # Ident !"Animal" - # Prefix - # Ident !"of" - # Ident !"RootObj" - typeName = head[1] - baseName = head[2][1] - exported = true - - else: - quit "Invalid node: " & head.lispRepr - - # The following prints out the AST structure: - # - # import macros - # dumptree: - # type X = ref object of Y - # z: int - # -------------------- - # StmtList - # TypeSection - # TypeDef - # Ident !"X" - # Empty - # RefTy - # ObjectTy - # Empty - # OfInherit - # Ident !"Y" - # RecList - # IdentDefs - # Ident !"z" - # Ident !"int" - # Empty - - # create a type section in the result - result = - if exported: - # mark `typeName` with an asterisk - quote do: - type `typeName`* = ref object of `baseName` - else: - quote do: - type `typeName` = ref object of `baseName` - - # echo treeRepr(body) - # -------------------- - # StmtList - # VarSection - # IdentDefs - # Ident !"name" - # Ident !"string" - # Empty - # IdentDefs - # Ident !"age" - # Ident !"int" - # Empty - # MethodDef - # Ident !"vocalize" - # Empty - # Empty - # FormalParams - # Ident !"string" - # Empty - # Empty - # StmtList - # StrLit ... - # MethodDef - # Ident !"age_human_yrs" - # Empty - # Empty - # FormalParams - # Ident !"int" - # Empty - # Empty - # StmtList - # DotExpr - # Ident !"this" - # Ident !"age" - - # var declarations will be turned into object fields - var recList = newNimNode(nnkRecList) - - # expected name of constructor - let ctorName = newIdentNode("new" & $typeName) - - # Iterate over the statements, adding `this: T` - # to the parameters of functions, unless the - # function is a constructor - for node in body.children: - case node.kind: - - of nnkMethodDef, nnkProcDef: - # check if it is the ctor proc - if node.name.kind != nnkAccQuoted and node.name.basename == ctorName: - # specify the return type of the ctor proc - node.params[0] = typeName - else: - # inject `self: T` into the arguments - node.params.insert(1, newIdentDefs(ident("self"), typeName)) - result.add(node) - - of nnkVarSection: - # variables get turned into fields of the type. - for n in node.children: - recList.add(n) - - else: - result.add(node) - - # Inspect the tree structure: - # - # echo result.treeRepr - # -------------------- - # StmtList - # TypeSection - # TypeDef - # Ident !"Animal" - # Empty - # RefTy - # ObjectTy - # Empty - # OfInherit - # Ident !"RootObj" - # Empty <= We want to replace this - # MethodDef - # ... - - result[0][0][2][0][2] = recList - - # Lets inspect the human-readable version of the output - #echo repr(result) - -# --- +from system import string, int, seq, `&`, `$`, `*`, `@`, echo, add, RootObj +import fixtures/mclass_macro class Animal of RootObj: var name: string @@ -202,9 +41,8 @@ echo r.age_human_yrs() echo r discard """ -disabled:true $nimsuggest --tester $file >sug $1 -sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;Prefix -sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;Prefix +sug;;skField;;age;;int;;$file;;6;;6;;"";;100;;Prefix +sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int{.raises: <inferred> [].};;$file;;8;;9;;"";;100;;Prefix """ |