From 1a50442c13bfc19fe314df0a0fec87dd1b3c5d48 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Fri, 18 Aug 2017 20:42:16 +0800 Subject: [PATCH 01/17] Generate deps file during C compilation The "genDepend" command was previously taught how to generate a "deps" file in 4910a87c6 (gendepend improvements; refs #5144). Such a deps file is useful in integrating the Nim compiler with an external build system or watch daemon, such that it's possible to only run the Nim compiler when any of the source files are modified. It's also useful to generate the deps file in the nimcache directory during C compilation, without needing to re-run the compilation passes with "genDepend". This would thus reduce overall project build times. --- compiler/main.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/main.nim b/compiler/main.nim index f662ded1ba..76e18a80b0 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -77,6 +77,7 @@ proc commandCompileToC(graph: ModuleGraph; cache: IdentCache) = let proj = changeFileExt(gProjectFull, "") extccomp.callCCompiler(proj) extccomp.writeJsonBuildInstructions(proj) + writeDepsFile(graph, toGeneratedFile(proj, "")) proc commandCompileToJS(graph: ModuleGraph; cache: IdentCache) = #incl(gGlobalOptions, optSafeCode) From b06c0f97a4640c16b2205635b378a39fcef9b814 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 22 Aug 2017 23:36:03 +0800 Subject: [PATCH 02/17] writeDepsFile: write included files as well `writeDepsFile()` does not list files which were included with the `include` statement, e.g, with: import file1 include file2 `file1` will be written to the deps file, while `file2` would not. Fix this by modifying `writeDepsFile()` to write included files as well. Now, both `file1` and `file2` in the above example will be written to the deps file. --- compiler/main.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/main.nim b/compiler/main.nim index 76e18a80b0..994c28ccb8 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -16,7 +16,7 @@ import cgen, jsgen, json, nversion, platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen, docgen2, service, parser, modules, ccgutils, sigmatch, ropes, - modulegraphs + modulegraphs, tables from magicsys import systemModule, resetSysTypes @@ -36,6 +36,9 @@ proc writeDepsFile(g: ModuleGraph; project: string) = for m in g.modules: if m != nil: f.writeLine(toFullPath(m.position.int32)) + for k in g.inclToMod.keys: + if g.getModule(k).isNil: # don't repeat includes which are also modules + f.writeLine(k.toFullPath) f.close() proc commandGenDepend(graph: ModuleGraph; cache: IdentCache) = From 277bf1098c2662d21314692963f0596abfc30d3e Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sun, 17 Sep 2017 13:43:22 +0100 Subject: [PATCH 03/17] Add check for broken code-block in docs --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 095c3ec74f..6b8cdbe03b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,3 +48,5 @@ script: - ./koch csource - ./koch nimsuggest # - nim c -r nimsuggest/tester + - ( ! grep -F '.. code-block' -l -r --include '*.html' --exclude contributing.html --exclude docgen.html --exclude tut2.html ) + - ( ! grep -F '..code-block' -l -r --include '*.html' --exclude contributing.html --exclude docgen.html --exclude tut2.html ) From ff98fe1387a24fbe5a09f64264a75a1b25f086d5 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sun, 17 Sep 2017 17:32:06 +0100 Subject: [PATCH 04/17] Fix broken code-block in docs --- lib/pure/times.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index c1d6c3e530..587ea1903b 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -305,6 +305,7 @@ proc `+`*(ti1, ti2: TimeInterval): TimeInterval = proc `-`*(ti: TimeInterval): TimeInterval = ## Reverses a time interval + ## ## .. code-block:: nim ## ## let day = -initInterval(hours=24) From 25831a83d799a0400fbd086ea9a6f704d4d6b216 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sat, 11 Nov 2017 16:59:42 +0000 Subject: [PATCH 05/17] Add unittest suite/test name filters Support simple globbing --- lib/pure/unittest.nim | 91 +++++++++++++++++++++++++++++++++----- tests/stdlib/tunittest.nim | 38 ++++++++++++++++ 2 files changed, 118 insertions(+), 11 deletions(-) diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 7a8d1dad09..fbce087ffd 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -21,13 +21,41 @@ ## ``nim c -r `` exits with 0 or 1 ## ## Running a single test -## --------------------- +## ===================== ## -## Simply specify the test name as a command line argument. +## Specify the test name as a command line argument. ## ## .. code:: ## -## nim c -r test "my super awesome test name" +## nim c -r test "my test name" "another test" +## +## Multiple arguments can be used. +## +## Running a single test suite +## =========================== +## +## Specify the suite name delimited by ``"::"``. +## +## .. code:: +## +## nim c -r test "my test name::" +## +## Selecting tests by pattern +## ========================== +## +## A single ``"*"`` can be used for globbing. +## +## Delimit the end of a suite name with ``"::"``. +## +## Tests matching **any** of the arguments are executed. +## +## .. code:: +## +## nim c -r test fast_suite::mytest1 fast_suite::mytest2 +## nim c -r test "fast_suite::mytest*" +## nim c -r test "auth*::" "crypto::hashing*" +## # Run suites starting with 'bug #' and standalone tests starting with '#' +## nim c -r test 'bug #*::' '::#*' ## ## Example ## ------- @@ -121,7 +149,7 @@ var checkpoints {.threadvar.}: seq[string] formatters {.threadvar.}: seq[OutputFormatter] - testsToRun {.threadvar.}: HashSet[string] + testsFilters {.threadvar.}: HashSet[string] when declared(stdout): abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR") @@ -300,22 +328,63 @@ method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) = method suiteEnded*(formatter: JUnitOutputFormatter) = formatter.stream.writeLine("\t") -proc shouldRun(testName: string): bool = - if testsToRun.len == 0: +proc glob(matcher, filter: string): bool = + ## Globbing using a single `*`. Empty `filter` matches everything. + if filter.len == 0: return true - result = testName in testsToRun + if not filter.contains('*'): + return matcher == filter + + let beforeAndAfter = filter.split('*', maxsplit=1) + if beforeAndAfter.len == 1: + # "foo*" + return matcher.startswith(beforeAndAfter[0]) + + if matcher.len < filter.len - 1: + return false # "12345" should not match "123*345" + + return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(beforeAndAfter[1]) + +proc matchFilter(suiteName, testName, filter: string): bool = + if filter == "": + return true + if testName == filter: + # corner case for tests containing "::" in their name + return true + let suiteAndTestFilters = filter.split("::", maxsplit=1) + + if suiteAndTestFilters.len == 1: + # no suite specified + let test_f = suiteAndTestFilters[0] + return glob(testName, test_f) + + return glob(suiteName, suiteAndTestFilters[0]) and glob(testName, suiteAndTestFilters[1]) + +when defined(testing): export matchFilter + +proc shouldRun(currentSuiteName, testName: string): bool = + ## Check if a test should be run by matching suiteName and testName against + ## test filters. + if testsFilters.len == 0: + return true + + for f in testsFilters: + if matchFilter(currentSuiteName, testName, f): + return true + + return false proc ensureInitialized() = if formatters == nil: formatters = @[OutputFormatter(defaultConsoleFormatter())] - if not testsToRun.isValid: - testsToRun.init() + if not testsFilters.isValid: + testsFilters.init() when declared(paramCount): # Read tests to run from the command line. for i in 1 .. paramCount(): - testsToRun.incl(paramStr(i)) + testsFilters.incl(paramStr(i)) # These two procs are added as workarounds for # https://github.com/nim-lang/Nim/issues/5549 @@ -395,7 +464,7 @@ template test*(name, body) {.dirty.} = ensureInitialized() - if shouldRun(name): + if shouldRun(when declared(testSuiteName): testSuiteName else: "", name): checkpoints = @[] var testStatusIMPL {.inject.} = OK diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index e4a8018713..86b9fd0370 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -13,6 +13,8 @@ discard """ [Suite] bug #5784 +[Suite] test name filtering + ''' """ @@ -120,3 +122,39 @@ suite "bug #5784": field: int var obj: Obj check obj.isNil or obj.field == 0 + +when defined(testing): + suite "test name filtering": + test "test name": + check matchFilter("suite1", "foo", "") + check matchFilter("suite1", "foo", "foo") + check matchFilter("suite1", "foo", "::") + check matchFilter("suite1", "foo", "*") + check matchFilter("suite1", "foo", "::foo") + check matchFilter("suite1", "::foo", "::foo") + + test "test name - glob": + check matchFilter("suite1", "foo", "f*") + check matchFilter("suite1", "foo", "*oo") + check matchFilter("suite1", "12345", "12*345") + check matchFilter("suite1", "q*wefoo", "q*wefoo") + check false == matchFilter("suite1", "foo", "::x") + check false == matchFilter("suite1", "foo", "::x*") + check false == matchFilter("suite1", "foo", "::*x") + # overlap + check false == matchFilter("suite1", "12345", "123*345") + check matchFilter("suite1", "ab*c::d*e::f", "ab*c::d*e::f") + + test "suite name": + check matchFilter("suite1", "foo", "suite1::") + check false == matchFilter("suite1", "foo", "suite2::") + check matchFilter("suite1", "qwe::foo", "qwe::foo") + check matchFilter("suite1", "qwe::foo", "suite1::qwe::foo") + + test "suite name - glob": + check matchFilter("suite1", "foo", "::*") + check matchFilter("suite1", "foo", "*::*") + check matchFilter("suite1", "foo", "*::foo") + check false == matchFilter("suite1", "foo", "*ite2::") + check matchFilter("suite1", "q**we::foo", "q**we::foo") + check matchFilter("suite1", "a::b*c::d*e", "a::b*c::d*e") From 7f6afa9e9b554799cf9a39d0f8cc7d35e47a2cb4 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 19 Dec 2017 13:57:37 +0200 Subject: [PATCH 06/17] Make asyncjs Future[void] play nicely with last line discardable calls --- lib/js/asyncjs.nim | 32 +++++++++++++++++++++++++++++--- tests/js/tasync.nim | 9 +++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim index bde3d787fb..73dbfb1e7d 100644 --- a/lib/js/asyncjs.nim +++ b/lib/js/asyncjs.nim @@ -83,20 +83,46 @@ proc replaceReturn(node: var NimNode) = replaceReturn(son) inc z +proc isFutureVoid(node: NimNode): bool = + result = node.kind == nnkBracketExpr and + node[0].kind == nnkIdent and $node[0] == "Future" and + node[1].kind == nnkIdent and $node[1] == "void" + proc generateJsasync(arg: NimNode): NimNode = assert arg.kind == nnkProcDef result = arg + var isVoid = false if arg.params[0].kind == nnkEmpty: result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void")) + isVoid = true + elif isFutureVoid(arg.params[0]): + isVoid = true var code = result.body replaceReturn(code) result.body = nnkStmtList.newTree() - var q = quote: + + var awaitFunction = quote: proc await[T](f: Future[T]): T {.importcpp: "(await #)".} - proc jsResolve[T](a: T): Future[T] {.importcpp: "#".} - result.body.add(q) + result.body.add(awaitFunction) + + var resolve: NimNode + var jsResolveNode = ident("jsResolve") + if isVoid: + resolve = quote: + var `jsResolveNode` {.importcpp: "undefined".}: Future[void] + else: + resolve = quote: + proc jsResolve[T](a: T): Future[T] {.importcpp: "#".} + result.body.add(resolve) + for child in code: result.body.add(child) + + if isVoid: + var voidFix = quote: + return `jsResolveNode` + result.body.add(voidFix) + result.pragma = quote: {.codegenDecl: "async function $2($3)".} diff --git a/tests/js/tasync.nim b/tests/js/tasync.nim index a164827d20..8cc972a626 100644 --- a/tests/js/tasync.nim +++ b/tests/js/tasync.nim @@ -3,6 +3,7 @@ discard """ output: ''' 0 x +e ''' """ @@ -12,15 +13,19 @@ import asyncjs # for js proc y(e: int): Future[string] -proc x(e: int) {.async.} = +proc e: int {.discardable.} = + echo "e" + return 2 + +proc x(e: int): Future[void] {.async.} = var s = await y(e) echo s + e() proc y(e: int): Future[string] {.async.} = echo 0 return "x" - discard x(2) From b3dfc93beee2ac47a907ea77c1ed2da84ba4b672 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 19 Dec 2017 20:50:37 +0200 Subject: [PATCH 07/17] Fix forward --- lib/js/asyncjs.nim | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim index 73dbfb1e7d..c99170a49d 100644 --- a/lib/js/asyncjs.nim +++ b/lib/js/asyncjs.nim @@ -92,33 +92,37 @@ proc generateJsasync(arg: NimNode): NimNode = assert arg.kind == nnkProcDef result = arg var isVoid = false + var jsResolveNode = ident("jsResolve") + if arg.params[0].kind == nnkEmpty: result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void")) isVoid = true elif isFutureVoid(arg.params[0]): isVoid = true + var code = result.body replaceReturn(code) result.body = nnkStmtList.newTree() - var awaitFunction = quote: - proc await[T](f: Future[T]): T {.importcpp: "(await #)".} - result.body.add(awaitFunction) + if len(code) > 0: + var awaitFunction = quote: + proc await[T](f: Future[T]): T {.importcpp: "(await #)".} + result.body.add(awaitFunction) - var resolve: NimNode - var jsResolveNode = ident("jsResolve") - if isVoid: - resolve = quote: - var `jsResolveNode` {.importcpp: "undefined".}: Future[void] + var resolve: NimNode + if isVoid: + resolve = quote: + var `jsResolveNode` {.importcpp: "undefined".}: Future[void] + else: + resolve = quote: + proc jsResolve[T](a: T): Future[T] {.importcpp: "#".} + result.body.add(resolve) else: - resolve = quote: - proc jsResolve[T](a: T): Future[T] {.importcpp: "#".} - result.body.add(resolve) - + result.body = newEmptyNode() for child in code: result.body.add(child) - if isVoid: + if len(code) > 0 and isVoid: var voidFix = quote: return `jsResolveNode` result.body.add(voidFix) @@ -126,6 +130,7 @@ proc generateJsasync(arg: NimNode): NimNode = result.pragma = quote: {.codegenDecl: "async function $2($3)".} + macro async*(arg: untyped): untyped = ## Macro which converts normal procedures into ## javascript-compatible async procedures From 7b495e23d453c6528c9eee3ac8ae6982744ef403 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Wed, 20 Dec 2017 14:09:02 +0200 Subject: [PATCH 08/17] Fix the forward test --- tests/js/tasync.nim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/js/tasync.nim b/tests/js/tasync.nim index 8cc972a626..34ef97b8bf 100644 --- a/tests/js/tasync.nim +++ b/tests/js/tasync.nim @@ -1,7 +1,6 @@ discard """ disabled: true output: ''' -0 x e ''' @@ -11,7 +10,7 @@ import asyncjs # demonstrate forward definition # for js -proc y(e: int): Future[string] +proc y(e: int): Future[string] {.async.} proc e: int {.discardable.} = echo "e" @@ -23,8 +22,10 @@ proc x(e: int): Future[void] {.async.} = e() proc y(e: int): Future[string] {.async.} = - echo 0 - return "x" + if e > 0: + return await y(0) + else: + return "x" discard x(2) From eba544996d5629943dbf84c0eeedaf5b958d6363 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Wed, 20 Dec 2017 14:11:22 +0200 Subject: [PATCH 09/17] Fix docs! --- lib/js/asyncjs.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim index c99170a49d..ec410ee39a 100644 --- a/lib/js/asyncjs.nim +++ b/lib/js/asyncjs.nim @@ -44,10 +44,10 @@ ## resolve(game) ## return promise ## -## Forward definitions work properly, you just don't need to add the ``{.async.}`` pragma: +## Forward definitions work properly, you just need to always add the ``{.async.}`` pragma: ## ## .. code-block:: nim -## proc loadGame(name: string): Future[Game] +## proc loadGame(name: string): Future[Game] {.async.} ## ## JavaScript compatibility ## ~~~~~~~~~~~~~~~~~~~~~~~~~ From b22d9e4339f06e6af02f8da50e68a98707987612 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Thu, 21 Dec 2017 16:45:42 +0200 Subject: [PATCH 10/17] Fix #6906 --- lib/pure/asyncdispatch.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 675e8fc5eb..23eb80b37b 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -1234,7 +1234,7 @@ else: processBasicCallbacks(fd, writeList) result = true - if Event.User in events or events == {Event.Error}: + if Event.User in events: processBasicCallbacks(fd, readList) custom = true if rLength == 0: From a89b81eb9679d1351aceae766e5eaf1bf8faeffe Mon Sep 17 00:00:00 2001 From: skilchen Date: Thu, 21 Dec 2017 16:32:26 +0100 Subject: [PATCH 11/17] fixes #6353 (#6951) --- lib/pure/math.nim | 17 ++++++++++++---- tests/stdlib/tfrexp1.nim | 44 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tests/stdlib/tfrexp1.nim diff --git a/lib/pure/math.nim b/lib/pure/math.nim index a9dabfa486..cbd04a145b 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -351,15 +351,19 @@ proc round*[T: float32|float64](x: T, places: int = 0): T = result = round0(x*mult)/mult when not defined(JS): - proc frexp*(x: float32, exponent: var int): float32 {. + proc c_frexp*(x: float32, exponent: var int32): float32 {. importc: "frexp", header: "".} - proc frexp*(x: float64, exponent: var int): float64 {. + proc c_frexp*(x: float64, exponent: var int32): float64 {. importc: "frexp", header: "".} + proc frexp*[T, U](x: T, exponent: var U): T = ## Split a number into mantissa and exponent. ## `frexp` calculates the mantissa m (a float greater than or equal to 0.5 ## and less than 1) and the integer value n such that `x` (the original ## float value) equals m * 2**n. frexp stores n in `exponent` and returns ## m. + var exp: int32 + result = c_frexp(x, exp) + exponent = exp else: proc frexp*[T: float32|float64](x: T, exponent: var int): T = if x == 0.0: @@ -368,9 +372,14 @@ else: elif x < 0.0: result = -frexp(-x, exponent) else: - var ex = floor(log2(x)) - exponent = round(ex) + var ex = trunc(log2(x)) + exponent = int(ex) result = x / pow(2.0, ex) + if abs(result) >= 1: + inc(exponent) + result = result / 2 + if exponent == 1024 and result == 0.0: + result = 0.99999999999999988898 proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = ## Breaks `x` into an integral and a fractional part. diff --git a/tests/stdlib/tfrexp1.nim b/tests/stdlib/tfrexp1.nim new file mode 100644 index 0000000000..c6bb2b38cc --- /dev/null +++ b/tests/stdlib/tfrexp1.nim @@ -0,0 +1,44 @@ +discard """ + targets: "js c c++" + output: '''ok''' +""" + +import math +import strformat + +const manualTest = false + +proc frexp_test(lo, hi, step: float64) = + var exp: int + var frac: float64 + + var eps = 1e-15.float64 + + var x:float64 = lo + while x <= hi: + frac = frexp(x.float, exp) + let rslt = pow(2.0, float(exp)) * frac + + doAssert(abs(rslt - x) < eps) + + when manualTest: + echo fmt("x: {x:10.3f} exp: {exp:4d} frac: {frac:24.20f} check: {$(abs(rslt - x) < eps):-5s} {rslt: 9.3f}") + x += step + +when manualTest: + var exp: int + var frac: float64 + + for flval in [1.7976931348623157e+308, -1.7976931348623157e+308, # max, min float64 + 3.4028234663852886e+38, -3.4028234663852886e+38, # max, min float32 + 4.9406564584124654e-324, -4.9406564584124654e-324, # smallest/largest positive/negative float64 + 1.4012984643248171e-45, -1.4012984643248171e-45, # smallest/largest positive/negative float32 + 2.2250738585072014e-308, 1.1754943508222875e-38]: # smallest normal float64/float32 + frac = frexp(flval, exp) + echo fmt("{flval:25.16e}, {exp: 6d}, {frac: .20f} {frac * pow(2.0, float(exp)): .20e}") + + frexp_test(-1000.0, 1000.0, 0.0125) +else: + frexp_test(-1000000.0, 1000000.0, 0.125) + +echo "ok" From da90657317e8a57bae80ebd2d637a972d3b438ab Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 21 Dec 2017 17:14:31 +0100 Subject: [PATCH 12/17] make the new --genDeps feature optional since it makes compilations slower --- compiler/commands.nim | 2 +- compiler/main.nim | 3 ++- doc/advopt.txt | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/commands.nim b/compiler/commands.nim index de474c6e68..386d7bda86 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -611,7 +611,7 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "skipparentcfg": expectNoArg(switch, arg, pass, info) incl(gGlobalOptions, optSkipParentConfigFiles) - of "genscript": + of "genscript", "gendeps": expectNoArg(switch, arg, pass, info) incl(gGlobalOptions, optGenScript) of "colors": processOnOffSwitchG({optUseColors}, arg, pass, info) diff --git a/compiler/main.nim b/compiler/main.nim index db03f0e4db..08fc4b138a 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -80,7 +80,8 @@ proc commandCompileToC(graph: ModuleGraph; cache: IdentCache) = let proj = changeFileExt(gProjectFull, "") extccomp.callCCompiler(proj) extccomp.writeJsonBuildInstructions(proj) - writeDepsFile(graph, toGeneratedFile(proj, "")) + if optGenScript in gGlobalOptions: + writeDepsFile(graph, toGeneratedFile(proj, "")) proc commandJsonScript(graph: ModuleGraph; cache: IdentCache) = let proj = changeFileExt(gProjectFull, "") diff --git a/doc/advopt.txt b/doc/advopt.txt index ab10d65ba7..a1210118e3 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -37,6 +37,7 @@ Advanced options: --noMain do not generate a main procedure --genScript generate a compile script (in the 'nimcache' subdirectory named 'compile_$project$scriptext') + --genDeps generate a '.deps' file containing the dependencies --os:SYMBOL set the target operating system (cross-compilation) --cpu:SYMBOL set the target processor (cross-compilation) --debuginfo enables debug information From c2d91771bc1593fc8432392a75223dc1106bcfa3 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 21 Dec 2017 19:05:23 +0100 Subject: [PATCH 13/17] DFA works for simple examples --- compiler/dfa.nim | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/compiler/dfa.nim b/compiler/dfa.nim index 66a71e8391..6bb7a03a9e 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -344,20 +344,20 @@ proc dfa(code: seq[Instr]) = case code[i].kind of use, useWithinCall: u[i].incl(code[i].sym.id) of def: d[i].incl(code[i].sym.id) - of fork: + of fork, goto: let d = i+code[i].dest backrefs.add(d, i) - of goto: discard var w = @[0] var maxIters = 50 var someChange = true - while w.len > 0 and maxIters > 0 and someChange: + var takenGotos = initIntSet() + while w.len > 0 and maxIters > 0: # and someChange: dec maxIters var pc = w.pop() # w[^1] var prevPc = -1 # this simulates a single linear control flow execution: - while pc < code.len and someChange: + while pc < code.len: # according to the paper, it is better to shrink the working set here # in this inner loop: #let widx = w.find(pc) @@ -386,17 +386,21 @@ proc dfa(code: seq[Instr]) = if def notin d[prevPc]: excl(intersect, def) someChange = true + when defined(debugDfa): + echo "Excluding ", pc, " prev ", prevPc assign d[pc], intersect # our interpretation ![I!]: prevPc = pc + when defined(debugDfa): + echo "looking at ", pc case code[pc].kind of goto: # we must leave endless loops eventually: - #if someChange: - pc = pc + code[pc].dest - #else: - # inc pc + if not takenGotos.containsOrIncl(pc) or someChange: + pc = pc + code[pc].dest + else: + inc pc of fork: # we follow the next instruction but push the dest onto our "work" stack: #if someChange: @@ -405,6 +409,10 @@ proc dfa(code: seq[Instr]) = of use, useWithinCall, def: inc pc + when defined(useDfa) and defined(debugDfa): + for i in 0.. Date: Sat, 23 Dec 2017 14:08:47 +0000 Subject: [PATCH 14/17] Add link to #6934 in changelog.md --- changelog.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 4d205faf89..efdd1f5207 100644 --- a/changelog.md +++ b/changelog.md @@ -102,10 +102,12 @@ This now needs to be written as: - Nim's ``rst2html`` command now supports the testing of code snippets via an RST extension that we called ``:test:``:: + ```rst .. code-block:: nim :test: # shows how the 'if' statement works if true: echo "yes" + ``` - The ``[]`` proc for strings now raises an ``IndexError`` exception when the specified slice is out of bounds. See issue [#6223](https://github.com/nim-lang/Nim/issues/6223) for more details. @@ -130,7 +132,8 @@ This now needs to be written as: - The ``random`` procs in ``random.nim`` have all been deprecated. Instead use the new ``rand`` procs. The module now exports the state of the random number generator as type ``Rand`` so multiple threads can easily use their - own random number generators that do not require locking. + own random number generators that do not require locking. For more information + about this rename see issue [#6934](https://github.com/nim-lang/Nim/issues/6934) - The compiler is now more consistent in its treatment of ambiguous symbols: Types that shadow procs and vice versa are marked as ambiguous (bug #6693). - ``yield`` (or ``await`` which is mapped to ``yield``) never worked reliably From 6bd3a2826fb559d4e88cd2fa5ba89be995553700 Mon Sep 17 00:00:00 2001 From: Mathias Stearn Date: Sun, 24 Dec 2017 09:23:17 -0500 Subject: [PATCH 15/17] cmp(x, y: string) now uses memcmp rather than strcmp (#6869) (#6968) --- lib/system.nim | 5 ++++- lib/system/sysstr.nim | 8 ++++---- tests/stdlib/tstring.nim | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index 83e87683ab..85643891ba 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2916,7 +2916,10 @@ when not defined(JS): #and not defined(nimscript): elif x > y: result = 1 else: result = 0 else: - result = int(c_strcmp(x, y)) + let minlen = min(x.len, y.len) + result = int(c_memcmp(x.cstring, y.cstring, minlen.csize)) + if result == 0: + result = x.len - y.len when defined(nimscript): proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.} diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 56b8ade97a..4c5f3d9a15 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -24,10 +24,10 @@ proc cmpStrings(a, b: NimString): int {.inline, compilerProc.} = if a == b: return 0 if a == nil: return -1 if b == nil: return 1 - when defined(nimNoArrayToCstringConversion): - return c_strcmp(addr a.data, addr b.data) - else: - return c_strcmp(a.data, b.data) + let minlen = min(a.len, b.len) + result = c_memcmp(addr a.data, addr b.data, minlen.csize) + if result == 0: + result = a.len - b.len proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} = if a == b: return true diff --git a/tests/stdlib/tstring.nim b/tests/stdlib/tstring.nim index 904bc462a2..6607461500 100644 --- a/tests/stdlib/tstring.nim +++ b/tests/stdlib/tstring.nim @@ -56,4 +56,24 @@ proc test_string_slice() = echo("OK") +proc test_string_cmp() = + let world = "hello\0world" + let earth = "hello\0earth" + let short = "hello\0" + let hello = "hello" + let goodbye = "goodbye" + + doAssert world == world + doAssert world != earth + doAssert world != short + doAssert world != hello + doAssert world != goodbye + + doAssert cmp(world, world) == 0 + doAssert cmp(world, earth) > 0 + doAssert cmp(world, short) > 0 + doAssert cmp(world, hello) > 0 + doAssert cmp(world, goodbye) > 0 + test_string_slice() +test_string_cmp() From 2b3ec0a7c66d2246371ed51348aaa87d4c3cf0f9 Mon Sep 17 00:00:00 2001 From: cooldome Date: Mon, 25 Dec 2017 00:22:03 +0300 Subject: [PATCH 16/17] Implement language feature #6885 (#6954) --- changelog.md | 22 ++++++++++++++ compiler/msgs.nim | 6 ++-- compiler/pragmas.nim | 2 ++ compiler/sem.nim | 13 ++++++++ compiler/semstmts.nim | 24 ++++++++------- lib/system/chcks.nim | 1 - tests/casestmt/tcasestm.nim | 59 +++++++++++++++++++++++++++++++++++++ tests/pragmas/tnoreturn.nim | 18 +++++++++++ 8 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 tests/pragmas/tnoreturn.nim diff --git a/changelog.md b/changelog.md index efdd1f5207..5734a4cb12 100644 --- a/changelog.md +++ b/changelog.md @@ -144,3 +144,25 @@ This now needs to be written as: - codegenDecl pragma now works for the JavaScript backend. It returns an empty string for function return type placeholders. - Asynchronous programming for the JavaScript backend using the `asyncjs` module. +- Extra semantic checks for procs with noreturn pragma: return type is not allowed, + statements after call to noreturn procs are no longer allowed. +- Noreturn proc calls and raising exceptions branches are now skipped during common type + deduction in if and case expressions. The following code snippets now compile: +```nim +import strutils +let str = "Y" +let a = case str: + of "Y": true + of "N": false + else: raise newException(ValueError, "Invalid boolean") +let b = case str: + of nil, "": raise newException(ValueError, "Invalid boolean") + elif str.startsWith("Y"): true + elif str.startsWith("N"): false + else: false +let c = if str == "Y": true + elif str == "N": false + else: + echo "invalid bool" + quit("this is the end") +``` diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 2668c72ae0..4e6226122e 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -26,7 +26,8 @@ type errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation, errExceptionExpected, errExceptionAlreadyHandled, errYieldNotAllowedHere, errYieldNotAllowedInTryStmt, - errInvalidNumberOfYieldExpr, errCannotReturnExpr, errAttemptToRedefine, + errInvalidNumberOfYieldExpr, errCannotReturnExpr, + errNoReturnWithReturnTypeNotAllowed, errAttemptToRedefine, errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel, errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected, errInvalidVarSubstitution, errUnknownVar, errUnknownCcompiler, @@ -179,8 +180,9 @@ const errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator", errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expressions", errCannotReturnExpr: "current routine cannot return an expression", + errNoReturnWithReturnTypeNotAllowed: "routines with NoReturn pragma are not allowed to have return type", errAttemptToRedefine: "redefinition of \'$1\'", - errStmtInvalidAfterReturn: "statement not allowed after \'return\', \'break\', \'raise\' or \'continue'", + errStmtInvalidAfterReturn: "statement not allowed after \'return\', \'break\', \'raise\', \'continue\' or proc call with noreturn pragma", errStmtExpected: "statement expected", errInvalidLabel: "\'$1\' is no label", errInvalidCmdLineOption: "invalid command line option: \'$1\'", diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index b598cadb20..35fedf4ea1 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -771,6 +771,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, of wNoreturn: noVal(it) incl(sym.flags, sfNoReturn) + if sym.ast[paramsPos][0].kind != nkEmpty: + localError(sym.ast[paramsPos][0].info, errNoReturnWithReturnTypeNotAllowed) of wDynlib: processDynLib(c, it, sym) of wCompilerproc: diff --git a/compiler/sem.nim b/compiler/sem.nim index bc994201d1..ababbd303c 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -165,6 +165,19 @@ proc commonType*(x, y: PType): PType = result = newType(k, r.owner) result.addSonSkipIntLit(r) +proc endsInNoReturn(n: PNode): bool = + # check if expr ends in raise exception or call of noreturn proc + var it = n + while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0: + it = it.lastSon + result = it.kind == nkRaiseStmt or + it.kind in nkCallKinds and it[0].kind == nkSym and sfNoReturn in it[0].sym.flags + +proc commonType*(x: PType, y: PNode): PType = + # ignore exception raising branches in case/if expressions + if endsInNoReturn(y): return x + commonType(x, y.typ) + proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym = result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) when defined(nimsuggest): diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 8ed120c98f..b1fa8c19b8 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -165,14 +165,14 @@ proc semIf(c: PContext, n: PNode): PNode = it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0])) when not newScopeForIf: openScope(c) it.sons[1] = semExprBranch(c, it.sons[1]) - typ = commonType(typ, it.sons[1].typ) + typ = commonType(typ, it.sons[1]) closeScope(c) elif it.len == 1: hasElse = true it.sons[0] = semExprBranchScope(c, it.sons[0]) - typ = commonType(typ, it.sons[0].typ) + typ = commonType(typ, it.sons[0]) else: illFormedAst(it) - if isEmptyType(typ) or typ.kind == tyNil or not hasElse: + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: for it in n: discardCheck(c, it.lastSon) result.kind = nkIfStmt # propagate any enforced VoidContext: @@ -180,7 +180,8 @@ proc semIf(c: PContext, n: PNode): PNode = else: for it in n: let j = it.len-1 - it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) + if not endsInNoReturn(it.sons[j]): + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) result.kind = nkIfExpr result.typ = typ @@ -213,7 +214,7 @@ proc semCase(c: PContext, n: PNode): PNode = semCaseBranch(c, n, x, i, covered) var last = sonsLen(x)-1 x.sons[last] = semExprBranchScope(c, x.sons[last]) - typ = commonType(typ, x.sons[last].typ) + typ = commonType(typ, x.sons[last]) of nkElifBranch: chckCovered = false checkSonsLen(x, 2) @@ -221,13 +222,13 @@ proc semCase(c: PContext, n: PNode): PNode = x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0])) when not newScopeForIf: openScope(c) x.sons[1] = semExprBranch(c, x.sons[1]) - typ = commonType(typ, x.sons[1].typ) + typ = commonType(typ, x.sons[1]) closeScope(c) of nkElse: chckCovered = false checkSonsLen(x, 1) x.sons[0] = semExprBranchScope(c, x.sons[0]) - typ = commonType(typ, x.sons[0].typ) + typ = commonType(typ, x.sons[0]) hasElse = true else: illFormedAst(x) @@ -237,7 +238,7 @@ proc semCase(c: PContext, n: PNode): PNode = else: localError(n.info, errNotAllCasesCovered) closeScope(c) - if isEmptyType(typ) or typ.kind == tyNil or not hasElse: + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) # propagate any enforced VoidContext: if typ == enforceVoidContext: @@ -246,7 +247,8 @@ proc semCase(c: PContext, n: PNode): PNode = for i in 1..n.len-1: var it = n.sons[i] let j = it.len-1 - it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) + if not endsInNoReturn(it.sons[j]): + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) result.typ = typ proc semTry(c: PContext, n: PNode): PNode = @@ -1851,8 +1853,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = else: n.typ = n.sons[i].typ if not isEmptyType(n.typ): n.kind = nkStmtListExpr - case n.sons[i].kind - of LastBlockStmts: + if n.sons[i].kind in LastBlockStmts or + n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and sfNoReturn in n.sons[i][0].sym.flags: for j in countup(i + 1, length - 1): case n.sons[j].kind of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr, diff --git a/lib/system/chcks.nim b/lib/system/chcks.nim index 1520f231e1..69b680dbdc 100644 --- a/lib/system/chcks.nim +++ b/lib/system/chcks.nim @@ -63,7 +63,6 @@ proc chckObj(obj, subclass: PNimType) {.compilerproc.} = while x != subclass: if x == nil: sysFatal(ObjectConversionError, "invalid object conversion") - break x = x.base proc chckObjAsgn(a, b: PNimType) {.compilerproc, inline.} = diff --git a/tests/casestmt/tcasestm.nim b/tests/casestmt/tcasestm.nim index 7ac20bf2f6..b005d8120e 100644 --- a/tests/casestmt/tcasestm.nim +++ b/tests/casestmt/tcasestm.nim @@ -36,5 +36,64 @@ var z = case i echo z #OUT ayyy +let str1 = "Y" +let str2 = "NN" +let a = case str1: + of "Y": true + of "N": false + else: + echo "no good" + quit("quiting") + +let b = case str2: + of nil, "": raise newException(ValueError, "Invalid boolean") + elif str2[0] == 'Y': true + elif str2[0] == 'N': false + else: "error".quit(2) + +doAssert(a == true) +doAssert(b == false) + +var bb: bool +doassert(not compiles( + bb = case str2: + of nil, "": raise newException(ValueError, "Invalid boolean") + elif str.startsWith("Y"): true + elif str.startsWith("N"): false +)) + +doassert(not compiles( + bb = case str2: + of "Y": true + of "N": false +)) + +doassert(not compiles( + bb = case str2: + of "Y": true + of "N": raise newException(ValueError, "N not allowed") +)) + +doassert(not compiles( + bb = case str2: + of "Y": raise newException(ValueError, "Invalid Y") + else: raise newException(ValueError, "Invalid N") +)) +doassert(not compiles( + bb = case str2: + of "Y": + raise newException(ValueError, "Invalid Y") + true + else: raise newException(ValueError, "Invalid") +)) + + +doassert(not compiles( + bb = case str2: + of "Y": + "invalid Y".quit(3) + true + else: raise newException(ValueError, "Invalid") +)) \ No newline at end of file diff --git a/tests/pragmas/tnoreturn.nim b/tests/pragmas/tnoreturn.nim new file mode 100644 index 0000000000..2075b352e6 --- /dev/null +++ b/tests/pragmas/tnoreturn.nim @@ -0,0 +1,18 @@ +discard """ +ccodeCheck: "\\i @'__attribute__((noreturn))' .*" +""" + +proc noret1*(i: int) {.noreturn.} = + echo i + +var p {.used.}: proc(i: int): int +doAssert(not compiles( + p = proc(i: int): int {.noreturn.} = i # noreturn lambda returns int +)) + + +doAssert(not compiles( + block: + noret1(5) + echo 1 # statement after noreturn +)) From 53cf0b2c24e5adc4fa99e49ddf1834991d663846 Mon Sep 17 00:00:00 2001 From: cooldome Date: Wed, 27 Dec 2017 12:09:24 +0300 Subject: [PATCH 17/17] Allow noreturn procs with void type (#6973) --- compiler/pragmas.nim | 2 +- tests/pragmas/tnoreturn.nim | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 35fedf4ea1..02b57d5a30 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -771,7 +771,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, of wNoreturn: noVal(it) incl(sym.flags, sfNoReturn) - if sym.ast[paramsPos][0].kind != nkEmpty: + if sym.typ[0] != nil: localError(sym.ast[paramsPos][0].info, errNoReturnWithReturnTypeNotAllowed) of wDynlib: processDynLib(c, it, sym) diff --git a/tests/pragmas/tnoreturn.nim b/tests/pragmas/tnoreturn.nim index 2075b352e6..4d00c60346 100644 --- a/tests/pragmas/tnoreturn.nim +++ b/tests/pragmas/tnoreturn.nim @@ -5,6 +5,10 @@ ccodeCheck: "\\i @'__attribute__((noreturn))' .*" proc noret1*(i: int) {.noreturn.} = echo i + +proc noret2*(i: int): void {.noreturn.} = + echo i + var p {.used.}: proc(i: int): int doAssert(not compiles( p = proc(i: int): int {.noreturn.} = i # noreturn lambda returns int