From fecad72e02256c947e1c16cd003ceca62a3633e5 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Tue, 7 Mar 2017 11:42:37 -0600 Subject: [PATCH 01/34] SMTP sync/async deduplication Deduplicate synchronous and asynchronous code with the multisync pragma. Pass address and port at connect rather than ``new(Async)Smtp``. --- lib/pure/smtp.nim | 172 ++++++++++++++++------------------------------ 1 file changed, 58 insertions(+), 114 deletions(-) diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim index 87865c0058..08e6c81125 100644 --- a/lib/pure/smtp.nim +++ b/lib/pure/smtp.nim @@ -20,7 +20,8 @@ ## var msg = createMessage("Hello from Nim's SMTP", ## "Hello!.\n Is this awesome or what?", ## @["foo@gmail.com"]) -## var smtpConn = connect("smtp.gmail.com", Port 465, true, true) +## let smtpConn = newSmtp(useSsl = true, debug=true) +## smtpConn.connect("smtp.gmail.com", Port 465) ## smtpConn.auth("username", "password") ## smtpConn.sendmail("username@gmail.com", @["foo@gmail.com"], $msg) ## @@ -34,10 +35,6 @@ import asyncnet, asyncdispatch export Port type - Smtp* = object - sock: Socket - debug: bool - Message* = object msgTo: seq[string] msgCc: seq[string] @@ -47,37 +44,29 @@ type ReplyError* = object of IOError - AsyncSmtp* = ref object - sock: AsyncSocket - address: string - port: Port - useSsl: bool + SmtpBase[SocketType] = ref object + sock: SocketType debug: bool + Smtp* = SmtpBase[Socket] + AsyncSmtp* = SmtpBase[AsyncSocket] + {.deprecated: [EInvalidReply: ReplyError, TMessage: Message, TSMTP: Smtp].} -proc debugSend(smtp: Smtp, cmd: string) = +proc debugSend(smtp: Smtp | AsyncSmtp, cmd: string) {.multisync.} = if smtp.debug: echo("C:" & cmd) - smtp.sock.send(cmd) - -proc debugRecv(smtp: var Smtp): TaintedString = - var line = TaintedString"" - smtp.sock.readLine(line) + await smtp.sock.send(cmd) +proc debugRecv(smtp: Smtp | AsyncSmtp): Future[TaintedString] {.multisync.} = + result = await smtp.sock.recvLine() if smtp.debug: - echo("S:" & line.string) - return line + echo("S:" & result.string) proc quitExcpt(smtp: Smtp, msg: string) = smtp.debugSend("QUIT") raise newException(ReplyError, msg) -proc checkReply(smtp: var Smtp, reply: string) = - var line = smtp.debugRecv() - if not line.string.startswith(reply): - quitExcpt(smtp, "Expected " & reply & " reply, got: " & line.string) - const compiledWithSsl = defined(ssl) when not defined(ssl): @@ -86,63 +75,6 @@ when not defined(ssl): else: let defaultSSLContext = newContext(verifyMode = CVerifyNone) -proc connect*(address: string, port = Port(25), - ssl = false, debug = false, - sslContext = defaultSSLContext): Smtp = - ## Establishes a connection with a SMTP server. - ## May fail with ReplyError or with a socket error. - result.sock = newSocket() - if ssl: - when compiledWithSsl: - sslContext.wrapSocket(result.sock) - else: - raise newException(ESystem, - "SMTP module compiled without SSL support") - result.sock.connect(address, port) - result.debug = debug - - result.checkReply("220") - result.debugSend("HELO " & address & "\c\L") - result.checkReply("250") - -proc auth*(smtp: var Smtp, username, password: string) = - ## Sends an AUTH command to the server to login as the `username` - ## using `password`. - ## May fail with ReplyError. - - smtp.debugSend("AUTH LOGIN\c\L") - smtp.checkReply("334") # TODO: Check whether it's asking for the "Username:" - # i.e "334 VXNlcm5hbWU6" - smtp.debugSend(encode(username) & "\c\L") - smtp.checkReply("334") # TODO: Same as above, only "Password:" (I think?) - - smtp.debugSend(encode(password) & "\c\L") - smtp.checkReply("235") # Check whether the authentification was successful. - -proc sendmail*(smtp: var Smtp, fromaddr: string, - toaddrs: seq[string], msg: string) = - ## Sends `msg` from `fromaddr` to `toaddr`. - ## Messages may be formed using ``createMessage`` by converting the - ## Message into a string. - - smtp.debugSend("MAIL FROM:<" & fromaddr & ">\c\L") - smtp.checkReply("250") - for address in items(toaddrs): - smtp.debugSend("RCPT TO:<" & address & ">\c\L") - smtp.checkReply("250") - - # Send the message - smtp.debugSend("DATA " & "\c\L") - smtp.checkReply("354") - smtp.debugSend(msg & "\c\L") - smtp.debugSend(".\c\L") - smtp.checkReply("250") - -proc close*(smtp: Smtp) = - ## Disconnects from the SMTP server and closes the socket. - smtp.debugSend("QUIT\c\L") - smtp.sock.close() - proc createMessage*(mSubject, mBody: string, mTo, mCc: seq[string], otherHeaders: openarray[tuple[name, value: string]]): Message = ## Creates a new MIME compliant message. @@ -178,81 +110,94 @@ proc `$`*(msg: Message): string = result.add("\c\L") result.add(msg.msgBody) -proc newAsyncSmtp*(address: string, port: Port, useSsl = false, +proc newSmtp*(useSsl = false, debug=false, + sslContext = defaultSslContext): Smtp = + ## Creates a new ``Smtp`` instance. + new result + result.debug = debug + + result.sock = newSocket() + if useSsl: + when compiledWithSsl: + sslContext.wrapSocket(result.sock) + else: + raise newException(SystemError, + "SMTP module compiled without SSL support") + +proc newAsyncSmtp*(useSsl = false, debug=false, sslContext = defaultSslContext): AsyncSmtp = ## Creates a new ``AsyncSmtp`` instance. new result - result.address = address - result.port = port - result.useSsl = useSsl + result.debug = debug result.sock = newAsyncSocket() if useSsl: when compiledWithSsl: sslContext.wrapSocket(result.sock) else: - raise newException(ESystem, + raise newException(SystemError, "SMTP module compiled without SSL support") proc quitExcpt(smtp: AsyncSmtp, msg: string): Future[void] = var retFuture = newFuture[void]() - var sendFut = smtp.sock.send("QUIT") + var sendFut = smtp.debugSend("QUIT") sendFut.callback = proc () = # TODO: Fix this in async procs. raise newException(ReplyError, msg) return retFuture -proc checkReply(smtp: AsyncSmtp, reply: string) {.async.} = - var line = await smtp.sock.recvLine() - if not line.string.startswith(reply): - await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line.string) +proc checkReply(smtp: Smtp | AsyncSmtp, reply: string) {.multisync.} = + var line = await smtp.debugRecv() + if not line.startswith(reply): + await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line) -proc connect*(smtp: AsyncSmtp) {.async.} = +proc connect*(smtp: Smtp | AsyncSmtp, + address: string, port: Port) {.multisync.} = ## Establishes a connection with a SMTP server. ## May fail with ReplyError or with a socket error. - await smtp.sock.connect(smtp.address, smtp.port) + await smtp.sock.connect(address, port) await smtp.checkReply("220") - await smtp.sock.send("HELO " & smtp.address & "\c\L") + await smtp.debugSend("HELO " & address & "\c\L") await smtp.checkReply("250") -proc auth*(smtp: AsyncSmtp, username, password: string) {.async.} = +proc auth*(smtp: Smtp | AsyncSmtp, username, password: string) {.multisync.} = ## Sends an AUTH command to the server to login as the `username` ## using `password`. ## May fail with ReplyError. - await smtp.sock.send("AUTH LOGIN\c\L") + await smtp.debugSend("AUTH LOGIN\c\L") await smtp.checkReply("334") # TODO: Check whether it's asking for the "Username:" # i.e "334 VXNlcm5hbWU6" - await smtp.sock.send(encode(username) & "\c\L") + await smtp.debugSend(encode(username) & "\c\L") await smtp.checkReply("334") # TODO: Same as above, only "Password:" (I think?) - await smtp.sock.send(encode(password) & "\c\L") + await smtp.debugSend(encode(password) & "\c\L") await smtp.checkReply("235") # Check whether the authentification was successful. -proc sendMail*(smtp: AsyncSmtp, fromAddr: string, - toAddrs: seq[string], msg: string) {.async.} = +proc sendMail*(smtp: Smtp | AsyncSmtp, fromAddr: string, + toAddrs: seq[string], msg: string) {.multisync.} = ## Sends ``msg`` from ``fromAddr`` to the addresses specified in ``toAddrs``. ## Messages may be formed using ``createMessage`` by converting the ## Message into a string. - await smtp.sock.send("MAIL FROM:<" & fromAddr & ">\c\L") + await smtp.debugSend("MAIL FROM:<" & fromAddr & ">\c\L") await smtp.checkReply("250") for address in items(toAddrs): - await smtp.sock.send("RCPT TO:<" & address & ">\c\L") + await smtp.debugSend("RCPT TO:<" & address & ">\c\L") await smtp.checkReply("250") # Send the message - await smtp.sock.send("DATA " & "\c\L") + await smtp.debugSend("DATA " & "\c\L") await smtp.checkReply("354") await smtp.sock.send(msg & "\c\L") - await smtp.sock.send(".\c\L") + await smtp.debugSend(".\c\L") await smtp.checkReply("250") -proc close*(smtp: AsyncSmtp) {.async.} = +proc close*(smtp: Smtp | AsyncSmtp) {.multisync.} = ## Disconnects from the SMTP server and closes the socket. - await smtp.sock.send("QUIT\c\L") + await smtp.debugSend("QUIT\c\L") smtp.sock.close() when not defined(testing) and isMainModule: @@ -278,25 +223,24 @@ when not defined(testing) and isMainModule: proc async_test() {.async.} = let client = newAsyncSmtp( - conf["smtphost"], - conf["port"].parseInt.Port, - conf["use_tls"].parseBool + conf["use_tls"].parseBool, + debug=true ) - await client.connect() + await client.connect(conf["smtphost"], conf["port"].parseInt.Port) await client.auth(conf["username"], conf["password"]) await client.sendMail(conf["sender"], @[conf["recipient"]], $msg) await client.close() echo "async email sent" proc sync_test() = - var smtpConn = connect( - conf["smtphost"], - conf["port"].parseInt.Port, + var smtpConn = newSmtp( conf["use_tls"].parseBool, - true, # debug + debug=true ) + smtpConn.connect(conf["smtphost"], conf["port"].parseInt.Port) smtpConn.auth(conf["username"], conf["password"]) - smtpConn.sendmail(conf["sender"], @[conf["recipient"]], $msg) + smtpConn.sendMail(conf["sender"], @[conf["recipient"]], $msg) + smtpConn.close() echo "sync email sent" waitFor async_test() From 4d0d6c47bd58d42c38e66acacf271823f6f1b7f2 Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 8 Mar 2017 15:59:34 +0100 Subject: [PATCH 02/34] bugfix: consider type contexts properly --- compiler/semdata.nim | 1 + compiler/suggest.nim | 74 ++++++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index ef23e40f28..d36278d03c 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -113,6 +113,7 @@ type recursiveDep*: string suggestionsMade*: bool inTypeContext*: int + suggestionNode*: PNode proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair = result.genericSym = s diff --git a/compiler/suggest.nim b/compiler/suggest.nim index ebabed4653..3c4bf8c091 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -525,37 +525,42 @@ proc safeSemExpr*(c: PContext, n: PNode): PNode = except ERecoverableError: result = ast.emptyNode +proc sugExpr(c: PContext, node: PNode, outputs: var Suggestions; cp: TCheckPointResult) = + var n = findClosestDot(node) + if n == nil: n = node + if n.kind == nkDotExpr: + var obj = safeSemExpr(c, n.sons[0]) + # it can happen that errnously we have collected the fieldname + # of the next line, so we check the 'field' is actually on the same + # line as the object to prevent this from happening: + let prefix = if n.len == 2 and n[1].info.line == n[0].info.line: n[1] else: nil + suggestFieldAccess(c, obj, prefix, outputs) + + #if optIdeDebug in gGlobalOptions: + # echo "expression ", renderTree(obj), " has type ", typeToString(obj.typ) + #writeStackTrace() + else: + #let m = findClosestSym(node) + #if m != nil: + # suggestPrefix(c, m, outputs) + #else: + let prefix = if cp == cpExact: n else: nil + suggestEverything(c, n, prefix, outputs) + proc suggestExpr*(c: PContext, node: PNode) = if gTrackPos.line < 0: return var cp = inCheckpoint(node.info) if cp == cpNone: return # This keeps semExpr() from coming here recursively: + if cp == cpFuzzy: + c.suggestionNode = node + return + if c.compilesContextId > 0: return inc(c.compilesContextId) - var outputs: Suggestions = @[] if gIdeCmd == ideSug: - var n = findClosestDot(node) - if n == nil: n = node - if n.kind == nkDotExpr: - var obj = safeSemExpr(c, n.sons[0]) - # it can happen that errnously we have collected the fieldname - # of the next line, so we check the 'field' is actually on the same - # line as the object to prevent this from happening: - let prefix = if n.len == 2 and n[1].info.line == n[0].info.line: n[1] else: nil - suggestFieldAccess(c, obj, prefix, outputs) - - #if optIdeDebug in gGlobalOptions: - # echo "expression ", renderTree(obj), " has type ", typeToString(obj.typ) - #writeStackTrace() - else: - #let m = findClosestSym(node) - #if m != nil: - # suggestPrefix(c, m, outputs) - #else: - let prefix = if cp == cpExact: n else: nil - suggestEverything(c, n, prefix, outputs) - + sugExpr(c, node, outputs, cp) elif gIdeCmd == ideCon: var n = findClosestCall(node) if n == nil: n = node @@ -583,17 +588,20 @@ proc suggestSentinel*(c: PContext) = if gIdeCmd != ideSug or c.module.position != gTrackPos.fileIndex: return if c.compilesContextId > 0: return inc(c.compilesContextId) - # suggest everything: - var isLocal = true var outputs: Suggestions = @[] - var scopeN = 0 - for scope in walkScopes(c.currentScope): - if scope == c.topLevelScope: isLocal = false - dec scopeN - for it in items(scope.symbols): - var pm: PrefixMatch - if filterSymNoOpr(it, nil, pm): - outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, PrefixMatch.None, false, scopeN)) + if c.suggestionNode != nil: + sugExpr(c, c.suggestionNode, outputs, cpExact) + else: + # suggest everything: + var isLocal = true + var scopeN = 0 + for scope in walkScopes(c.currentScope): + if scope == c.topLevelScope: isLocal = false + dec scopeN + for it in items(scope.symbols): + var pm: PrefixMatch + if filterSymNoOpr(it, nil, pm): + outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, PrefixMatch.None, false, scopeN)) - produceOutput(outputs) dec(c.compilesContextId) + produceOutput(outputs) From da821a22d9e390d59f66018630fb4c39ba83eaf3 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 9 Mar 2017 11:30:36 +0100 Subject: [PATCH 03/34] nimsuggest: revert cpFuzzy bailouts --- compiler/semdata.nim | 1 - compiler/suggest.nim | 42 ++++++++++++++++---------------- compiler/transf.nim | 5 +++- tools/nimsuggest/tests/tdot1.nim | 2 +- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index d36278d03c..ef23e40f28 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -113,7 +113,6 @@ type recursiveDep*: string suggestionsMade*: bool inTypeContext*: int - suggestionNode*: PNode proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair = result.genericSym = s diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 3c4bf8c091..5630fa34f1 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -405,13 +405,16 @@ proc inCheckpoint*(current: TLineInfo): TCheckPointResult = if current.line >= gTrackPos.line: return cpFuzzy -proc findClosestDot(n: PNode): PNode = +proc findClosestDot(n: PNode; inType: var bool): PNode = if n.kind == nkDotExpr and inCheckpoint(n.info) == cpExact: result = n else: for i in 0.. 0: return inc(c.compilesContextId) var outputs: Suggestions = @[] @@ -589,19 +592,16 @@ proc suggestSentinel*(c: PContext) = if c.compilesContextId > 0: return inc(c.compilesContextId) var outputs: Suggestions = @[] - if c.suggestionNode != nil: - sugExpr(c, c.suggestionNode, outputs, cpExact) - else: - # suggest everything: - var isLocal = true - var scopeN = 0 - for scope in walkScopes(c.currentScope): - if scope == c.topLevelScope: isLocal = false - dec scopeN - for it in items(scope.symbols): - var pm: PrefixMatch - if filterSymNoOpr(it, nil, pm): - outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, PrefixMatch.None, false, scopeN)) + # suggest everything: + var isLocal = true + var scopeN = 0 + for scope in walkScopes(c.currentScope): + if scope == c.topLevelScope: isLocal = false + dec scopeN + for it in items(scope.symbols): + var pm: PrefixMatch + if filterSymNoOpr(it, nil, pm): + outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, PrefixMatch.None, false, scopeN)) dec(c.compilesContextId) produceOutput(outputs) diff --git a/compiler/transf.nim b/compiler/transf.nim index 0c53c0cbf0..771dc58f44 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -291,10 +291,13 @@ proc transformBreak(c: PTransf, n: PNode): PTransNode = else: result = newTransNode(n.kind, n.info, 1) result[0] = lablCopy.PTransNode - else: + elif c.breakSyms.len > 0: + # this check can fail for 'nim check' let labl = c.breakSyms[c.breakSyms.high] result = transformSons(c, n) result[0] = newSymNode(labl).PTransNode + else: + result = n.PTransNode proc unpackTuple(c: PTransf, n: PNode, father: PTransNode) = # XXX: BUG: what if `n` is an expression with side-effects? diff --git a/tools/nimsuggest/tests/tdot1.nim b/tools/nimsuggest/tests/tdot1.nim index d31085f9d9..9ac92f8a54 100644 --- a/tools/nimsuggest/tests/tdot1.nim +++ b/tools/nimsuggest/tests/tdot1.nim @@ -11,4 +11,4 @@ type x, y: int proc main(f: Foo) = - f.#[!]# + if f.#[!]#: From 475579541621ca83cfcbb35df7f5d9ef4236cdfa Mon Sep 17 00:00:00 2001 From: Araq Date: Thu, 9 Mar 2017 14:58:14 +0100 Subject: [PATCH 04/34] nimsuggest: more precise cursor tracking --- compiler/lexer.nim | 116 +++++++++++++----- compiler/msgs.nim | 5 + compiler/suggest.nim | 55 ++------- koch.nim | 8 +- .../nimsuggest => nimsuggest}/crashtester.nim | 0 .../nimsuggest => nimsuggest}/nimsuggest.nim | 1 + .../nimsuggest.nim.cfg | 0 .../nimsuggest.nimble | 0 {tools/nimsuggest => nimsuggest}/sexp.nim | 0 {tools/nimsuggest => nimsuggest}/tester.nim | 0 .../tests/dep_v1.nim | 0 .../tests/dep_v2.nim | 0 .../nimsuggest => nimsuggest}/tests/tchk1.nim | 0 .../tests/tcursor_at_end.nim | 0 .../nimsuggest => nimsuggest}/tests/tdef1.nim | 0 .../nimsuggest => nimsuggest}/tests/tdot1.nim | 0 .../nimsuggest => nimsuggest}/tests/tdot2.nim | 0 .../nimsuggest => nimsuggest}/tests/tdot3.nim | 0 .../tests/tinclude.nim | 0 .../tests/tno_deref.nim | 0 .../tests/tstrutils.nim | 0 .../tests/tsug_regression.nim | 0 .../tests/twithin_macro.nim | 0 .../tests/twithin_macro_prefix.nim | 0 24 files changed, 104 insertions(+), 81 deletions(-) rename {tools/nimsuggest => nimsuggest}/crashtester.nim (100%) rename {tools/nimsuggest => nimsuggest}/nimsuggest.nim (99%) rename {tools/nimsuggest => nimsuggest}/nimsuggest.nim.cfg (100%) rename {tools/nimsuggest => nimsuggest}/nimsuggest.nimble (100%) rename {tools/nimsuggest => nimsuggest}/sexp.nim (100%) rename {tools/nimsuggest => nimsuggest}/tester.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/dep_v1.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/dep_v2.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tchk1.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tcursor_at_end.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tdef1.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tdot1.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tdot2.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tdot3.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tinclude.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tno_deref.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tstrutils.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/tsug_regression.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/twithin_macro.nim (100%) rename {tools/nimsuggest => nimsuggest}/tests/twithin_macro_prefix.nim (100%) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index db370f8b3e..afdf17baab 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -67,6 +67,10 @@ type TTokTypes* = set[TTokType] const + weakTokens = {tkComma, tkSemiColon, tkColon, + tkParRi, tkParDotRi, tkBracketRi, tkBracketDotRi, + tkCurlyRi} # \ + # tokens that should not be considered for previousToken tokKeywordLow* = succ(tkSymbol) tokKeywordHigh* = pred(tkIntLit) TokTypeToStr*: array[TTokType, string] = ["tkInvalid", "[EOF]", @@ -105,6 +109,9 @@ type # so that it is the correct default value base2, base8, base16 + CursorPosition* {.pure.} = enum ## XXX remove this again + None, InToken, BeforeToken, AfterToken + TToken* = object # a Nim token tokType*: TTokType # the type of the token indent*: int # the indentation; != -1 if the token has been @@ -128,8 +135,11 @@ type # needs so much look-ahead currLineIndent*: int strongSpaces*, allowTabs*: bool + cursor*: CursorPosition errorHandler*: TErrorHandler cache*: IdentCache + when defined(nimsuggest): + previousToken: TLineInfo var gLinesCompiled*: int # all lines that have been compiled @@ -203,6 +213,7 @@ proc openLexer*(lex: var TLexer, fileIdx: int32, inputstream: PLLStream; lex.currLineIndent = 0 inc(lex.lineNumber, inputstream.lineOffset) lex.cache = cache + lex.previousToken.fileIndex = fileIdx proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream; cache: IdentCache) = @@ -235,6 +246,41 @@ proc lexMessagePos(L: var TLexer, msg: TMsgKind, pos: int, arg = "") = proc matchTwoChars(L: TLexer, first: char, second: set[char]): bool = result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in second) +template tokenBegin(pos) {.dirty.} = + when defined(nimsuggest): + var colA = getColNumber(L, pos) + +template tokenEnd(pos) {.dirty.} = + when defined(nimsuggest): + let colB = getColNumber(L, pos) + if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and + L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}: + L.cursor = CursorPosition.InToken + gTrackPos.col = colA.int16 + colA = 0 + +template tokenEndIgnore(pos) = + when defined(nimsuggest): + let colB = getColNumber(L, pos) + if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and + L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}: + gTrackPos.fileIndex = trackPosInvalidFileIdx + gTrackPos.line = -1 + colA = 0 + +template tokenEndPrevious(pos) = + when defined(nimsuggest): + # when we detect the cursor in whitespace, we attach the track position + # to the token that came before that, but only if we haven't detected + # the cursor in a string literal or comment: + let colB = getColNumber(L, pos) + if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and + L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}: + L.cursor = CursorPosition.BeforeToken + gTrackPos = L.previousToken + gTrackPosAttached = true + colA = 0 + {.push overflowChecks: off.} # We need to parse the largest uint literal without overflow checks proc unsafeParseUInt(s: string, b: var BiggestInt, start = 0): int = @@ -318,6 +364,7 @@ proc getNumber(L: var TLexer, result: var TToken) = result.literal = "" result.base = base10 startpos = L.bufpos + tokenBegin(startPos) # First stage: find out base, make verifications, build token literal string if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'O'}: @@ -526,6 +573,7 @@ proc getNumber(L: var TLexer, result: var TToken) = lexMessageLitNum(L, errInvalidNumber, startpos) except OverflowError, RangeError: lexMessageLitNum(L, errNumberOutOfRange, startpos) + tokenEnd(postPos-1) L.bufpos = postPos proc handleHexChar(L: var TLexer, xi: var int) = @@ -642,21 +690,11 @@ proc handleCRLF(L: var TLexer, pos: int): int = result = nimlexbase.handleLF(L, pos) else: result = pos -template tokenRange(colA, pos) = - when defined(nimsuggest): - let colB = getColNumber(L, pos) - if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and - L.lineNumber == gTrackPos.line and gIdeCmd == ideSug: - gTrackPos.fileIndex = trackPosInvalidFileIdx - gTrackPos.line = -1 - colA = 0 - proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = var pos = L.bufpos + 1 # skip " var buf = L.buf # put `buf` in a register var line = L.lineNumber # save linenumber for better error message - when defined(nimsuggest): - var colA = getColNumber(L, pos) + tokenBegin(pos) if buf[pos] == '\"' and buf[pos+1] == '\"': tok.tokType = tkTripleStrLit # long string literal: inc(pos, 2) # skip "" @@ -672,18 +710,18 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = of '\"': if buf[pos+1] == '\"' and buf[pos+2] == '\"' and buf[pos+3] != '\"': - tokenRange(colA, pos+2) + tokenEndIgnore(pos+2) L.bufpos = pos + 3 # skip the three """ break add(tok.literal, '\"') inc(pos) of CR, LF: - tokenRange(colA, pos) + tokenEndIgnore(pos) pos = handleCRLF(L, pos) buf = L.buf add(tok.literal, tnl) of nimlexbase.EndOfFile: - tokenRange(colA, pos) + tokenEndIgnore(pos) var line2 = L.lineNumber L.lineNumber = line lexMessagePos(L, errClosingTripleQuoteExpected, L.lineStart) @@ -704,11 +742,11 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = inc(pos, 2) add(tok.literal, '"') else: - tokenRange(colA, pos) + tokenEndIgnore(pos) inc(pos) # skip '"' break elif c in {CR, LF, nimlexbase.EndOfFile}: - tokenRange(colA, pos) + tokenEndIgnore(pos) lexMessage(L, errClosingQuoteExpected) break elif (c == '\\') and not rawMode: @@ -721,6 +759,7 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = L.bufpos = pos proc getCharacter(L: var TLexer, tok: var TToken) = + tokenBegin(L.bufpos) inc(L.bufpos) # skip ' var c = L.buf[L.bufpos] case c @@ -730,12 +769,14 @@ proc getCharacter(L: var TLexer, tok: var TToken) = tok.literal = $c inc(L.bufpos) if L.buf[L.bufpos] != '\'': lexMessage(L, errMissingFinalQuote) + tokenEndIgnore(L.bufpos) inc(L.bufpos) # skip ' proc getSymbol(L: var TLexer, tok: var TToken) = var h: Hash = 0 var pos = L.bufpos var buf = L.buf + tokenBegin(pos) while true: var c = buf[pos] case c @@ -762,6 +803,7 @@ proc getSymbol(L: var TLexer, tok: var TToken) = inc(pos) else: break + tokenEnd(pos-1) h = !$h tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h) L.bufpos = pos @@ -782,6 +824,7 @@ proc endOperator(L: var TLexer, tok: var TToken, pos: int, proc getOperator(L: var TLexer, tok: var TToken) = var pos = L.bufpos var buf = L.buf + tokenBegin(pos) var h: Hash = 0 while true: var c = buf[pos] @@ -789,6 +832,7 @@ proc getOperator(L: var TLexer, tok: var TToken) = h = h !& ord(c) inc(pos) endOperator(L, tok, pos, h) + tokenEnd(pos-1) # advance pos but don't store it in L.bufpos so the next token (which might # be an operator too) gets the preceding spaces: tok.strongSpaceB = 0 @@ -803,8 +847,7 @@ proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int; var pos = start var buf = L.buf var toStrip = 0 - when defined(nimsuggest): - var colA = getColNumber(L, pos) + tokenBegin(pos) # detect the amount of indentation: if isDoc: toStrip = getColNumber(L, pos) @@ -831,20 +874,20 @@ proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int; if isDoc: if buf[pos+1] == '#' and buf[pos+2] == '#': if nesting == 0: - tokenRange(colA, pos+2) + tokenEndIgnore(pos+2) inc(pos, 3) break dec nesting tok.literal.add ']' elif buf[pos+1] == '#': if nesting == 0: - tokenRange(colA, pos+1) + tokenEndIgnore(pos+1) inc(pos, 2) break dec nesting inc pos of CR, LF: - tokenRange(colA, pos) + tokenEndIgnore(pos) pos = handleCRLF(L, pos) buf = L.buf # strip leading whitespace: @@ -856,7 +899,7 @@ proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int; inc pos dec c of nimlexbase.EndOfFile: - tokenRange(colA, pos) + tokenEndIgnore(pos) lexMessagePos(L, errGenerated, pos, "end of multiline comment expected") break else: @@ -867,8 +910,6 @@ proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int; proc scanComment(L: var TLexer, tok: var TToken) = var pos = L.bufpos var buf = L.buf - when defined(nimsuggest): - var colA = getColNumber(L, pos) tok.tokType = tkComment # iNumber contains the number of '\n' in the token tok.iNumber = 0 @@ -876,6 +917,7 @@ proc scanComment(L: var TLexer, tok: var TToken) = if buf[pos+2] == '[': skipMultiLineComment(L, tok, pos+3, true) return + tokenBegin(pos) inc(pos, 2) var toStrip = 0 @@ -889,7 +931,7 @@ proc scanComment(L: var TLexer, tok: var TToken) = if buf[pos] == '\\': lastBackslash = pos+1 add(tok.literal, buf[pos]) inc(pos) - tokenRange(colA, pos) + tokenEndIgnore(pos) pos = handleCRLF(L, pos) buf = L.buf var indent = 0 @@ -908,13 +950,14 @@ proc scanComment(L: var TLexer, tok: var TToken) = else: if buf[pos] > ' ': L.indentAhead = indent - tokenRange(colA, pos) + tokenEndIgnore(pos) break L.bufpos = pos proc skip(L: var TLexer, tok: var TToken) = var pos = L.bufpos var buf = L.buf + tokenBegin(pos) tok.strongSpaceA = 0 while true: case buf[pos] @@ -925,6 +968,7 @@ proc skip(L: var TLexer, tok: var TToken) = if not L.allowTabs: lexMessagePos(L, errTabulatorsAreNotAllowed, pos) inc(pos) of CR, LF: + tokenEndPrevious(pos) pos = handleCRLF(L, pos) buf = L.buf var indent = 0 @@ -951,15 +995,24 @@ proc skip(L: var TLexer, tok: var TToken) = pos = L.bufpos buf = L.buf else: - when defined(nimsuggest): - var colA = getColNumber(L, pos) + tokenBegin(pos) while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}: inc(pos) - tokenRange(colA, pos) + tokenEndIgnore(pos+1) else: break # EndOfFile also leaves the loop + tokenEndPrevious(pos-1) L.bufpos = pos proc rawGetTok*(L: var TLexer, tok: var TToken) = + template atTokenEnd() {.dirty.} = + when defined(nimsuggest): + # we attach the cursor to the last *strong* token + if tok.tokType notin weakTokens: + L.previousToken.line = tok.line.int16 + L.previousToken.col = tok.col.int16 + + when defined(nimsuggest): + L.cursor = CursorPosition.None fillToken(tok) if L.indentAhead >= 0: tok.indent = L.indentAhead @@ -1022,10 +1075,12 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = inc(L.bufpos) of '.': when defined(nimsuggest): - if L.fileIdx == gTrackPos.fileIndex and tok.col+1 == gTrackPos.col and + if L.fileIdx == gTrackPos.fileIndex and tok.col == gTrackPos.col and tok.line == gTrackPos.line and gIdeCmd == ideSug: tok.tokType = tkDot + L.cursor = CursorPosition.InToken inc(L.bufpos) + atTokenEnd() return if L.buf[L.bufpos+1] == ']': tok.tokType = tkBracketDotRi @@ -1092,3 +1147,4 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = tok.tokType = tkInvalid lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')') inc(L.bufpos) + atTokenEnd() diff --git a/compiler/msgs.nim b/compiler/msgs.nim index e50ed0f2a0..b89b4ee93d 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -739,6 +739,8 @@ proc `??`* (info: TLineInfo, filename: string): bool = const trackPosInvalidFileIdx* = -2 # special marker so that no suggestions # are produced within comments and string literals var gTrackPos*: TLineInfo +var gTrackPosAttached*: bool ## whether the tracking position was attached to some + ## close token. type MsgFlag* = enum ## flags altering msgWriteln behavior @@ -863,6 +865,9 @@ proc handleError(msg: TMsgKind, eh: TErrorHandling, s: string) = proc `==`*(a, b: TLineInfo): bool = result = a.line == b.line and a.fileIndex == b.fileIndex +proc exactEquals*(a, b: TLineInfo): bool = + result = a.fileIndex == b.fileIndex and a.line == b.line and a.col == b.col + proc writeContext(lastinfo: TLineInfo) = var info = lastinfo for i in countup(0, len(msgContext) - 1): diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 5630fa34f1..c780f8084e 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -405,39 +405,12 @@ proc inCheckpoint*(current: TLineInfo): TCheckPointResult = if current.line >= gTrackPos.line: return cpFuzzy -proc findClosestDot(n: PNode; inType: var bool): PNode = - if n.kind == nkDotExpr and inCheckpoint(n.info) == cpExact: - result = n - else: - for i in 0.. = current.col and col <= current.col+tokenLen-1: return true -proc findClosestSym(n: PNode): PNode = - if n.kind == nkSym and inCheckpoint(n.info) == cpExact: - result = n - elif n.kind notin {nkNone..nkNilLit}: - for i in 0.. 0: return inc(c.compilesContextId) var outputs: Suggestions = @[] if gIdeCmd == ideSug: - sugExpr(c, node, outputs, cp) + sugExpr(c, n, outputs) elif gIdeCmd == ideCon: - var n = findClosestCall(node) - if n == nil: n = node if n.kind in nkCallKinds: var a = copyNode(n) var x = safeSemExpr(c, n.sons[0]) diff --git a/koch.nim b/koch.nim index 7c01939171..20d01ae98d 100644 --- a/koch.nim +++ b/koch.nim @@ -214,9 +214,9 @@ proc buildNimble(latest: bool) = proc bundleNimsuggest(buildExe: bool) = if buildExe: - nimexec("c --noNimblePath -d:release -p:compiler tools/nimsuggest/nimsuggest.nim") - copyExe("tools/nimsuggest/nimsuggest".exe, "bin/nimsuggest".exe) - removeFile("tools/nimsuggest/nimsuggest".exe) + nimexec("c --noNimblePath -d:release -p:compiler nimsuggest/nimsuggest.nim") + copyExe("nimsuggest/nimsuggest".exe, "bin/nimsuggest".exe) + removeFile("nimsuggest/nimsuggest".exe) proc bundleWinTools() = nimexec("c tools/finish.nim") @@ -253,7 +253,7 @@ proc buildTool(toolname, args: string) = proc buildTools(latest: bool) = let nimsugExe = "bin/nimsuggest".exe nimexec "c --noNimblePath -p:compiler -d:release -o:" & nimsugExe & - " tools/nimsuggest/nimsuggest.nim" + " nimsuggest/nimsuggest.nim" let nimgrepExe = "bin/nimgrep".exe nimexec "c -o:" & nimgrepExe & " tools/nimgrep.nim" diff --git a/tools/nimsuggest/crashtester.nim b/nimsuggest/crashtester.nim similarity index 100% rename from tools/nimsuggest/crashtester.nim rename to nimsuggest/crashtester.nim diff --git a/tools/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim similarity index 99% rename from tools/nimsuggest/nimsuggest.nim rename to nimsuggest/nimsuggest.nim index 1798ac4e99..ee1647fbf1 100644 --- a/tools/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -174,6 +174,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; else: msgs.setDirtyFile(dirtyIdx, nil) gTrackPos = newLineInfo(dirtyIdx, line, col) + gTrackPosAttached = false gErrorCounter = 0 if suggestVersion < 2: graph.usageSym = nil diff --git a/tools/nimsuggest/nimsuggest.nim.cfg b/nimsuggest/nimsuggest.nim.cfg similarity index 100% rename from tools/nimsuggest/nimsuggest.nim.cfg rename to nimsuggest/nimsuggest.nim.cfg diff --git a/tools/nimsuggest/nimsuggest.nimble b/nimsuggest/nimsuggest.nimble similarity index 100% rename from tools/nimsuggest/nimsuggest.nimble rename to nimsuggest/nimsuggest.nimble diff --git a/tools/nimsuggest/sexp.nim b/nimsuggest/sexp.nim similarity index 100% rename from tools/nimsuggest/sexp.nim rename to nimsuggest/sexp.nim diff --git a/tools/nimsuggest/tester.nim b/nimsuggest/tester.nim similarity index 100% rename from tools/nimsuggest/tester.nim rename to nimsuggest/tester.nim diff --git a/tools/nimsuggest/tests/dep_v1.nim b/nimsuggest/tests/dep_v1.nim similarity index 100% rename from tools/nimsuggest/tests/dep_v1.nim rename to nimsuggest/tests/dep_v1.nim diff --git a/tools/nimsuggest/tests/dep_v2.nim b/nimsuggest/tests/dep_v2.nim similarity index 100% rename from tools/nimsuggest/tests/dep_v2.nim rename to nimsuggest/tests/dep_v2.nim diff --git a/tools/nimsuggest/tests/tchk1.nim b/nimsuggest/tests/tchk1.nim similarity index 100% rename from tools/nimsuggest/tests/tchk1.nim rename to nimsuggest/tests/tchk1.nim diff --git a/tools/nimsuggest/tests/tcursor_at_end.nim b/nimsuggest/tests/tcursor_at_end.nim similarity index 100% rename from tools/nimsuggest/tests/tcursor_at_end.nim rename to nimsuggest/tests/tcursor_at_end.nim diff --git a/tools/nimsuggest/tests/tdef1.nim b/nimsuggest/tests/tdef1.nim similarity index 100% rename from tools/nimsuggest/tests/tdef1.nim rename to nimsuggest/tests/tdef1.nim diff --git a/tools/nimsuggest/tests/tdot1.nim b/nimsuggest/tests/tdot1.nim similarity index 100% rename from tools/nimsuggest/tests/tdot1.nim rename to nimsuggest/tests/tdot1.nim diff --git a/tools/nimsuggest/tests/tdot2.nim b/nimsuggest/tests/tdot2.nim similarity index 100% rename from tools/nimsuggest/tests/tdot2.nim rename to nimsuggest/tests/tdot2.nim diff --git a/tools/nimsuggest/tests/tdot3.nim b/nimsuggest/tests/tdot3.nim similarity index 100% rename from tools/nimsuggest/tests/tdot3.nim rename to nimsuggest/tests/tdot3.nim diff --git a/tools/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim similarity index 100% rename from tools/nimsuggest/tests/tinclude.nim rename to nimsuggest/tests/tinclude.nim diff --git a/tools/nimsuggest/tests/tno_deref.nim b/nimsuggest/tests/tno_deref.nim similarity index 100% rename from tools/nimsuggest/tests/tno_deref.nim rename to nimsuggest/tests/tno_deref.nim diff --git a/tools/nimsuggest/tests/tstrutils.nim b/nimsuggest/tests/tstrutils.nim similarity index 100% rename from tools/nimsuggest/tests/tstrutils.nim rename to nimsuggest/tests/tstrutils.nim diff --git a/tools/nimsuggest/tests/tsug_regression.nim b/nimsuggest/tests/tsug_regression.nim similarity index 100% rename from tools/nimsuggest/tests/tsug_regression.nim rename to nimsuggest/tests/tsug_regression.nim diff --git a/tools/nimsuggest/tests/twithin_macro.nim b/nimsuggest/tests/twithin_macro.nim similarity index 100% rename from tools/nimsuggest/tests/twithin_macro.nim rename to nimsuggest/tests/twithin_macro.nim diff --git a/tools/nimsuggest/tests/twithin_macro_prefix.nim b/nimsuggest/tests/twithin_macro_prefix.nim similarity index 100% rename from tools/nimsuggest/tests/twithin_macro_prefix.nim rename to nimsuggest/tests/twithin_macro_prefix.nim From 57ea01309eb7ff1425fb3bc7907c67139fc50edf Mon Sep 17 00:00:00 2001 From: Araq Date: Thu, 9 Mar 2017 17:09:39 +0100 Subject: [PATCH 05/34] nimsuggest: more things work --- compiler/lexer.nim | 3 ++- compiler/sem.nim | 4 ++++ compiler/semexprs.nim | 6 +++++- compiler/suggest.nim | 28 ++++++++++++++++++++-------- nimsuggest/nimsuggest.nim | 33 +++++++++++++++++++-------------- nimsuggest/nimsuggest.nim.cfg | 2 +- 6 files changed, 51 insertions(+), 25 deletions(-) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index afdf17baab..04419d92f8 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -213,7 +213,8 @@ proc openLexer*(lex: var TLexer, fileIdx: int32, inputstream: PLLStream; lex.currLineIndent = 0 inc(lex.lineNumber, inputstream.lineOffset) lex.cache = cache - lex.previousToken.fileIndex = fileIdx + when defined(nimsuggest): + lex.previousToken.fileIndex = fileIdx proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream; cache: IdentCache) = diff --git a/compiler/sem.nim b/compiler/sem.nim index 21a5c435a9..2ad506b411 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -167,6 +167,8 @@ proc commonType*(x, y: PType): PType = proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym = result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) + when defined(nimsuggest): + suggestDecl(c, n, result) proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = proc `$`(kind: TSymKind): string = substr(system.`$`(kind), 2).toLowerAscii @@ -191,6 +193,8 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) #if kind in {skForVar, skLet, skVar} and result.owner.kind == skModule: # incl(result.flags, sfGlobal) + when defined(nimsuggest): + suggestDecl(c, n, result) proc semIdentVis(c: PContext, kind: TSymKind, n: PNode, allowed: TSymFlags): PSym diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index a419cd0008..3dc1745276 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1054,7 +1054,9 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = # here at all! #if isSymChoice(n.sons[1]): return when defined(nimsuggest): - if gCmd == cmdIdeTools: suggestExpr(c, n) + if gCmd == cmdIdeTools: + suggestExpr(c, n) + if exactEquals(gTrackPos, n[1].info): suggestExprNoCheck(c, n) var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule}) if s != nil: @@ -2234,6 +2236,8 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: # check if it is an expression macro: checkMinSonsLen(n, 1) + #when defined(nimsuggest): + # if gIdeCmd == ideCon and gTrackPos == n.info: suggestExprNoCheck(c, n) let mode = if nfDotField in n.flags: {} else: {checkUndeclared} var s = qualifiedLookUp(c, n.sons[0], mode) if s != nil: diff --git a/compiler/suggest.nim b/compiler/suggest.nim index c780f8084e..e5a9f424af 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -181,7 +181,7 @@ proc `$`*(suggest: Suggest): string = result.add(sep) when not defined(noDocgen): result.add(suggest.doc.escape) - if suggestVersion == 2: + if suggestVersion == 0: result.add(sep) result.add($suggest.quality) if suggest.section == ideSug: @@ -202,6 +202,10 @@ proc suggestResult(s: Suggest) = proc produceOutput(a: var Suggestions) = if gIdeCmd in {ideSug, ideCon}: a.sort cmpSuggestions + when false: + # debug code + writeStackTrace() + if a.len > 10: a.setLen(10) if not isNil(suggestionResultHook): for s in a: suggestionResultHook(s) @@ -332,7 +336,7 @@ proc suggestEverything(c: PContext, n, f: PNode, outputs: var Suggestions) = var pm: PrefixMatch if filterSym(it, f, pm): outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, pm, c.inTypeContext > 0, scopeN)) - if scope == c.topLevelScope and f.isNil: break + #if scope == c.topLevelScope and f.isNil: break proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) = # special code that deals with ``myObj.``. `n` is NOT the nkDotExpr-node, but @@ -340,7 +344,7 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) var typ = n.typ var pm: PrefixMatch when defined(nimsuggest): - if n.kind == nkSym and n.sym.kind == skError and suggestVersion == 2: + if n.kind == nkSym and n.sym.kind == skError and suggestVersion == 0: # consider 'foo.|' where 'foo' is some not imported module. let fullPath = findModule(n.sym.name.s, n.info.toFullPath) if fullPath.len == 0: @@ -429,7 +433,7 @@ var lastLineInfo*: TLineInfo proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) = - if suggestVersion < 2: + if suggestVersion == 1: if usageSym == nil and isTracked(info, s.name.s.len): usageSym = s suggestResult(symToSuggest(s, isLocal=false, $ideUse, 100, PrefixMatch.None, false, 0)) @@ -460,7 +464,7 @@ proc ensureSeq[T](x: var seq[T]) = proc suggestSym*(info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} = ## misnamed: should be 'symDeclared' when defined(nimsuggest): - if suggestVersion == 2: + if suggestVersion == 0: if s.allUsages.isNil: s.allUsages = @[info] else: @@ -509,7 +513,6 @@ proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) = # line as the object to prevent this from happening: let prefix = if n.len == 2 and n[1].info.line == n[0].info.line and not gTrackPosAttached: n[1] else: nil - echo n[1].kind suggestFieldAccess(c, obj, prefix, outputs) #if optIdeDebug in gGlobalOptions: @@ -519,8 +522,7 @@ proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) = let prefix = if gTrackPosAttached: nil else: n suggestEverything(c, n, prefix, outputs) -proc suggestExpr*(c: PContext, n: PNode) = - if not exactEquals(gTrackPos, n.info): return +proc suggestExprNoCheck*(c: PContext, n: PNode) = # This keeps semExpr() from coming here recursively: if c.compilesContextId > 0: return inc(c.compilesContextId) @@ -545,6 +547,16 @@ proc suggestExpr*(c: PContext, n: PNode) = produceOutput(outputs) suggestQuit() +proc suggestExpr*(c: PContext, n: PNode) = + if exactEquals(gTrackPos, n.info): suggestExprNoCheck(c, n) + +proc suggestDecl*(c: PContext, n: PNode; s: PSym) = + let attached = gTrackPosAttached + if attached: inc(c.inTypeContext) + defer: + if attached: dec(c.inTypeContext) + suggestExpr(c, n) + proc suggestStmt*(c: PContext, n: PNode) = suggestExpr(c, n) diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index ee1647fbf1..9a1da0e511 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -38,10 +38,9 @@ Options: --epc use emacs epc mode --debug enable debug output --log enable verbose logging to nimsuggest.log file - --v2 use version 2 of the protocol; more features and - much faster + --v1 use version 1 of the protocol; for backwards compatibility --refresh perform automatic refreshes to keep the analysis precise - --tester implies --v2 and --stdin and outputs a line + --tester implies --stdin and outputs a line '""" & DummyEof & """' for the tester The server then listens to the connection and takes line-based commands. @@ -50,7 +49,7 @@ In addition, all command line options of Nim that do not affect code generation are supported. """ type - Mode = enum mstdin, mtcp, mepc, mcmdline + Mode = enum mstdin, mtcp, mepc, mcmdsug, mcmdcon CachedMsg = object info: TLineInfo msg: string @@ -165,7 +164,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; else: msgs.structuredErrorHook = nil msgs.writelnHook = proc (s: string) = discard - if cmd == ideUse and suggestVersion != 2: + if cmd == ideUse and suggestVersion != 0: graph.resetAllModules() var isKnownFile = true let dirtyIdx = file.fileInfoIdx(isKnownFile) @@ -176,11 +175,11 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; gTrackPos = newLineInfo(dirtyIdx, line, col) gTrackPosAttached = false gErrorCounter = 0 - if suggestVersion < 2: + if suggestVersion == 1: graph.usageSym = nil if not isKnownFile: graph.compileProject(cache) - if suggestVersion == 2 and gIdeCmd in {ideUse, ideDus} and + if suggestVersion == 0 and gIdeCmd in {ideUse, ideDus} and dirtyfile.len == 0: discard "no need to recompile anything" else: @@ -190,7 +189,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; if gIdeCmd != ideMod: graph.compileProject(cache, modIdx) if gIdeCmd in {ideUse, ideDus}: - let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: graph.usageSym + let u = if suggestVersion != 1: graph.symFromInfo(gTrackPos) else: graph.usageSym if u != nil: listUsages(u) else: @@ -503,8 +502,10 @@ proc mainCommand(graph: ModuleGraph; cache: IdentCache) = of mstdin: createThread(inputThread, replStdin, (gPort, gAddress)) of mtcp: createThread(inputThread, replTcp, (gPort, gAddress)) of mepc: createThread(inputThread, replEpc, (gPort, gAddress)) - of mcmdline: createThread(inputThread, replCmdline, + of mcmdsug: createThread(inputThread, replCmdline, (gPort, "sug \"" & options.gProjectFull & "\":" & gAddress)) + of mcmdcon: createThread(inputThread, replCmdline, + (gPort, "con \"" & options.gProjectFull & "\":" & gAddress)) mainThread(graph, cache) joinThread(inputThread) close(requests) @@ -525,17 +526,21 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = gAddress = p.val gMode = mtcp of "stdin": gMode = mstdin - of "cmdline": - gMode = mcmdline - suggestVersion = 2 + of "cmdsug": + gMode = mcmdsug gAddress = p.val + incl(gGlobalOptions, optIdeDebug) + of "cmdcon": + gMode = mcmdcon + gAddress = p.val + incl(gGlobalOptions, optIdeDebug) of "epc": gMode = mepc gVerbosity = 0 # Port number gotta be first. of "debug": incl(gGlobalOptions, optIdeDebug) - of "v2": suggestVersion = 2 + of "v2": suggestVersion = 0 + of "v1": suggestVersion = 1 of "tester": - suggestVersion = 2 gMode = mstdin gEmitEof = true gRefresh = false diff --git a/nimsuggest/nimsuggest.nim.cfg b/nimsuggest/nimsuggest.nim.cfg index 2e14a4dd35..38e74b3c7d 100644 --- a/nimsuggest/nimsuggest.nim.cfg +++ b/nimsuggest/nimsuggest.nim.cfg @@ -22,4 +22,4 @@ define:nimsuggest --path:"$nim" --threads:on --noNimblePath ---path:"../../compiler" +--path:"../compiler" From d1119c120d0b69897d84fb8c213f3e85c0856c88 Mon Sep 17 00:00:00 2001 From: konqoro Date: Thu, 9 Mar 2017 23:37:44 +0200 Subject: [PATCH 06/34] Fix links to manual (#5500) --- lib/system.nim | 56 +++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index 74dca461a2..b7e2c6ebac 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -417,7 +417,7 @@ type ## Base exception class. ## ## Each exception has to inherit from `Exception`. See the full `exception - ## hierarchy`_. + ## hierarchy `_. parent*: ref Exception ## parent exception (can be used as a stack) name*: cstring ## The exception's name is its Nim identifier. ## This field is filled automatically in the @@ -430,51 +430,51 @@ type SystemError* = object of Exception ## \ ## Abstract class for exceptions that the runtime system raises. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. IOError* = object of SystemError ## \ ## Raised if an IO error occurred. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. EOFError* = object of IOError ## \ ## Raised if an IO "end of file" error occurred. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. OSError* = object of SystemError ## \ ## Raised if an operating system service failed. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. errorCode*: int32 ## OS-defined error code describing this error. LibraryError* = object of OSError ## \ ## Raised if a dynamic library could not be loaded. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. ResourceExhaustedError* = object of SystemError ## \ ## Raised if a resource request could not be fulfilled. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. ArithmeticError* = object of Exception ## \ ## Raised if any kind of arithmetic error occurred. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. DivByZeroError* = object of ArithmeticError ## \ ## Raised for runtime integer divide-by-zero errors. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. OverflowError* = object of ArithmeticError ## \ ## Raised for runtime integer overflows. ## ## This happens for calculations whose results are too large to fit in the - ## provided bits. See the full `exception hierarchy`_. + ## provided bits. See the full `exception hierarchy `_. AccessViolationError* = object of Exception ## \ ## Raised for invalid memory access errors ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. AssertionError* = object of Exception ## \ ## Raised when assertion is proved wrong. ## ## Usually the result of using the `assert() template <#assert>`_. See the - ## full `exception hierarchy`_. + ## full `exception hierarchy `_. ValueError* = object of Exception ## \ ## Raised for string and object conversion errors. KeyError* = object of ValueError ## \ @@ -482,66 +482,66 @@ type ## ## Mostly used by the `tables `_ module, it can also be raised ## by other collection modules like `sets `_ or `strtabs - ## `_. See the full `exception hierarchy`_. + ## `_. See the full `exception hierarchy `_. OutOfMemError* = object of SystemError ## \ ## Raised for unsuccessful attempts to allocate memory. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. IndexError* = object of Exception ## \ ## Raised if an array index is out of bounds. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. FieldError* = object of Exception ## \ ## Raised if a record field is not accessible because its dicriminant's ## value does not fit. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. RangeError* = object of Exception ## \ ## Raised if a range check error occurred. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. StackOverflowError* = object of SystemError ## \ ## Raised if the hardware stack used for subroutine calls overflowed. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. ReraiseError* = object of Exception ## \ ## Raised if there is no exception to reraise. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. ObjectAssignmentError* = object of Exception ## \ ## Raised if an object gets assigned to its parent's object. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. ObjectConversionError* = object of Exception ## \ ## Raised if an object is converted to an incompatible object type. ## You can use ``of`` operator to check if conversion will succeed. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. FloatingPointError* = object of Exception ## \ ## Base class for floating point exceptions. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. FloatInvalidOpError* = object of FloatingPointError ## \ ## Raised by invalid operations according to IEEE. ## ## Raised by ``0.0/0.0``, for example. See the full `exception - ## hierarchy`_. + ## hierarchy `_. FloatDivByZeroError* = object of FloatingPointError ## \ ## Raised by division by zero. ## ## Divisor is zero and dividend is a finite nonzero number. See the full - ## `exception hierarchy`_. + ## `exception hierarchy `_. FloatOverflowError* = object of FloatingPointError ## \ ## Raised for overflows. ## ## The operation produced a result that exceeds the range of the exponent. - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. FloatUnderflowError* = object of FloatingPointError ## \ ## Raised for underflows. ## ## The operation produced a result that is too small to be represented as a - ## normal number. See the full `exception hierarchy`_. + ## normal number. See the full `exception hierarchy `_. FloatInexactError* = object of FloatingPointError ## \ ## Raised for inexact results. ## @@ -549,11 +549,11 @@ type ## precision -- for example: ``2.0 / 3.0, log(1.1)`` ## ## **NOTE**: Nim currently does not detect these! See the full - ## `exception hierarchy`_. + ## `exception hierarchy `_. DeadThreadError* = object of Exception ## \ ## Raised if it is attempted to send a message to a dead thread. ## - ## See the full `exception hierarchy`_. + ## See the full `exception hierarchy `_. {.deprecated: [TObject: RootObj, PObject: RootRef, TEffect: RootEffect, FTime: TimeEffect, FIO: IOEffect, FReadIO: ReadIOEffect, From 556b564c7d5f0a980c189af5c2c1b8c9b234f386 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 10 Mar 2017 00:18:24 +0100 Subject: [PATCH 07/34] nimsuggest: make tests green again --- compiler/ast.nim | 4 ++-- compiler/parser.nim | 4 ++-- nimsuggest/nimsuggest.nim | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 66fbe577ca..177def594e 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -736,7 +736,7 @@ type TLibKind* = enum libHeader, libDynamic - + TLib* = object # also misused for headers! kind*: TLibKind generated*: bool # needed for the backends: @@ -744,7 +744,7 @@ type name*: Rope path*: PNode # can be a string literal! - + CompilesId* = int ## id that is used for the caching logic within ## ``system.compiles``. See the seminst module. TInstantiation* = object diff --git a/compiler/parser.nim b/compiler/parser.nim index d34a6d88ae..0503b29eb7 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -2002,12 +2002,12 @@ proc parseStmt(p: var TParser): PNode = break p.hasProgress = false var a = complexOrSimpleStmt(p) - if not p.hasProgress and p.tok.tokType == tkEof: break - if a.kind != nkEmpty and p.hasProgress: + if a.kind != nkEmpty: addSon(result, a) else: parMessage(p, errExprExpected, p.tok) getTok(p) + if not p.hasProgress and p.tok.tokType == tkEof: break else: # the case statement is only needed for better error messages: case p.tok.tokType diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index 9a1da0e511..8f2e4ff1ed 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -424,7 +424,7 @@ proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache; cachedMsgs: Cac else: if gIdeCmd == ideChk: for cm in cachedMsgs: errorHook(cm.info, cm.msg, cm.sev) - execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache) + execute(gIdeCmd, orig, dirtyfile, line, col, graph, cache) sentinel() proc recompileFullProject(graph: ModuleGraph; cache: IdentCache) = From 16cef36cfd7f0b6d7458b77a425da5082696e866 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 10 Mar 2017 09:56:04 +0100 Subject: [PATCH 08/34] nimsuggest now uses 0 based columsn consistently --- nimsuggest/tester.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nimsuggest/tester.nim b/nimsuggest/tester.nim index 0bee142549..16f70beb52 100644 --- a/nimsuggest/tester.nim +++ b/nimsuggest/tester.nim @@ -30,8 +30,8 @@ proc parseTest(filename: string; epcMode=false): Test = var markers = newSeq[string]() var i = 1 for x in lines(filename): - let marker = x.find(cursorMarker)+1 - if marker > 0: + let marker = x.find(cursorMarker) + if marker >= 0: if epcMode: markers.add "(\"" & filename & "\" " & $i & " " & $marker & " \"" & result.dest & "\")" else: From db888475dcbd9d5a138ff191bdf3e188d750805f Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 10 Mar 2017 09:57:36 +0100 Subject: [PATCH 09/34] nimsuggest: make tests green again --- compiler/lexer.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 04419d92f8..b2c2a78778 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -253,7 +253,7 @@ template tokenBegin(pos) {.dirty.} = template tokenEnd(pos) {.dirty.} = when defined(nimsuggest): - let colB = getColNumber(L, pos) + let colB = getColNumber(L, pos)+1 if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}: L.cursor = CursorPosition.InToken @@ -1076,10 +1076,11 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = inc(L.bufpos) of '.': when defined(nimsuggest): - if L.fileIdx == gTrackPos.fileIndex and tok.col == gTrackPos.col and + if L.fileIdx == gTrackPos.fileIndex and tok.col+1 == gTrackPos.col and tok.line == gTrackPos.line and gIdeCmd == ideSug: tok.tokType = tkDot L.cursor = CursorPosition.InToken + gTrackPos.col = tok.col.int16 inc(L.bufpos) atTokenEnd() return From 62ef5dfec832e0d9f68033118c02fc69df9e222a Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 10 Mar 2017 12:02:55 +0200 Subject: [PATCH 10/34] new debugging helper to replace and friends --- compiler/astalgo.nim | 17 +++++++++++++++++ compiler/options.nim | 5 ----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 226d5ee42c..161e4d6372 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -67,6 +67,23 @@ proc debug*(n: PSym) {.deprecated.} proc debug*(n: PType) {.deprecated.} proc debug*(n: PNode) {.deprecated.} +template mdbg*: bool {.dirty.} = + when compiles(c.module): + c.module.fileIdx == gProjectMainIdx + elif compiles(m.c.module): + m.c.module.fileIdx == gProjectMainIdx + elif compiles(cl.c.module): + cl.c.module.fileIdx == gProjectMainIdx + elif compiles(p): + when compiles(p.lex): + p.lex.fileIdx == gProjectMainIdx + else: + p.module.module.fileIdx == gProjectMainIdx + elif compiles(L.fileIdx): + L.fileIdx == gProjectMainIdx + else: + false + # --------------------------- ident tables ---------------------------------- proc idTableGet*(t: TIdTable, key: PIdObj): RootRef proc idTableGet*(t: TIdTable, key: int): RootRef diff --git a/compiler/options.nim b/compiler/options.nim index c6d0160951..349f9dae16 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -420,11 +420,6 @@ proc binaryStrSearch*(x: openArray[string], y: string): int = return mid result = - 1 -template nimdbg*: untyped = c.module.fileIdx == gProjectMainIdx -template cnimdbg*: untyped = p.module.module.fileIdx == gProjectMainIdx -template pnimdbg*: untyped = p.lex.fileIdx == gProjectMainIdx -template lnimdbg*: untyped = L.fileIdx == gProjectMainIdx - proc parseIdeCmd*(s: string): IdeCmd = case s: of "sug": ideSug From 68181e6da2ee22ad3411cd55f1c8b04df3d375cb Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 10 Mar 2017 11:29:16 +0100 Subject: [PATCH 11/34] nimsuggest: maxresults limit; fixed local symbol usages priorizations --- compiler/suggest.nim | 89 ++++++++++++++++++-------------------- nimsuggest/nimsuggest.nim | 3 ++ nimsuggest/tester.nim | 3 +- nimsuggest/tests/tdot4.nim | 16 +++++++ 4 files changed, 62 insertions(+), 49 deletions(-) create mode 100644 nimsuggest/tests/tdot4.nim diff --git a/compiler/suggest.nim b/compiler/suggest.nim index e5a9f424af..04f5baac48 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -63,6 +63,7 @@ type var suggestionResultHook*: proc (result: Suggest) {.closure.} suggestVersion*: int + suggestMaxResults* = 10_000 #template sectionSuggest(): expr = "##begin\n" & getStackTrace() & "##end\n" @@ -104,11 +105,11 @@ proc cmpSuggestions(a, b: Suggest): int = # independent of hashing order: result = cmp(a.name.s, b.name.s) -proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo; +proc symToSuggest(s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo; quality: range[0..100]; prefix: PrefixMatch; inTypeContext: bool; scope: int): Suggest = new(result) - result.section = parseIdeCmd(section) + result.section = section result.quality = quality result.isGlobal = sfGlobal in s.flags result.tokenLen = s.name.s.len @@ -120,15 +121,10 @@ proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo; result.globalUsages = s.allUsages.len var c = 0 for u in s.allUsages: - if u.fileIndex == li.fileIndex: inc c + if u.fileIndex == info.fileIndex: inc c result.localUsages = c - if optIdeTerse in gGlobalOptions: - result.symkind = s.kind - result.filePath = toFullPath(li) - result.line = toLinenumber(li) - result.column = toColumn(li) - else: - result.symkind = s.kind + result.symkind = s.kind + if optIdeTerse notin gGlobalOptions: result.qualifiedPath = @[] if not isLocal and s.kind != skModule: let ow = s.owner @@ -143,11 +139,12 @@ proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo; result.forth = typeToString(s.typ) else: result.forth = "" - result.filePath = toFullPath(li) - result.line = toLinenumber(li) - result.column = toColumn(li) when not defined(noDocgen): result.doc = s.extractDocComment + let infox = if section in {ideUse, ideHighlight, ideOutline}: info else: s.info + result.filePath = toFullPath(infox) + result.line = toLinenumber(infox) + result.column = toColumn(infox) proc `$`*(suggest: Suggest): string = result = $suggest.section @@ -188,11 +185,6 @@ proc `$`*(suggest: Suggest): string = result.add(sep) result.add($suggest.prefix) -proc symToSuggest(s: PSym, isLocal: bool, section: string; - quality: range[0..100], prefix: PrefixMatch; inTypeContext: bool; - scope: int): Suggest = - result = symToSuggest(s, isLocal, section, s.info, quality, prefix, inTypeContext, scope) - proc suggestResult(s: Suggest) = if not isNil(suggestionResultHook): suggestionResultHook(s) @@ -205,7 +197,7 @@ proc produceOutput(a: var Suggestions) = when false: # debug code writeStackTrace() - if a.len > 10: a.setLen(10) + if a.len > suggestMaxResults: a.setLen(suggestMaxResults) if not isNil(suggestionResultHook): for s in a: suggestionResultHook(s) @@ -241,10 +233,10 @@ proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} = result = true break -proc suggestField(c: PContext, s: PSym; f: PNode; outputs: var Suggestions) = +proc suggestField(c: PContext, s: PSym; f: PNode; info: TLineInfo; outputs: var Suggestions) = var pm: PrefixMatch if filterSym(s, f, pm) and fieldVisible(c, s): - outputs.add(symToSuggest(s, isLocal=true, $ideSug, 100, pm, c.inTypeContext > 0, 0)) + outputs.add(symToSuggest(s, isLocal=true, ideSug, info, 100, pm, c.inTypeContext > 0, 0)) proc getQuality(s: PSym): range[0..100] = if s.typ != nil and s.typ.len > 1: @@ -263,25 +255,25 @@ template wholeSymTab(cond, section: untyped) = let it {.inject.} = item var pm {.inject.}: PrefixMatch if cond: - outputs.add(symToSuggest(it, isLocal = isLocal, section, getQuality(it), + outputs.add(symToSuggest(it, isLocal = isLocal, section, info, getQuality(it), pm, c.inTypeContext > 0, scopeN)) -proc suggestSymList(c: PContext, list, f: PNode, outputs: var Suggestions) = +proc suggestSymList(c: PContext, list, f: PNode; info: TLineInfo, outputs: var Suggestions) = for i in countup(0, sonsLen(list) - 1): if list.sons[i].kind == nkSym: - suggestField(c, list.sons[i].sym, f, outputs) + suggestField(c, list.sons[i].sym, f, info, outputs) #else: InternalError(list.info, "getSymFromList") -proc suggestObject(c: PContext, n, f: PNode, outputs: var Suggestions) = +proc suggestObject(c: PContext, n, f: PNode; info: TLineInfo, outputs: var Suggestions) = case n.kind of nkRecList: - for i in countup(0, sonsLen(n)-1): suggestObject(c, n.sons[i], f, outputs) + for i in countup(0, sonsLen(n)-1): suggestObject(c, n.sons[i], f, info, outputs) of nkRecCase: var L = sonsLen(n) if L > 0: - suggestObject(c, n.sons[0], f, outputs) - for i in countup(1, L-1): suggestObject(c, lastSon(n.sons[i]), f, outputs) - of nkSym: suggestField(c, n.sym, f, outputs) + suggestObject(c, n.sons[0], f, info, outputs) + for i in countup(1, L-1): suggestObject(c, lastSon(n.sons[i]), f, info, outputs) + of nkSym: suggestField(c, n.sym, f, info, outputs) else: discard proc nameFits(c: PContext, s: PSym, n: PNode): bool = @@ -305,8 +297,9 @@ proc argsFit(c: PContext, candidate: PSym, n, nOrig: PNode): bool = result = false proc suggestCall(c: PContext, n, nOrig: PNode, outputs: var Suggestions) = + let info = n.info wholeSymTab(filterSym(it, nil, pm) and nameFits(c, it, n) and argsFit(c, it, n, nOrig), - $ideCon) + ideCon) proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} = if s.typ != nil and sonsLen(s.typ) > 1 and s.typ.sons[1] != nil: @@ -323,7 +316,8 @@ proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} = proc suggestOperations(c: PContext, n, f: PNode, typ: PType, outputs: var Suggestions) = assert typ != nil - wholeSymTab(filterSymNoOpr(it, f, pm) and typeFits(c, it, typ), $ideSug) + let info = n.info + wholeSymTab(filterSymNoOpr(it, f, pm) and typeFits(c, it, typ), ideSug) proc suggestEverything(c: PContext, n, f: PNode, outputs: var Suggestions) = # do not produce too many symbols: @@ -335,7 +329,8 @@ proc suggestEverything(c: PContext, n, f: PNode, outputs: var Suggestions) = for it in items(scope.symbols): var pm: PrefixMatch if filterSym(it, f, pm): - outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, pm, c.inTypeContext > 0, scopeN)) + outputs.add(symToSuggest(it, isLocal = isLocal, ideSug, n.info, 0, pm, + c.inTypeContext > 0, scopeN)) #if scope == c.topLevelScope and f.isNil: break proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) = @@ -356,8 +351,8 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) else: for it in items(n.sym.tab): if filterSym(it, field, pm): - outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -100)) - outputs.add(symToSuggest(m, isLocal=false, $ideMod, 100, PrefixMatch.None, + outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -100)) + outputs.add(symToSuggest(m, isLocal=false, ideMod, n.info, 100, PrefixMatch.None, c.inTypeContext > 0, -99)) if typ == nil: @@ -367,11 +362,11 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) # all symbols accessible, because we are in the current module: for it in items(c.topLevelScope.symbols): if filterSym(it, field, pm): - outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -99)) + outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99)) else: for it in items(n.sym.tab): if filterSym(it, field, pm): - outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -99)) + outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99)) else: # fallback: suggestEverything(c, n, field, outputs) @@ -379,7 +374,7 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) # look up if the identifier belongs to the enum: var t = typ while t != nil: - suggestSymList(c, t.n, field, outputs) + suggestSymList(c, t.n, field, n.info, outputs) t = t.sons[0] suggestOperations(c, n, field, typ, outputs) else: @@ -388,11 +383,11 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) if typ.kind == tyObject: var t = typ while true: - suggestObject(c, t.n, field, outputs) + suggestObject(c, t.n, field, n.info, outputs) if t.sons[0] == nil: break t = skipTypes(t.sons[0], skipPtrs) elif typ.kind == tyTuple and typ.n != nil: - suggestSymList(c, typ.n, field, outputs) + suggestSymList(c, typ.n, field, n.info, outputs) suggestOperations(c, n, field, orig, outputs) if typ != orig: suggestOperations(c, n, field, typ, outputs) @@ -436,23 +431,23 @@ proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) = if suggestVersion == 1: if usageSym == nil and isTracked(info, s.name.s.len): usageSym = s - suggestResult(symToSuggest(s, isLocal=false, $ideUse, 100, PrefixMatch.None, false, 0)) + suggestResult(symToSuggest(s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) elif s == usageSym: if lastLineInfo != info: - suggestResult(symToSuggest(s, isLocal=false, $ideUse, info, 100, PrefixMatch.None, false, 0)) + suggestResult(symToSuggest(s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) lastLineInfo = info when defined(nimsuggest): proc listUsages*(s: PSym) = #echo "usages ", len(s.allUsages) for info in s.allUsages: - let x = if info == s.info and info.col == s.info.col: "def" else: "use" + let x = if info == s.info and info.col == s.info.col: ideDef else: ideUse suggestResult(symToSuggest(s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0)) proc findDefinition(info: TLineInfo; s: PSym) = if s.isNil: return if isTracked(info, s.name.s.len): - suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100, PrefixMatch.None, false, 0)) + suggestResult(symToSuggest(s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) suggestQuit() proc ensureIdx[T](x: var T, y: int) = @@ -476,13 +471,13 @@ proc suggestSym*(info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.in findDefinition(info, s) elif gIdeCmd == ideDus and s != nil: if isTracked(info, s.name.s.len): - suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100, PrefixMatch.None, false, 0)) + suggestResult(symToSuggest(s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) findUsages(info, s, usageSym) elif gIdeCmd == ideHighlight and info.fileIndex == gTrackPos.fileIndex: - suggestResult(symToSuggest(s, isLocal=false, $ideHighlight, info, 100, PrefixMatch.None, false, 0)) + suggestResult(symToSuggest(s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) elif gIdeCmd == ideOutline and info.fileIndex == gTrackPos.fileIndex and isDecl: - suggestResult(symToSuggest(s, isLocal=false, $ideOutline, info, 100, PrefixMatch.None, false, 0)) + suggestResult(symToSuggest(s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) proc markUsed(info: TLineInfo; s: PSym; usageSym: var PSym) = incl(s.flags, sfUsed) @@ -574,7 +569,7 @@ proc suggestSentinel*(c: PContext) = for it in items(scope.symbols): var pm: PrefixMatch if filterSymNoOpr(it, nil, pm): - outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, PrefixMatch.None, false, scopeN)) + outputs.add(symToSuggest(it, isLocal = isLocal, ideSug, newLineInfo(gTrackPos.fileIndex, -1, -1), 0, PrefixMatch.None, false, scopeN)) dec(c.compilesContextId) produceOutput(outputs) diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index 8f2e4ff1ed..188d7fb5ab 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -40,6 +40,7 @@ Options: --log enable verbose logging to nimsuggest.log file --v1 use version 1 of the protocol; for backwards compatibility --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 @@ -550,6 +551,8 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = gRefresh = parseBool(p.val) else: gRefresh = true + of "maxresults": + suggestMaxResults = parseInt(p.val) else: processSwitch(pass, p) of cmdArgument: options.gProjectName = unixToNativePath(p.key) diff --git a/nimsuggest/tester.nim b/nimsuggest/tester.nim index 16f70beb52..4cda272af8 100644 --- a/nimsuggest/tester.nim +++ b/nimsuggest/tester.nim @@ -304,8 +304,7 @@ proc runTest(filename: string): int = proc main() = var failures = 0 if os.paramCount() > 0: - let f = os.paramStr(1) - let x = getAppDir() / f + let x = os.paramStr(1) let xx = expandFilename x failures += runTest(xx) failures += runEpcTest(xx) diff --git a/nimsuggest/tests/tdot4.nim b/nimsuggest/tests/tdot4.nim new file mode 100644 index 0000000000..3d98f91324 --- /dev/null +++ b/nimsuggest/tests/tdot4.nim @@ -0,0 +1,16 @@ +discard """ +$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;;1497;;5;;"Replaces `sub` in `s` by the string `by`.";;100;;None +""" + +import strutils + +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".#[!]# From c5566f7c375edeb0768753e27ef4c2ad5011b2a5 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 10 Mar 2017 13:20:32 +0100 Subject: [PATCH 12/34] nimsuggest: make 'con' work again --- compiler/lexer.nim | 4 ++++ nimsuggest/tests/tcon1.nim | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 nimsuggest/tests/tcon1.nim diff --git a/compiler/lexer.nim b/compiler/lexer.nim index b2c2a78778..2bb228f41e 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -1061,6 +1061,10 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = inc(L.bufpos) else: tok.tokType = tkParLe + when defined(nimsuggest): + if L.fileIdx == gTrackPos.fileIndex and tok.col < gTrackPos.col and + tok.line == gTrackPos.line and gIdeCmd == ideCon: + gTrackPos.col = tok.col.int16 of ')': tok.tokType = tkParRi inc(L.bufpos) diff --git a/nimsuggest/tests/tcon1.nim b/nimsuggest/tests/tcon1.nim new file mode 100644 index 0000000000..262dd51510 --- /dev/null +++ b/nimsuggest/tests/tcon1.nim @@ -0,0 +1,13 @@ +proc test(s: string; a: int) = discard +proc testB(a, b: string) = discard +test("hello here", #[!]#) +testB(#[!]# + + +discard """ +$nimsuggest --tester $file +>con $1 +con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;1;;5;;"";;100 +>con $2 +con;;skProc;;tcon1.testB;;proc (a: string, b: string);;$file;;2;;5;;"";;100 +""" From 2430fc7d82d54bb10b540ac6498520c116536a6f Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 10 Mar 2017 14:42:11 +0100 Subject: [PATCH 13/34] nimsuggest: special rule for 'of' completion in case statements --- compiler/semstmts.nim | 6 +++++- compiler/suggest.nim | 6 ++++++ nimsuggest/tests/tcase.nim | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 nimsuggest/tests/tcase.nim diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 7c6e3af6d6..6d0190257e 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -205,7 +205,8 @@ proc semCase(c: PContext, n: PNode): PNode = var typ = commonTypeBegin var hasElse = false var notOrdinal = false - case skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}).kind + let caseTyp = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}) + case caseTyp.kind of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool: chckCovered = true of tyFloat..tyFloat128, tyString, tyError: @@ -215,6 +216,9 @@ proc semCase(c: PContext, n: PNode): PNode = return for i in countup(1, sonsLen(n) - 1): var x = n.sons[i] + when defined(nimsuggest): + if gIdeCmd == ideSug and exactEquals(gTrackPos, x.info) and caseTyp.kind == tyEnum: + suggestEnum(c, x, caseTyp) case x.kind of nkOfBranch: checkMinSonsLen(x, 2) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 04f5baac48..63f769c7cc 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -555,6 +555,12 @@ proc suggestDecl*(c: PContext, n: PNode; s: PSym) = proc suggestStmt*(c: PContext, n: PNode) = suggestExpr(c, n) +proc suggestEnum*(c: PContext; n: PNode; t: PType) = + var outputs: Suggestions = @[] + suggestSymList(c, t.n, nil, n.info, outputs) + produceOutput(outputs) + if outputs.len > 0: suggestQuit() + proc suggestSentinel*(c: PContext) = if gIdeCmd != ideSug or c.module.position != gTrackPos.fileIndex: return if c.compilesContextId > 0: return diff --git a/nimsuggest/tests/tcase.nim b/nimsuggest/tests/tcase.nim new file mode 100644 index 0000000000..8e3fc55483 --- /dev/null +++ b/nimsuggest/tests/tcase.nim @@ -0,0 +1,17 @@ + +type + MyEnum = enum + nkIf, nkElse, nkElif + +proc test(a: MyEnum) = + case a + of nkElse: discard + of #[!]# + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skEnumField;;nkElse;;MyEnum;;$file;;4;;10;;"";;100;;None +sug;;skEnumField;;nkElif;;MyEnum;;$file;;4;;18;;"";;100;;None +sug;;skEnumField;;nkIf;;MyEnum;;$file;;4;;4;;"";;100;;None +""" From cb9d554ac93480c442a00129eaa0126396c884a3 Mon Sep 17 00:00:00 2001 From: Anatoly Galiulin Date: Fri, 10 Mar 2017 22:18:56 +0700 Subject: [PATCH 14/34] Fix typo (#5501) --- lib/pure/ospaths.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 87ece25829..71991e35a1 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -25,8 +25,8 @@ when not declared(getEnv) or defined(nimscript): WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write ## to an environment variable - ReadDirEffect* = object of ReadIOEffect ## effect that denotes a write - ## operation to the directory + ReadDirEffect* = object of ReadIOEffect ## effect that denotes a read + ## operation from the directory ## structure WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write ## operation to From 6e358e318747ecd6bea66911d6144cb7eff9d172 Mon Sep 17 00:00:00 2001 From: zah Date: Sun, 12 Mar 2017 10:27:05 +0200 Subject: [PATCH 15/34] don't allow casting to non-concrete types; fixes #5428 (#5502) --- compiler/msgs.nim | 2 ++ compiler/semexprs.nim | 13 +++++--- compiler/types.nim | 1 + tests/errmsgs/tnon_concrete_cast.nim | 47 ++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 tests/errmsgs/tnon_concrete_cast.nim diff --git a/compiler/msgs.nim b/compiler/msgs.nim index e50ed0f2a0..bf90900896 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -89,6 +89,7 @@ type errMainModuleMustBeSpecified, errXExpected, errTIsNotAConcreteType, + errCastToANonConcreteType, errInvalidSectionStart, errGridTableNotImplemented, errGeneralParseError, errNewSectionExpected, errWhitespaceExpected, errXisNoValidIndexFile, errCannotRenderX, errVarVarTypeNotAllowed, errInstantiateXExplicitly, @@ -326,6 +327,7 @@ const errMainModuleMustBeSpecified: "please, specify a main module in the project configuration file", errXExpected: "\'$1\' expected", errTIsNotAConcreteType: "\'$1\' is not a concrete type.", + errCastToANonConcreteType: "cannot cast to a non concrete type: \'$1\'", errInvalidSectionStart: "invalid section start", errGridTableNotImplemented: "grid table is not implemented", errGeneralParseError: "general parse error", diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index a419cd0008..755d444482 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -218,13 +218,16 @@ proc semConv(c: PContext, n: PNode): PNode = proc semCast(c: PContext, n: PNode): PNode = ## Semantically analyze a casting ("cast[type](param)") checkSonsLen(n, 2) + let targetType = semTypeNode(c, n.sons[0], nil) + let castedExpr = semExprWithType(c, n.sons[1]) + if tfHasMeta in targetType.flags: + localError(n.sons[0].info, errCastToANonConcreteType, $targetType) + if not isCastable(targetType, castedExpr.typ): + localError(n.info, errExprCannotBeCastToX, $targetType) result = newNodeI(nkCast, n.info) - result.typ = semTypeNode(c, n.sons[0], nil) + result.typ = targetType addSon(result, copyTree(n.sons[0])) - addSon(result, semExprWithType(c, n.sons[1])) - if not isCastable(result.typ, result.sons[1].typ): - localError(result.info, errExprCannotBeCastToX, - typeToString(result.typ)) + addSon(result, castedExpr) proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode = const diff --git a/compiler/types.nim b/compiler/types.nim index df1d3e3cac..80fb6612dd 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -20,6 +20,7 @@ type preferName, preferDesc, preferExported, preferModuleInfo, preferGenericArg proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string +template `$`*(typ: PType): string = typeToString(typ) proc base*(t: PType): PType = result = t.sons[0] diff --git a/tests/errmsgs/tnon_concrete_cast.nim b/tests/errmsgs/tnon_concrete_cast.nim new file mode 100644 index 0000000000..e4ae890ce8 --- /dev/null +++ b/tests/errmsgs/tnon_concrete_cast.nim @@ -0,0 +1,47 @@ +discard """ + errormsg: "cannot cast to a non concrete type: 'ptr SomeNumber'" + line: 36 +""" + +# https://github.com/nim-lang/Nim/issues/5428 + +type + MemFile = object + mem: pointer + +proc memfileopen(filename: string, newFileSize: int): MemFile = + # just a memfile mock + return + +type + MyData = object + member1: seq[int] + member2: int + +type + MyReadWrite = object + memfile: MemFile + offset: int + +# Here, SomeNumber is bound to a concrete type, and that's OK +proc write(rw: var MyReadWrite; value: SomeNumber): void = + (cast[ptr SomeNumber](cast[uint](rw.memfile.mem) + rw.offset.uint))[] = value + rw.offset += sizeof(SomeNumber) + +# Here, we try to use SomeNumber without binding it to a type. This should +# produce an error message for now. It's also possible to relax the rules +# and allow for type-class based type inference in such situations. +proc write[T](rw: var MyReadWrite; value: seq[T]): void = + rw.write value.len + let dst = cast[ptr SomeNumber](cast[uint](rw.memfile.mem) + uint(rw.offset)) + let src = cast[pointer](value[0].unsafeAddr) + let size = sizeof(T) * value.len + copyMem(dst, src, size) + rw.offset += size + +proc saveBinFile(arg: var MyData, filename: string): void = + var rw: MyReadWrite + rw.memfile = memfileOpen(filename, newFileSize = rw.offset) + rw.offset = 0 + rw.write arg.member1 + From 1be0022e7c6a8d168918998fd27412901432075d Mon Sep 17 00:00:00 2001 From: zah Date: Sun, 12 Mar 2017 10:33:49 +0200 Subject: [PATCH 16/34] Fixes #5167 and related problems (#5475) This commit returns to a bit less strict checking of the number of macro arguments, because some old immediate macros rely on a behavior where even the arity of the macro is not being checked. It may be better if such macros are just declared to use varargs[expr], but this remains for another day. --- compiler/ast.nim | 2 ++ compiler/evaltempl.nim | 5 +++++ compiler/msgs.nim | 6 ++++++ compiler/sem.nim | 7 +++++++ compiler/semcall.nim | 1 + compiler/semexprs.nim | 2 ++ compiler/semstmts.nim | 2 ++ compiler/semtypes.nim | 5 ++++- compiler/sigmatch.nim | 1 + compiler/types.nim | 4 +++- tests/errmsgs/t5167_1.nim | 17 +++++++++++++++++ tests/errmsgs/t5167_2.nim | 12 ++++++++++++ tests/errmsgs/t5167_3.nim | 25 +++++++++++++++++++++++++ tests/errmsgs/t5167_4.nim | 20 ++++++++++++++++++++ tests/errmsgs/t5167_5.nim | 25 +++++++++++++++++++++++++ tests/macros/tmacro4.nim | 2 +- tests/macros/tquotewords.nim | 2 +- 17 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 tests/errmsgs/t5167_1.nim create mode 100644 tests/errmsgs/t5167_2.nim create mode 100644 tests/errmsgs/t5167_3.nim create mode 100644 tests/errmsgs/t5167_4.nim create mode 100644 tests/errmsgs/t5167_5.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index 66fbe577ca..9d79620b2f 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -460,6 +460,8 @@ type # proc foo(T: typedesc, list: seq[T]): var T # proc foo(L: static[int]): array[L, int] # can be attached to ranges to indicate that the range + # can be attached to generic procs with free standing + # type parameters: e.g. proc foo[T]() # depends on unresolved static params. tfRetType, # marks return types in proc (used to detect type classes # used as return types for return type inference) diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim index 318254a809..5bd274a3ea 100644 --- a/compiler/evaltempl.nim +++ b/compiler/evaltempl.nim @@ -80,9 +80,14 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode = expectedRegularParams = expectedRegularParams + genericParams: globalError(n.info, errWrongNumberOfArguments) + if totalParams < genericParams: + globalError(n.info, errMissingGenericParamsForTemplate, + n.renderTree) + result = newNodeI(nkArgList, n.info) for i in 1 .. givenRegularParams: result.addSon n.sons[i] diff --git a/compiler/msgs.nim b/compiler/msgs.nim index bf90900896..eaaa0aaf39 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -64,6 +64,8 @@ type errVarForOutParamNeeded, errPureTypeMismatch, errTypeMismatch, errButExpected, errButExpectedX, errAmbiguousCallXYZ, errWrongNumberOfArguments, + errWrongNumberOfArgumentsInCall, + errMissingGenericParamsForTemplate, errXCannotBePassedToProcVar, errXCannotBeInParamDecl, errPragmaOnlyInHeaderOfProcX, errImplOfXNotAllowed, errImplOfXexpected, errNoSymbolToBorrowFromFound, errDiscardValueX, @@ -108,6 +110,7 @@ type errCannotInferTypeOfTheLiteral, errCannotInferReturnType, errGenericLambdaNotAllowed, + errProcHasNoConcreteType, errCompilerDoesntSupportTarget, errUser, warnCannotOpenFile, @@ -270,6 +273,8 @@ const errButExpectedX: "but expected \'$1\'", errAmbiguousCallXYZ: "ambiguous call; both $1 and $2 match for: $3", errWrongNumberOfArguments: "wrong number of arguments", + errWrongNumberOfArgumentsInCall: "wrong number of arguments in call to '$1'", + errMissingGenericParamsForTemplate: "'$1' has unspecified generic parameters", errXCannotBePassedToProcVar: "\'$1\' cannot be passed to a procvar", errXCannotBeInParamDecl: "$1 cannot be declared in parameter declaration", errPragmaOnlyInHeaderOfProcX: "pragmas are only allowed in the header of a proc; redefinition of $1", @@ -371,6 +376,7 @@ const errGenericLambdaNotAllowed: "A nested proc can have generic parameters only when " & "it is used as an operand to another routine and the types " & "of the generic paramers can be inferred from the expected signature.", + errProcHasNoConcreteType: "'$1' doesn't have a concrete type, due to unspecified generic parameters.", errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target", errUser: "$1", warnCannotOpenFile: "cannot open \'$1\'", diff --git a/compiler/sem.nim b/compiler/sem.nim index 21a5c435a9..e1d18e61f4 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -381,6 +381,13 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, if sym == c.p.owner: globalError(n.info, errRecursiveDependencyX, sym.name.s) + let genericParams = if sfImmediate in sym.flags: 0 + else: sym.ast[genericParamsPos].len + let suppliedParams = max(n.safeLen - 1, 0) + + if suppliedParams < genericParams: + globalError(n.info, errMissingGenericParamsForTemplate, n.renderTree) + #if c.evalContext == nil: # c.evalContext = c.createEvalContext(emStatic) result = evalMacroCall(c.module, c.cache, n, nOrig, sym) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 98667b0857..3a43c63b25 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -411,6 +411,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = let tm = typeRel(m, formal, arg, true) if tm in {isNone, isConvertible}: return nil var newInst = generateInstance(c, s, m.bindings, n.info) + newInst.typ.flags.excl tfUnresolved markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(newInst, n.info) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 755d444482..f1bf5d8641 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -30,6 +30,8 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # result = errorNode(c, n) if result.typ != nil: # XXX tyGenericInst here? + if result.typ.kind == tyProc and tfUnresolved in result.typ.flags: + localError(n.info, errProcHasNoConcreteType, n.renderTree) if result.typ.kind == tyVar: result = newDeref(result) elif {efWantStmt, efAllowStmt} * flags != {}: result.typ = newTypeS(tyVoid, c) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 7c6e3af6d6..45b75cb3e4 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -499,6 +499,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = if hasEmpty(typ): localError(def.info, errCannotInferTypeOfTheLiteral, ($typ.kind).substr(2).toLowerAscii) + elif typ.kind == tyProc and tfUnresolved in typ.flags: + localError(def.info, errProcHasNoConcreteType, def.renderTree) else: if symkind == skLet: localError(a.info, errLetNeedsInit) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 83d0c83b22..7877a26a96 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1009,8 +1009,11 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, result.sons[0] = r result.n.typ = r - if genericParams != nil: + if genericParams != nil and genericParams.len > 0: for n in genericParams: + if {sfUsed, sfAnon} * n.sym.flags == {}: + result.flags.incl tfUnresolved + if tfWildcard in n.sym.typ.flags: n.sym.kind = skType n.sym.typ.flags.excl tfWildcard diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index f2caab41f0..587598d3e5 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1514,6 +1514,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType, if arg.sons[i].sym.kind in {skProc, skMethod, skConverter, skIterator}: copyCandidate(z, m) z.callee = arg.sons[i].typ + if tfUnresolved in z.callee.flags: continue z.calleeSym = arg.sons[i].sym #if arg.sons[i].sym.name.s == "cmp": # ggDebug = true diff --git a/compiler/types.nim b/compiler/types.nim index 80fb6612dd..f4ef750945 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -548,7 +548,9 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = if prefer != preferExported: result.add("(" & typeToString(t.sons[0]) & ")") of tyProc: - result = if tfIterator in t.flags: "iterator (" else: "proc (" + result = if tfIterator in t.flags: "iterator " else: "proc " + if tfUnresolved in t.flags: result.add "[*missing parameters*]" + result.add "(" for i in countup(1, sonsLen(t) - 1): if t.n != nil and i < t.n.len and t.n[i].kind == nkSym: add(result, t.n[i].sym.name.s) diff --git a/tests/errmsgs/t5167_1.nim b/tests/errmsgs/t5167_1.nim new file mode 100644 index 0000000000..9f4f208a45 --- /dev/null +++ b/tests/errmsgs/t5167_1.nim @@ -0,0 +1,17 @@ +discard """ +errormsg: "'bar' doesn't have a concrete type, due to unspecified generic parameters." +line: 16 +""" + +proc foo[T]() = + var y1 = foo[string] + var y2 = foo[T] + +proc bar[T]() = + let x = 0 + +let good1 = foo[int] +let good2 = bar[int] + +let err = bar + diff --git a/tests/errmsgs/t5167_2.nim b/tests/errmsgs/t5167_2.nim new file mode 100644 index 0000000000..17d96ef471 --- /dev/null +++ b/tests/errmsgs/t5167_2.nim @@ -0,0 +1,12 @@ +discard """ +cmd: "nim c --threads:on $file" +errormsg: "'threadFunc' doesn't have a concrete type, due to unspecified generic parameters." +line: 11 +""" + +proc threadFunc[T]() {.thread.} = + let x = 0 + +var thr: Thread[void] +thr.createThread(threadFunc) + diff --git a/tests/errmsgs/t5167_3.nim b/tests/errmsgs/t5167_3.nim new file mode 100644 index 0000000000..2781d39439 --- /dev/null +++ b/tests/errmsgs/t5167_3.nim @@ -0,0 +1,25 @@ +discard """ +cmd: "nim c --threads:on $file" +errormsg: "type mismatch" +line: 24 +""" + +type + TGeneric[T] = object + x: int + +proc foo1[A, B, C, D](x: proc (a: A, b: B, c: C, d: D)) = + echo "foo1" + +proc foo2(x: proc(x: int)) = + echo "foo2" + +# The goal of this test is to verify that none of the generic parameters of the +# proc will be marked as unused. The error message should be "type mismatch" instead +# of "'bar' doesn't have a concrete type, due to unspecified generic parameters". +proc bar[A, B, C, D](x: A, y: seq[B], z: array[4, TGeneric[C]], r: TGeneric[D]) = + echo "bar" + +foo1[int, seq[int], array[4, TGeneric[float]], TGeneric[string]] bar +foo2 bar + diff --git a/tests/errmsgs/t5167_4.nim b/tests/errmsgs/t5167_4.nim new file mode 100644 index 0000000000..3d77fae02f --- /dev/null +++ b/tests/errmsgs/t5167_4.nim @@ -0,0 +1,20 @@ +discard """ +errormsg: "type mismatch: got (proc [*missing parameters*](x: int) | proc (x: string){.gcsafe, locks: 0.})" +line: 19 +""" + +type + TGeneric[T] = object + x: int + +proc foo[B](x: int) = + echo "foo1" + +proc foo(x: string) = + echo "foo2" + +proc bar(x: proc (x: int)) = + echo "bar" + +bar foo + diff --git a/tests/errmsgs/t5167_5.nim b/tests/errmsgs/t5167_5.nim new file mode 100644 index 0000000000..ab02f29f65 --- /dev/null +++ b/tests/errmsgs/t5167_5.nim @@ -0,0 +1,25 @@ +discard """ +cmd: "nim check $file" +errormsg: "'m' has unspecified generic parameters" +nimout: ''' +t5167_5.nim(20, 9) Error: 't' has unspecified generic parameters +t5167_5.nim(21, 5) Error: 't' has unspecified generic parameters +t5167_5.nim(23, 9) Error: 'm' has unspecified generic parameters +t5167_5.nim(24, 5) Error: 'm' has unspecified generic parameters +''' +""" + +template t[B]() = + echo "foo1" + +macro m[T]: stmt = nil + +proc bar(x: proc (x: int)) = + echo "bar" + +let x = t +bar t + +let y = m +bar m + diff --git a/tests/macros/tmacro4.nim b/tests/macros/tmacro4.nim index a563693690..fb07941a9e 100644 --- a/tests/macros/tmacro4.nim +++ b/tests/macros/tmacro4.nim @@ -5,7 +5,7 @@ discard """ import macros, strutils -macro test_macro*(n: stmt): stmt {.immediate.} = +macro test_macro*(s: string, n: stmt): stmt {.immediate.} = result = newNimNode(nnkStmtList) var ass : NimNode = newNimNode(nnkAsgn) add(ass, newIdentNode("str")) diff --git a/tests/macros/tquotewords.nim b/tests/macros/tquotewords.nim index 7a575f5417..48fcafd62c 100644 --- a/tests/macros/tquotewords.nim +++ b/tests/macros/tquotewords.nim @@ -6,7 +6,7 @@ discard """ import macros -macro quoteWords(n: expr): expr {.immediate.} = +macro quoteWords(n: varargs[expr]): expr {.immediate.} = let n = callsite() result = newNimNode(nnkBracket, n) for i in 1..n.len-1: From 3ab884a9e3a0c2845faac2c88cdd2042110a3339 Mon Sep 17 00:00:00 2001 From: mark-summerfield Date: Sun, 12 Mar 2017 16:40:14 +0000 Subject: [PATCH 17/34] Suggested small change to code (#5509) In a code example I think it best to either use full names (index, item) or abbreviated names where that's common (i, item) but not non-standard abbreviations (indx, itm). So I've changed it to index, item since it is a tutorial, although i, item would be just as good. --- doc/tut1.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tut1.rst b/doc/tut1.rst index e79214dee0..9ebc806898 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -411,8 +411,8 @@ Other useful iterators for collections (like arrays and sequences) are * ``pairs`` and ``mpairs`` which provides the element and an index number (immutable and mutable respectively) .. code-block:: nim - for indx, itm in ["a","b"].pairs: - echo itm, " at index ", indx + for index, item in ["a","b"].pairs: + echo item, " at index ", index # => a at index 0 # => b at index 1 From 639f786e5dd2a55f127a4dd3bad74a7952fa31e4 Mon Sep 17 00:00:00 2001 From: mark-summerfield Date: Sun, 12 Mar 2017 16:44:33 +0000 Subject: [PATCH 18/34] Update tut1.rst (#5510) In general: s/have to/must/g - but you can't do this mechanically because sometimes the must has to go back a word (e.g., line 519). This looks really odd to me: if thisIsaLongCondition() and thisIsAnotherLongCondition(1, 2, 3, 4): x = true I would have expected: if thisIsaLongCondition() and thisIsAnotherLongCondition( 1, 2, 3, 4): x = true If the second form is valid and good Nim style then I suggest using it rather than the original. However, if the original is the preferred style then this should be mentioned in the text since it is unusual. Since Nim is case-sensitive I think it is bad to write wrongly cased names, e.g., ``Bool`` is a built-in type on line 589. This isn't true since Bool isn't anything, but bool is. So in these cases I'd always reword to avoid this problem (and that's what I've done -- and it also avoids "bool. Bool" which was ugly). --- doc/tut1.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/tut1.rst b/doc/tut1.rst index 9ebc806898..277fb2988d 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -489,10 +489,10 @@ Example: else: echo "unknown operating system" -The ``when`` statement is almost identical to the ``if`` statement with some +The ``when`` statement is almost identical to the ``if`` statement, but with these differences: -* Each condition has to be a constant expression since it is evaluated by the +* Each condition must be a constant expression since it is evaluated by the compiler. * The statements within a branch do not open a new scope. * The compiler checks the semantics and produces code *only* for the statements @@ -516,8 +516,8 @@ In Nim there is a distinction between *simple statements* and *complex statements*. *Simple statements* cannot contain other statements: Assignment, procedure calls or the ``return`` statement belong to the simple statements. *Complex statements* like ``if``, ``when``, ``for``, ``while`` can -contain other statements. To avoid ambiguities, complex statements always have -to be indented, but single simple statements do not: +contain other statements. To avoid ambiguities, complex statements must always +be indented, but single simple statements do not: .. code-block:: nim # no indentation needed for single assignment statement: @@ -586,9 +586,9 @@ false if they answered "no" (or something similar). A ``return`` statement leaves the procedure (and therefore the while loop) immediately. The ``(question: string): bool`` syntax describes that the procedure expects a parameter named ``question`` of type ``string`` and returns a value of type -``bool``. ``Bool`` is a built-in type: the only valid values for ``bool`` are +``bool``. The ``bool`` type is built-in: the only valid values for ``bool`` are ``true`` and ``false``. -The conditions in if or while statements should be of the type ``bool``. +The conditions in if or while statements must be of type ``bool``. Some terminology: in the example ``question`` is called a (formal) *parameter*, ``"Should I..."`` is called an *argument* that is passed to this parameter. From c1ce20594e8e65e557495605f50bbd570c9d84d3 Mon Sep 17 00:00:00 2001 From: mark-summerfield Date: Sun, 12 Mar 2017 16:49:48 +0000 Subject: [PATCH 19/34] Nicer English (#5514) --- doc/tut1.rst | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/tut1.rst b/doc/tut1.rst index 277fb2988d..8e1cef047c 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -1156,9 +1156,9 @@ Sets Arrays ------ An array is a simple fixed length container. Each element in -the array has the same type. The array's index type can be any ordinal type. +an array has the same type. The array's index type can be any ordinal type. -Arrays can be constructed via ``[]``: +Arrays can be constructed using ``[]``: .. code-block:: nim @@ -1222,7 +1222,7 @@ subdivided in height levels accessed through their integer index: #tower[0][1] = on Note how the built-in ``len`` proc returns only the array's first dimension -length. Another way of defining the ``LightTower`` to show better its +length. Another way of defining the ``LightTower`` to better illustrate its nested nature would be to omit the previous definition of the ``LevelSetting`` type and instead write it embedded directly as the type of the first dimension: @@ -1230,7 +1230,7 @@ type and instead write it embedded directly as the type of the first dimension: type LightTower = array[1..10, array[north..west, BlinkLights]] -It is quite frequent to have arrays start at zero, so there's a shortcut syntax +It is quite common to have arrays start at zero, so there's a shortcut syntax to specify a range from zero to the specified index minus one: .. code-block:: nim @@ -1288,8 +1288,8 @@ value. Here the ``for`` statement is looping over the results from the `_ module. Examples: .. code-block:: nim - for i in @[3, 4, 5]: - echo i + for value in @[3, 4, 5]: + echo value # --> 3 # --> 4 # --> 5 @@ -1320,7 +1320,7 @@ type does not matter. fruits = @[] # creates an empty sequence on the heap that will be referenced by 'fruits' - capitals = ["New York", "London", "Berlin"] # array 'capitals' allows only assignment of three elements + capitals = ["New York", "London", "Berlin"] # array 'capitals' allows assignment of only three elements fruits.add("Banana") # sequence 'fruits' is dynamically expandable during runtime fruits.add("Mango") @@ -1406,7 +1406,7 @@ the same type and of the same name in the same order. The assignment operator for tuples copies each component. The notation ``t.field`` is used to access a tuple's field. Another notation is -``t[i]`` to access the ``i``'th field. Here ``i`` needs to be a constant +``t[i]`` to access the ``i``'th field. Here ``i`` must be a constant integer. .. code-block:: nim @@ -1449,10 +1449,10 @@ Tuples can be *unpacked* during variable assignment (and only then!). This can be handy to assign directly the fields of the tuples to individually named variables. An example of this is the `splitFile `_ proc from the `os module `_ which returns the directory, name and -extension of a path at the same time. For tuple unpacking to work you have to -use parenthesis around the values you want to assign the unpacking to, +extension of a path at the same time. For tuple unpacking to work you must +use parentheses around the values you want to assign the unpacking to, otherwise you will be assigning the same value to all the individual -variables! Example: +variables! For example: .. code-block:: nim @@ -1494,12 +1494,12 @@ point to and modify the same location in memory. Nim distinguishes between `traced`:idx: and `untraced`:idx: references. Untraced references are also called *pointers*. Traced references point to -objects of a garbage collected heap, untraced references point to -manually allocated objects or to objects somewhere else in memory. Thus +objects in a garbage collected heap, untraced references point to +manually allocated objects or to objects elsewhere in memory. Thus untraced references are *unsafe*. However for certain low-level operations -(accessing the hardware) untraced references are unavoidable. +(e.g., accessing the hardware), untraced references are necessary. -Traced references are declared with the **ref** keyword, untraced references +Traced references are declared with the **ref** keyword; untraced references are declared with the **ptr** keyword. The empty ``[]`` subscript notation can be used to *derefer* a reference, @@ -1520,10 +1520,10 @@ operators perform implicit dereferencing operations for reference types: n.data = 9 # no need to write n[].data; in fact n[].data is highly discouraged! -To allocate a new traced object, the built-in procedure ``new`` has to be used. +To allocate a new traced object, the built-in procedure ``new`` must be used. To deal with untraced memory, the procedures ``alloc``, ``dealloc`` and -``realloc`` can be used. The documentation of the `system `_ -module contains further information. +``realloc`` can be used. The `system `_ +module's documentation contains further details. If a reference points to *nothing*, it has the value ``nil``. @@ -1555,8 +1555,8 @@ listed in the `manual `_. Distinct type ------------- -A Distinct type allows for the creation of new type that "does not imply a subtype relationship between it and its base type". -You must EXPLICITLY define all behaviour for the distinct type. +A Distinct type allows for the creation of new type that "does not imply a subtype relationship between it and its base type". +You must **explicitly** define all behaviour for the distinct type. To help with this, both the distinct type and its base type can cast from one type to the other. Examples are provided in the `manual `_. @@ -1564,8 +1564,8 @@ Modules ======= Nim supports splitting a program into pieces with a module concept. Each module is in its own file. Modules enable `information hiding`:idx: and -`separate compilation`:idx:. A module may gain access to symbols of another -module by the `import`:idx: statement. Only top-level symbols that are marked +`separate compilation`:idx:. A module may gain access to the symbols of another +module by using the `import`:idx: statement. Only top-level symbols that are marked with an asterisk (``*``) are exported: .. code-block:: nim @@ -1585,7 +1585,7 @@ with an asterisk (``*``) are exported: The above module exports ``x`` and ``*``, but not ``y``. -The top-level statements of a module are executed at the start of the program. +A module's top-level statements are executed at the start of the program. This can be used to initialize complex data structures for example. Each module has a special magic constant ``isMainModule`` that is true if the @@ -1625,8 +1625,8 @@ This is best illustrated by an example: result = x + 1 -A symbol of a module *can* be *qualified* with the ``module.symbol`` syntax. If -the symbol is ambiguous, it even *has* to be qualified. A symbol is ambiguous +A symbol of a module *can* be *qualified* with the ``module.symbol`` syntax. And if +a symbol is ambiguous, it *must* be qualified. A symbol is ambiguous if it is defined in two (or more) different modules and both modules are imported by a third one: @@ -1642,7 +1642,7 @@ imported by a third one: # Module C import A, B write(stdout, x) # error: x is ambiguous - write(stdout, A.x) # no error: qualifier used + write(stdout, A.x) # okay: qualifier used var x = 4 write(stdout, x) # not ambiguous: uses the module C's x From 974b4d59b41ca8458d702a6dce0cd3e3cc7dc5ec Mon Sep 17 00:00:00 2001 From: mark-summerfield Date: Sun, 12 Mar 2017 16:55:30 +0000 Subject: [PATCH 20/34] Nicer English (#5511) --- doc/tut1.rst | 86 ++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/doc/tut1.rst b/doc/tut1.rst index 8e1cef047c..7e8d09b673 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -658,8 +658,8 @@ a tuple as a return value instead of using var parameters. Discard statement ----------------- To call a procedure that returns a value just for its side effects and ignoring -its return value, a ``discard`` statement **has** to be used. Nim does not -allow to silently throw away a return value: +its return value, a ``discard`` statement **must** be used. Nim does not +allow silently throwing away a return value: .. code-block:: nim discard yes("May I ask a pointless question?") @@ -708,7 +708,7 @@ The compiler checks that each parameter receives exactly one argument. Default values -------------- To make the ``createWindow`` proc easier to use it should provide `default -values`, these are values that are used as arguments if the caller does not +values`; these are values that are used as arguments if the caller does not specify them: .. code-block:: nim @@ -750,19 +750,19 @@ algorithm. Ambiguous calls are reported as errors. Operators --------- The Nim library makes heavy use of overloading - one reason for this is that -each operator like ``+`` is a just an overloaded proc. The parser lets you +each operator like ``+`` is just an overloaded proc. The parser lets you use operators in `infix notation` (``a + b``) or `prefix notation` (``+ a``). An infix operator always receives two arguments, a prefix operator always one. -Postfix operators are not possible, because this would be ambiguous: does +(Postfix operators are not possible, because this would be ambiguous: does ``a @ @ b`` mean ``(a) @ (@b)`` or ``(a@) @ (b)``? It always means -``(a) @ (@b)``, because there are no postfix operators in Nim. +``(a) @ (@b)``, because there are no postfix operators in Nim.) Apart from a few built-in keyword operators such as ``and``, ``or``, ``not``, operators always consist of these characters: ``+ - * \ / < > = @ $ ~ & % ! ? ^ . |`` User defined operators are allowed. Nothing stops you from defining your own -``@!?+~`` operator, but readability can suffer. +``@!?+~`` operator, but doing so may reduce readability. The operator's precedence is determined by its first character. The details can be found in the manual. @@ -785,7 +785,7 @@ Forward declarations -------------------- Every variable, procedure, etc. needs to be declared before it can be used. -(The reason for this is that it is non-trivial to do better than that in a +(The reason for this is that it is non-trivial to avoid this need in a language that supports meta programming as extensively as Nim does.) However, this cannot be done for mutually recursive procedures: @@ -822,7 +822,7 @@ whose value is then returned implicitly. Iterators ========= -Let's return to the boring counting example: +Let's return to the simple counting example: .. code-block:: nim echo "Counting to ten: " @@ -843,7 +843,7 @@ However, this does not work. The problem is that the procedure should not only ``return``, but return and **continue** after an iteration has finished. This *return and continue* is called a `yield` statement. Now the only thing left to do is to replace the ``proc`` keyword by ``iterator`` -and there it is - our first iterator: +and here it is - our first iterator: .. code-block:: nim iterator countup(a, b: int): int = @@ -856,8 +856,8 @@ Iterators look very similar to procedures, but there are several important differences: * Iterators can only be called from for loops. -* Iterators cannot contain a ``return`` statement and procs cannot contain a - ``yield`` statement. +* Iterators cannot contain a ``return`` statement (and procs cannot contain a + ``yield`` statement). * Iterators have no implicit ``result`` variable. * Iterators do not support recursion. * Iterators cannot be forward declared, because the compiler must be able @@ -866,8 +866,8 @@ important differences: However, you can also use a ``closure`` iterator to get a different set of restrictions. See `first class iterators `_ -for details. Iterators can have the same name and parameters as a proc, -essentially they have their own namespace. Therefore it is common practice to +for details. Iterators can have the same name and parameters as a proc, since +essentially they have their own namespaces. Therefore it is common practice to wrap iterators in procs of the same name which accumulate the result of the iterator and return it as a sequence, like ``split`` from the `strutils module `_. @@ -882,13 +882,13 @@ that are available for them in detail. Booleans -------- -The boolean type is named ``bool`` in Nim and consists of the two +Nim's boolean type is called ``bool`` and consists of the two pre-defined values ``true`` and ``false``. Conditions in while, -if, elif, when statements need to be of type bool. +if, elif, and when statements must be of type bool. The operators ``not, and, or, xor, <, <=, >, >=, !=, ==`` are defined -for the bool type. The ``and`` and ``or`` operators perform short-cut -evaluation. Example: +for the bool type. The ``and`` and ``or`` operators perform short-circuit +evaluation. For example: .. code-block:: nim @@ -899,8 +899,8 @@ evaluation. Example: Characters ---------- -The `character type` is named ``char`` in Nim. Its size is one byte. -Thus it cannot represent an UTF-8 character, but a part of it. +The `character type` is called ``char``. Its size is always one byte, so +it cannot represent most UTF-8 characters; but it *can* represent one of the bytes that makes up a multi-byte UTF-8 character. The reason for this is efficiency: for the overwhelming majority of use-cases, the resulting programs will still handle UTF-8 properly as UTF-8 was specially designed for this. @@ -914,11 +914,11 @@ Converting from an integer to a ``char`` is done with the ``chr`` proc. Strings ------- -String variables in Nim are **mutable**, so appending to a string -is quite efficient. Strings in Nim are both zero-terminated and have a -length field. One can retrieve a string's length with the builtin ``len`` +String variables are **mutable**, so appending to a string +is possible, and quite efficient. Strings in Nim are both zero-terminated and have a +length field. A string's length can be retrieved with the builtin ``len`` procedure; the length never counts the terminating zero. Accessing the -terminating zero is no error and often leads to simpler code: +terminating zero is not an error and often leads to simpler code: .. code-block:: nim if s[i] == 'a' and s[i+1] == 'b': @@ -928,15 +928,15 @@ terminating zero is no error and often leads to simpler code: The assignment operator for strings copies the string. You can use the ``&`` operator to concatenate strings and ``add`` to append to a string. -Strings are compared by their lexicographical order. All comparison operators -are available. Per convention, all strings are UTF-8 strings, but this is not +Strings are compared using their lexicographical order. All the comparison operators +are supported. By convention, all strings are UTF-8 encoded, but this is not enforced. For example, when reading strings from binary files, they are merely a sequence of bytes. The index operation ``s[i]`` means the i-th *char* of ``s``, not the i-th *unichar*. String variables are initialized with a special value, called ``nil``. However, most string operations cannot deal with ``nil`` (leading to an exception being -raised) for performance reasons. One should use empty strings ``""`` +raised) for performance reasons. It is best to use empty strings ``""`` rather than ``nil`` as the *empty* value. But ``""`` often creates a string object on the heap, so there is a trade-off to be made here. @@ -947,7 +947,7 @@ Nim has these integer types built-in: ``int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64``. The default integer type is ``int``. Integer literals can have a *type suffix* -to mark them to be of another integer type: +to specify a non-default integer type: .. code-block:: nim @@ -961,18 +961,18 @@ Most often integers are used for counting objects that reside in memory, so ``int`` has the same size as a pointer. The common operators ``+ - * div mod < <= == != > >=`` are defined for -integers. The ``and or xor not`` operators are defined for integers too and +integers. The ``and or xor not`` operators are also defined for integers, and provide *bitwise* operations. Left bit shifting is done with the ``shl``, right shifting with the ``shr`` operator. Bit shifting operators always treat their arguments as *unsigned*. For `arithmetic bit shifts`:idx: ordinary multiplication or division can be used. -Unsigned operations all wrap around; they cannot lead to over- or underflow +Unsigned operations all wrap around; they cannot lead to over- or under-flow errors. -`Automatic type conversion`:idx: is performed in expressions where different +Lossless `Automatic type conversion`:idx: is performed in expressions where different kinds of integer types are used. However, if the type conversion -loses information, the `EOutOfRange`:idx: exception is raised (if the error +would cause loss of information, the `EOutOfRange`:idx: exception is raised (if the error cannot be detected at compile time). @@ -981,9 +981,9 @@ Floats Nim has these floating point types built-in: ``float float32 float64``. The default float type is ``float``. In the current implementation, -``float`` is always 64 bit wide. +``float`` is always 64-bits. -Float literals can have a *type suffix* to mark them to be of another float +Float literals can have a *type suffix* to specify a non-default float type: .. code-block:: nim @@ -993,18 +993,18 @@ type: z = 0.0'f64 # z is of type ``float64`` The common operators ``+ - * / < <= == != > >=`` are defined for -floats and follow the IEEE standard. +floats and follow the IEEE-754 standard. Automatic type conversion in expressions with different kinds of floating point types is performed: the smaller type is converted to the larger. Integer -types are **not** converted to floating point types automatically and vice -versa. The `toInt `_ and `toFloat `_ -procs can be used for these conversions. +types are **not** converted to floating point types automatically, nor vice +versa. Use the `toInt `_ and `toFloat `_ +procs for these conversions. Type Conversion --------------- -Conversion between basic types in nim is performed by using the +Conversion between basic types is performed by using the type as a function: .. code-block:: nim @@ -1019,9 +1019,9 @@ Internal type representation ============================ As mentioned earlier, the built-in `$ `_ (stringify) operator -turns any basic type into a string, which you can then print to the screen -with the ``echo`` proc. However, advanced types, or types you may define -yourself won't work with the ``$`` operator until you define one for them. +turns any basic type into a string, which you can then print to the console +using the ``echo`` proc. However, advanced types, and your own custom types, +won't work with the ``$`` operator until you define it for them. Sometimes you just want to debug the current value of a complex type without having to write its ``$`` operator. You can use then the `repr `_ proc which works with any type and even complex data @@ -1057,7 +1057,7 @@ In Nim new types can be defined within a ``type`` statement: biggestInt = int64 # biggest integer type that is available biggestFloat = float64 # biggest float type that is available -Enumeration and object types cannot be defined on the fly, but only within a +Enumeration and object types may only be defined within a ``type`` statement. From ef59b1a8eb1e6f68febedf8c9d40379294598692 Mon Sep 17 00:00:00 2001 From: mark-summerfield Date: Sun, 12 Mar 2017 18:55:14 +0000 Subject: [PATCH 21/34] Nicer English (#5513) --- doc/tut1.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/tut1.rst b/doc/tut1.rst index 7e8d09b673..fb1ccc055d 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -1063,10 +1063,10 @@ Enumeration and object types may only be defined within a Enumerations ------------ -A variable of an enumeration type can only be assigned a value of a -limited set. This set consists of ordered symbols. Each symbol is mapped +A variable of an enumeration type can only be assigned one of the enumeration's specified values. +These values are a set of ordered symbols. Each symbol is mapped to an integer value internally. The first symbol is represented -at runtime by 0, the second by 1 and so on. Example: +at runtime by 0, the second by 1 and so on. For example: .. code-block:: nim @@ -1077,17 +1077,17 @@ at runtime by 0, the second by 1 and so on. Example: var x = south # `x` is of type `Direction`; its value is `south` echo x # writes "south" to `stdout` -All comparison operators can be used with enumeration types. +All the comparison operators can be used with enumeration types. An enumeration's symbol can be qualified to avoid ambiguities: ``Direction.south``. -The ``$`` operator can convert any enumeration value to its name, the ``ord`` -proc to its underlying integer value. +The ``$`` operator can convert any enumeration value to its name, and the ``ord`` +proc can convert it to its underlying integer value. For better interfacing to other programming languages, the symbols of enum types can be assigned an explicit ordinal value. However, the ordinal values -have to be in ascending order. A symbol whose ordinal value is not +must be in ascending order. A symbol whose ordinal value is not explicitly given is assigned the value of the previous symbol + 1. An explicit ordered enum can have *holes*: @@ -1142,8 +1142,8 @@ subrange types (and vice versa) are allowed. The ``system`` module defines the important `Natural `_ type as ``range[0..high(int)]`` (`high `_ returns the -maximal value). Other programming languages mandate the usage of unsigned -integers for natural numbers. This is often **wrong**: you don't want unsigned +maximal value). Other programming languages may suggest the use of unsigned +integers for natural numbers. This is often **unwise**: you don't want unsigned arithmetic (which wraps around) just because the numbers cannot be negative. Nim's ``Natural`` type helps to avoid this common programming error. From 9fda97b05840a373f1e49417e7f50fa1328d7b06 Mon Sep 17 00:00:00 2001 From: mark-summerfield Date: Sun, 12 Mar 2017 19:01:01 +0000 Subject: [PATCH 22/34] Fixed typo (#5508) --- doc/tut1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tut1.rst b/doc/tut1.rst index fb1ccc055d..65906376e2 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -393,7 +393,7 @@ Since counting up occurs so often in programs, Nim also has a `.. for i in 1..10: ... -Zero-indexed counting have two shortcuts ``..<`` and ``..^`` to simplify counting to one less then the higher index: +Zero-indexed counting have two shortcuts ``..<`` and ``..^`` to simplify counting to one less than the higher index: .. code-block:: nim for i in 0..<10: From d59441340dcc3b131c984def530084da93796775 Mon Sep 17 00:00:00 2001 From: c-blake Date: Sun, 12 Mar 2017 15:45:10 -0400 Subject: [PATCH 23/34] Fixes incorrect fd==0 test on Unix; Conserves handles by default. (#5512) * Fix 2 problems. First, 0 is a valid fd on Unix (easily gotten if user first closes all fds and then starts using memfiles). Use -1 instead for an invalid fd. Second, it is best practice to conserve open fds on Unix and file handles on Windows. These handles are not needed unless the user wants to remap the memory with ``mapMem`` (or a hypothetical future ``proc resize``). Adding a new bool param ``allowRemap=false`` to ``memfiles.open`` solves this cleanly in a "mostly" backward compatible way. This is only "mostly" because the default ``false`` case does not keep unneeded resources allocated, but that most sensible default means that any ``mapMem`` callers need to fix all their open calls to have allowRemap=true, as this PR also does for tmemfiles2.nim. * Include backwards compatibility note. --- lib/pure/memfiles.nim | 28 ++++++++++++++++++++-------- tests/stdlib/tmemfiles2.nim | 2 +- web/news/e031_version_0_16_2.rst | 3 +++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index c6322c7bb6..b6154d8de8 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -83,7 +83,8 @@ proc unmapMem*(f: var MemFile, p: pointer, size: int) = proc open*(filename: string, mode: FileMode = fmRead, - mappedSize = -1, offset = 0, newFileSize = -1): MemFile = + mappedSize = -1, offset = 0, newFileSize = -1, + allowRemap = false): MemFile = ## opens a memory mapped file. If this fails, ``EOS`` is raised. ## ## ``newFileSize`` can only be set if the file does not exist and is opened @@ -95,6 +96,9 @@ proc open*(filename: string, mode: FileMode = fmRead, ## ``offset`` must be multiples of the PAGE SIZE of your OS ## (usually 4K or 8K but is unique to your OS) ## + ## ``allowRemap`` only needs to be true if you want to call ``mapMem`` on + ## the resulting MemFile; else file handles are not kept open. + ## ## Example: ## ## .. code-block:: nim @@ -189,11 +193,14 @@ proc open*(filename: string, mode: FileMode = fmRead, else: result.size = fileSize.int result.wasOpened = true + if not allowRemap and result.fHandle != INVALID_HANDLE_VALUE: + if closeHandle(result.fHandle) == 0: + result.fHandle = INVALID_HANDLE_VALUE else: template fail(errCode: OSErrorCode, msg: expr) = rollback() - if result.handle != 0: discard close(result.handle) + if result.handle != -1: discard close(result.handle) raiseOSError(errCode) var flags = if readonly: O_RDONLY else: O_RDWR @@ -236,6 +243,10 @@ proc open*(filename: string, mode: FileMode = fmRead, if result.mem == cast[pointer](MAP_FAILED): fail(osLastError(), "file mapping failed") + if not allowRemap and result.handle != -1: + if close(result.handle) == 0: + result.handle = -1 + proc close*(f: var MemFile) = ## closes the memory mapped file `f`. All changes are written back to the ## file system, if `f` was opened with write access. @@ -244,15 +255,16 @@ proc close*(f: var MemFile) = var lastErr: OSErrorCode when defined(windows): - if f.fHandle != INVALID_HANDLE_VALUE and f.wasOpened: + if f.wasOpened: error = unmapViewOfFile(f.mem) == 0 lastErr = osLastError() error = (closeHandle(f.mapHandle) == 0) or error - error = (closeHandle(f.fHandle) == 0) or error + if f.fHandle != INVALID_HANDLE_VALUE: + error = (closeHandle(f.fHandle) == 0) or error else: - if f.handle != 0: - error = munmap(f.mem, f.size) != 0 - lastErr = osLastError() + error = munmap(f.mem, f.size) != 0 + lastErr = osLastError() + if f.handle != -1: error = (close(f.handle) != 0) or error f.size = 0 @@ -263,7 +275,7 @@ proc close*(f: var MemFile) = f.mapHandle = 0 f.wasOpened = false else: - f.handle = 0 + f.handle = -1 if error: raiseOSError(lastErr) diff --git a/tests/stdlib/tmemfiles2.nim b/tests/stdlib/tmemfiles2.nim index 026443e93e..665e92e8a2 100644 --- a/tests/stdlib/tmemfiles2.nim +++ b/tests/stdlib/tmemfiles2.nim @@ -18,7 +18,7 @@ mm = memfiles.open(fn, mode = fmReadWrite, newFileSize = 20) mm.close() # read, change -mm_full = memfiles.open(fn, mode = fmWrite, mappedSize = -1) +mm_full = memfiles.open(fn, mode = fmWrite, mappedSize = -1, allowRemap = true) echo "Full read size: ",mm_full.size p = mm_full.mapMem(fmReadWrite, 20, 0) var p2 = cast[cstring](p) diff --git a/web/news/e031_version_0_16_2.rst b/web/news/e031_version_0_16_2.rst index 3f111b503b..802478090f 100644 --- a/web/news/e031_version_0_16_2.rst +++ b/web/news/e031_version_0_16_2.rst @@ -23,6 +23,9 @@ Changes affecting backwards compatibility pointer. Now the hash is calculated from the contents of the string, assuming ``cstring`` is a null-terminated string. Equal ``string`` and ``cstring`` values produce an equal hash value. +- ``memfiles.open`` now closes file handleds/fds by default. Passing + ``allowRemap=true`` to ``memfiles.open`` recovers the old behavior. The old + behavior is only needed to call ``mapMem`` on the resulting ``MemFile``. Library Additions ----------------- From 3d534375c79d521c9fa747efceb09b5115b94010 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 13 Mar 2017 21:59:23 +0100 Subject: [PATCH 24/34] nimsuggest: logging enable when compiled with -d:logging --- nimsuggest/nimsuggest.nim | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index 188d7fb5ab..2cf19925ce 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -62,7 +62,7 @@ var gAddress = "" gMode: Mode gEmitEof: bool # whether we write '!EOF!' dummy lines - gLogging = false + gLogging = defined(logging) gRefresh: bool requests: Channel[string] @@ -79,6 +79,9 @@ proc errorHook(info: TLineInfo; msg: string; sev: Severity) = line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev)) +proc myLog(s: string) = + if gLogging: log(s) + const seps = {':', ';', ' ', '\t'} Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline|known file.nim[;dirtyfile.nim]:line:col\n" & @@ -155,16 +158,15 @@ proc symFromInfo(graph: ModuleGraph; gTrackPos: TLineInfo): PSym = proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; graph: ModuleGraph; cache: IdentCache) = - if gLogging: - log("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & - "[" & $line & ":" & $col & "]") + myLog("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & + "[" & $line & ":" & $col & "]") gIdeCmd = cmd if cmd == ideChk: msgs.structuredErrorHook = errorHook - msgs.writelnHook = proc (s: string) = discard + msgs.writelnHook = myLog else: msgs.structuredErrorHook = nil - msgs.writelnHook = proc (s: string) = discard + msgs.writelnHook = myLog if cmd == ideUse and suggestVersion != 0: graph.resetAllModules() var isKnownFile = true @@ -353,8 +355,7 @@ proc replEpc(x: ThreadParams) {.thread.} = setVerbosity(0) else: discard let cmd = $gIdeCmd & " " & args.argsToStr - if gLogging: - log "MSG CMD: " & cmd + myLog "MSG CMD: " & cmd requests.send(cmd) toEpc(client, uid) of "methods": @@ -583,8 +584,7 @@ proc handleCmdLine(cache: IdentCache; config: ConfigRef) = "Cannot find Nim standard library: Nim compiler not in PATH") gPrefixDir = binaryPath.splitPath().head.parentDir() #msgs.writelnHook = proc (line: string) = log(line) - if gLogging: - log("START " & gProjectFull) + myLog("START " & gProjectFull) loadConfigs(DefaultConfig, cache, config) # load all config files # now process command line arguments again, because some options in the From 650b20dc5e9a1ce7e0990e2edc0ed01e6f0cada4 Mon Sep 17 00:00:00 2001 From: zah Date: Mon, 13 Mar 2017 23:02:11 +0200 Subject: [PATCH 25/34] fix varargs forwarding for templates; fixes #5455 (#5505) * fix varargs forwarding for templates; fixes #5455 * document the macros' varargs change in the news for 0.16.2 --- compiler/sigmatch.nim | 4 +-- tests/overload/tparam_forwarding.nim | 37 ++++++++++++++++++++++++++++ web/news/e031_version_0_16_2.rst | 3 +++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 tests/overload/tparam_forwarding.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 587598d3e5..bc9888df9d 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1651,7 +1651,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, if a >= formalLen-1 and formal != nil and formal.typ.isVarargsUntyped: incl(marker, formal.position) if container.isNil: - container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, n.info)) + container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info)) setSon(m.call, formal.position + 1, container) else: incrIndexType(container.typ) @@ -1739,7 +1739,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, if formal.typ.isVarargsUntyped: if container.isNil: - container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, n.info)) + container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info)) setSon(m.call, formal.position + 1, container) else: incrIndexType(container.typ) diff --git a/tests/overload/tparam_forwarding.nim b/tests/overload/tparam_forwarding.nim new file mode 100644 index 0000000000..c1b276bfc9 --- /dev/null +++ b/tests/overload/tparam_forwarding.nim @@ -0,0 +1,37 @@ +discard """ +output: '''baz +10 +100 +1000 +a +b +c +''' +""" + +type + Foo = object + x: int + +proc stringVarargs*(strings: varargs[string, `$`]): void = + for s in strings: echo s + +proc fooVarargs*(foos: varargs[Foo]) = + for f in foos: echo f.x + +template templateForwarding*(callable: untyped, + condition: bool, + forwarded: varargs[untyped]): untyped = + if condition: + callable(forwarded) + +proc procForwarding(args: varargs[string]) = + stringVarargs(args) + +templateForwarding stringVarargs, 17 + 4 < 21, "foo", "bar", 100 +templateForwarding stringVarargs, 10 < 21, "baz" + +templateForwarding fooVarargs, "test".len > 3, Foo(x: 10), Foo(x: 100), Foo(x: 1000) + +procForwarding "a", "b", "c" + diff --git a/web/news/e031_version_0_16_2.rst b/web/news/e031_version_0_16_2.rst index 802478090f..37137169b2 100644 --- a/web/news/e031_version_0_16_2.rst +++ b/web/news/e031_version_0_16_2.rst @@ -23,6 +23,9 @@ Changes affecting backwards compatibility pointer. Now the hash is calculated from the contents of the string, assuming ``cstring`` is a null-terminated string. Equal ``string`` and ``cstring`` values produce an equal hash value. +- Macros accepting `varargs` arguments will now receive a node having the + `nkArgList` node kind. Previous code expecting the node kind to be `nkBracket` + may have to be updated. - ``memfiles.open`` now closes file handleds/fds by default. Passing ``allowRemap=true`` to ``memfiles.open`` recovers the old behavior. The old behavior is only needed to call ``mapMem`` on the resulting ``MemFile``. From 15a8996d57eb201cdc1db2e1e79faba4a3530636 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 14 Mar 2017 08:36:57 +0100 Subject: [PATCH 26/34] valgrind support for nim --- koch.nim | 9 +++++++++ tools/nimgrind.supp | 14 ++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tools/nimgrind.supp diff --git a/koch.nim b/koch.nim index 7c01939171..37b33c5ace 100644 --- a/koch.nim +++ b/koch.nim @@ -512,6 +512,14 @@ proc pushCsources() = finally: setCurrentDir(cwd) +proc valgrind(cmd: string) = + exec("nim c " & cmd) + var i = cmd.len-1 + while i >= 0 and cmd[i] != ' ': dec i + let file = if i >= 0: substr(cmd, i+1) else: cmd + let supp = getAppDir() / "tools" / "nimgrind.supp" + exec("valgrind --suppressions=" & supp & " " & changeFileExt(file, "")) + proc showHelp() = quit(HelpText % [VersionAsString & spaces(44-len(VersionAsString)), CompileDate, CompileTime], QuitSuccess) @@ -548,5 +556,6 @@ of cmdArgument: of "nimsuggest": bundleNimsuggest(buildExe=true) of "tools": buildTools(existsDir(".git")) of "pushcsource", "pushcsources": pushCsources() + of "valgrind": valgrind(op.cmdLineRest) else: showHelp() of cmdEnd: showHelp() diff --git a/tools/nimgrind.supp b/tools/nimgrind.supp new file mode 100644 index 0000000000..44499ebc77 --- /dev/null +++ b/tools/nimgrind.supp @@ -0,0 +1,14 @@ +{ + markstackandregisters_Cond + Memcheck:Cond + ... + fun:markStackAndRegisters* + ... +} +{ + markstackandregisters_Value8 + Memcheck:Value8 + ... + fun:markStackAndRegisters* + ... +} From e32f08d05b7e9a7f8cc121f02a3622bf26e29733 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 14 Mar 2017 08:40:02 +0100 Subject: [PATCH 27/34] sequtils: removed outdated note --- lib/pure/collections/sequtils.nim | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 45a148fbf7..19512d5f4f 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -16,9 +16,6 @@ ## `_ to procs like ``filter`` to reduce typing. ## Anonymous procs can use `the special do notation `_ ## which is more convenient in certain situations. -## -## **Note**: This interface will change as soon as the compiler supports -## closures and proper coroutines. include "system/inclrtl" From c149a235e7d8748422240c56c132b6594a629113 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 14 Mar 2017 10:23:23 +0100 Subject: [PATCH 28/34] nimsuggest: make test green again --- nimsuggest/tests/tinclude.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim index 27391c5229..ee4a6698d2 100644 --- a/nimsuggest/tests/tinclude.nim +++ b/nimsuggest/tests/tinclude.nim @@ -1,7 +1,7 @@ discard """ $nimsuggest --tester compiler/nim.nim >def compiler/semexprs.nim:13:50 -def;;skType;;ast.PSym;;PSym;;*ast.nim;;669;;2;;"";;100 +def;;skType;;ast.PSym;;PSym;;*ast.nim;;671;;2;;"";;100 >def compiler/semexprs.nim:13:50 -def;;skType;;ast.PSym;;PSym;;*ast.nim;;669;;2;;"";;100 +def;;skType;;ast.PSym;;PSym;;*ast.nim;;671;;2;;"";;100 """ From f162ff7773c01cc0eb7f193ff3a075b48aad75b2 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 14 Mar 2017 10:28:50 +0100 Subject: [PATCH 29/34] nimsuggest: make tdot1 test case green again --- compiler/lexer.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 7e54a30e2a..2bb228f41e 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -1080,7 +1080,7 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = inc(L.bufpos) of '.': when defined(nimsuggest): - if L.fileIdx == gTrackPos.fileIndex and tok.col == gTrackPos.col and + if L.fileIdx == gTrackPos.fileIndex and tok.col+1 == gTrackPos.col and tok.line == gTrackPos.line and gIdeCmd == ideSug: tok.tokType = tkDot L.cursor = CursorPosition.InToken From b414806e66efbf37c8e7edf832cb9a266f433d60 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 14 Mar 2017 11:21:35 +0100 Subject: [PATCH 30/34] nimsuggest: suggest types in a type section --- compiler/parser.nim | 1 + compiler/semstmts.nim | 6 +++++- compiler/suggest.nim | 4 ++-- nimsuggest/tests/ttype_decl.nim | 15 +++++++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 nimsuggest/tests/ttype_decl.nim diff --git a/compiler/parser.nim b/compiler/parser.nim index 0503b29eb7..362a5c286c 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1846,6 +1846,7 @@ proc parseTypeDef(p: var TParser): PNode = else: addSon(result, ast.emptyNode) if p.tok.tokType == tkEquals: + result.info = parLineInfo(p) getTok(p) optInd(p, result) addSon(result, parseTypeDefAux(p)) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 069ece6a64..9a1850932d 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -717,7 +717,11 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = # we even look at the type definitions on the right for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] - if gCmd == cmdIdeTools: suggestStmt(c, a) + when defined(nimsuggest): + if gCmd == cmdIdeTools: + inc c.inTypeContext + suggestStmt(c, a) + dec c.inTypeContext if a.kind == nkCommentStmt: continue if a.kind != nkTypeDef: illFormedAst(a) checkSonsLen(a, 3) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 63f769c7cc..f9210cc93e 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -72,7 +72,7 @@ template origModuleName(m: PSym): string = m.name.s proc findDocComment(n: PNode): PNode = if n == nil: return nil if not isNil(n.comment): return n - if n.kind in {nkStmtList, nkStmtListExpr} and n.len > 0: + if n.kind in {nkStmtList, nkStmtListExpr, nkObjectTy, nkRecList} and n.len > 0: result = findDocComment(n.sons[0]) if result != nil: return if n.len > 1: @@ -194,7 +194,7 @@ proc suggestResult(s: Suggest) = proc produceOutput(a: var Suggestions) = if gIdeCmd in {ideSug, ideCon}: a.sort cmpSuggestions - when false: + when defined(debug): # debug code writeStackTrace() if a.len > suggestMaxResults: a.setLen(suggestMaxResults) diff --git a/nimsuggest/tests/ttype_decl.nim b/nimsuggest/tests/ttype_decl.nim new file mode 100644 index 0000000000..846eb7b1b6 --- /dev/null +++ b/nimsuggest/tests/ttype_decl.nim @@ -0,0 +1,15 @@ +discard """ +$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 +""" +import strutils +type + Other = object ## My other object. + Foo = #[!]# + +proc main(f: Foo) = + +# XXX why no doc comments? From a330d967b5f2ad8f3dfcac62bc2cc8e9b00cdcba Mon Sep 17 00:00:00 2001 From: Mark Summerfield Date: Tue, 14 Mar 2017 11:17:39 +0000 Subject: [PATCH 31/34] Deleted parenthetical phrase (#5526) I deleted it because it came across as arrogant. It is also contentious so IMO has no place in a manual. --- doc/manual/procs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/procs.txt b/doc/manual/procs.txt index 9b08235c5a..5f4c9f2fa1 100644 --- a/doc/manual/procs.txt +++ b/doc/manual/procs.txt @@ -2,7 +2,7 @@ Procedures ========== What most programming languages call `methods`:idx: or `functions`:idx: are -called `procedures`:idx: in Nim (which is the correct terminology). A procedure +called `procedures`:idx: in Nim. A procedure declaration consists of an identifier, zero or more formal parameters, a return value type and a block of code. Formal parameters are declared as a list of identifiers separated by either comma or semicolon. A parameter is given a type From f7d760cb94ffef99b596d167fc517284e5a6a757 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 14 Mar 2017 12:28:15 +0100 Subject: [PATCH 32/34] nimsuggest: when invoked with a directory, detect the main nim file on its own --- compiler/options.nim | 17 +++++++++++++++++ nimsuggest/nimsuggest.nim | 8 +++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/compiler/options.nim b/compiler/options.nim index 349f9dae16..6372cddac3 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -392,6 +392,23 @@ proc findModule*(modulename, currentModule: string): string = result = findFile(m) patchModule() +proc findProjectNimFile*(pkg: string): string = + const extensions = [".nims", ".cfg", ".nimcfg", ".nimble"] + var candidates: seq[string] = @[] + for k, f in os.walkDir(pkg, relative=true): + if k == pcFile and f != "config.nims": + let (_, name, ext) = splitFile(f) + if ext in extensions: + let x = changeFileExt(pkg / name, ".nim") + if fileExists(x): + candidates.add x + for c in candidates: + # nim-foo foo or foo nfoo + if (pkg in c) or (c in pkg): return c + if candidates.len >= 1: + return candidates[0] + return "" + proc canonDynlibName(s: string): string = let start = if s.startsWith("lib"): 3 else: 0 let ende = strutils.find(s, {'(', ')', '.'}) diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index 2cf19925ce..0b66dfb404 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -556,7 +556,13 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = suggestMaxResults = parseInt(p.val) else: processSwitch(pass, p) of cmdArgument: - options.gProjectName = unixToNativePath(p.key) + let a = unixToNativePath(p.key) + if dirExists(a) and not fileExists(a.addFileExt("nim")): + options.gProjectName = findProjectNimFile(a) + # don't make it worse, report the error the old way: + if options.gProjectName.len == 0: options.gProjectName = a + else: + options.gProjectName = a # if processArgument(pass, p, argsCount): break proc handleCmdLine(cache: IdentCache; config: ConfigRef) = From 3eff1b776533019e3e69b18f4ad06aa54438e761 Mon Sep 17 00:00:00 2001 From: Mark Summerfield Date: Tue, 14 Mar 2017 14:06:06 +0000 Subject: [PATCH 33/34] Minor doc fix as per issue #5523 (#5533) --- lib/pure/collections/critbits.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index bb234565ba..519c58653e 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -8,8 +8,9 @@ # ## This module implements a `crit bit tree`:idx: which is an efficient -## container for a set or a mapping of strings. Based on the excellent paper +## container for a sorted set of strings, or for a sorted mapping of strings. Based on the excellent paper ## by Adam Langley. +## (A crit bit tree is a form of `radix tree`:idx: or `patricia trie`:idx:.) include "system/inclrtl" From 0510c0cecefb50dedd691de82151bc629b35d816 Mon Sep 17 00:00:00 2001 From: Mark Summerfield Date: Tue, 14 Mar 2017 14:07:45 +0000 Subject: [PATCH 34/34] Mentioned that critbits is sorted... (#5524) Having a lexicographically sorted collection is a big benefit (I asked GvR years ago to add one to Python but it was no then and seems to be no now!). Anyone looking for a such a collection could easily miss the critbits model because very few people have heard of them (according to Wikipedia most people in a ration of approx 750:1 know them as radix trees: https://en.wikipedia.org/wiki/Talk%3ACrit_bit_tree). --- doc/lib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/lib.rst b/doc/lib.rst index b43f295ef0..ea43c0db9a 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -84,7 +84,7 @@ Collections and algorithms Efficient implementation of a set of ints as a sparse bit set. * `critbits `_ This module implements a *crit bit tree* which is an efficient - container for a set or a mapping of strings. + container for a sorted set of strings, or for a sorted mapping of strings. * `sequtils `_ This module implements operations for the built-in seq type which were inspired by functional programming languages.