From 34aa579af4d5709c16423bc2807e9a1476492f28 Mon Sep 17 00:00:00 2001 From: E M <5089238+emizzle@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:05:19 +1000 Subject: [PATCH] fix(binding): if + binding statement list expressions is broken in nim 2.2.8 Requires change in the nim compiler to work: https://github.com/nim-lang/Nim/pull/25761 --- .gitignore | 3 + questionable.nimble | 2 +- testmodules/async/config.nims | 3 + testmodules/async/test.nim | 2 + testmodules/async/test.nimble | 16 ++++ testmodules/async/testAsyncDispatch.nim | 107 ++++++++++++++++++++++++ testmodules/async/testChronos.nim | 107 ++++++++++++++++++++++++ 7 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 testmodules/async/config.nims create mode 100644 testmodules/async/test.nim create mode 100644 testmodules/async/test.nimble create mode 100644 testmodules/async/testAsyncDispatch.nim create mode 100644 testmodules/async/testChronos.nim diff --git a/.gitignore b/.gitignore index 442aeef..0c098dd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ nimbledeps nimble.develop nimble.paths +nimbledeps +.claude +nimcache diff --git a/questionable.nimble b/questionable.nimble index e2439fe..c7a541b 100644 --- a/questionable.nimble +++ b/questionable.nimble @@ -4,7 +4,7 @@ description = "Elegant optional types" license = "MIT" task test, "Runs the test suite": - for module in ["options", "results", "stew"]: + for module in ["options", "results", "stew", "async"]: withDir "testmodules/" & module: delEnv "NIMBLE_DIR" # use nimbledeps dir exec "nimble install -d -y" diff --git a/testmodules/async/config.nims b/testmodules/async/config.nims new file mode 100644 index 0000000..be1be8f --- /dev/null +++ b/testmodules/async/config.nims @@ -0,0 +1,3 @@ +--path:"../.." +--threads:on +import "../../config.nims" diff --git a/testmodules/async/test.nim b/testmodules/async/test.nim new file mode 100644 index 0000000..8a1a305 --- /dev/null +++ b/testmodules/async/test.nim @@ -0,0 +1,2 @@ +import ./testChronos +import ./testAsyncDispatch diff --git a/testmodules/async/test.nimble b/testmodules/async/test.nimble new file mode 100644 index 0000000..b3126e2 --- /dev/null +++ b/testmodules/async/test.nimble @@ -0,0 +1,16 @@ +version = "0.1.0" +author = "Questionable Authors" +description = "Questionable tests for pkg/chronos" +license = "MIT" + +requires "chronos >= 4.2.2" +requires "asynctest >= 0.5.4" + +let nimBin = getEnv("NIM_PATH", "nim") + +when (NimMajor, NimMinor) >= (1, 6): + task test, "Runs the test suite": + exec nimBin & " c -f -r --skipParentCfg --nimcache:./nimcache test.nim" +else: + task test, "Runs the test suite": + echo "Warning: Skipping test with chronos on Nim < 1.6" diff --git a/testmodules/async/testAsyncDispatch.nim b/testmodules/async/testAsyncDispatch.nim new file mode 100644 index 0000000..8028d39 --- /dev/null +++ b/testmodules/async/testAsyncDispatch.nim @@ -0,0 +1,107 @@ +import std/asyncdispatch +import pkg/questionable/results +import pkg/asynctest/asyncdispatch/unittest + +{.experimental: "parallel".} + +suite "chronos": + var asyncSuccessCalled: bool + var asyncFailureCalled: bool + var assignValueCalled: bool + var assignValueFailedCalled: bool + var returnBoolCalled: bool + + setup: + asyncSuccessCalled = false + asyncFailureCalled = false + assignValueCalled = false + assignValueFailedCalled = false + returnBoolCalled = false + + proc asyncSuccess(): Future[?!int] {.async.} = + asyncSuccessCalled = true + success 42 + + proc asyncFailure(): Future[?!int] {.async.} = + asyncFailureCalled = true + failure "failed to complete async operation" + + proc assignValue(): ?!int = + assignValueCalled = true + success 43 + + proc assignValueFailed(): ?!int = + assignValueFailedCalled = true + failure "failed to assign value" + + proc returnBool(val: bool): bool = + returnBoolCalled = true + return val + + test "=? works with if + await": + if a =? (await asyncSuccess()): + check a == 42 + else: + fail() + + test "=? works with if + await + value binding (and)": + if a =? (await asyncSuccess()) and b =? assignValue(): + check a == 42 + check b == 43 + else: + fail() + + test "=? works with if + await failure + value binding -- short circuit doesn't works (and)": + if a =? (await asyncFailure()) and b =? assignValue(): + fail() + + check assignValueCalled # should not be called if short circuiting works for statement list expressions + + test "=? works with if + await failure + non-statement list expression -- short circuit works (and)": + if a =? (await asyncFailure()) and returnBool(true): + fail() + + check not returnBoolCalled + + test "=? works with if + value binding + await (and)": + if a =? assignValue() and b =? (await asyncSuccess()): + check a == 43 + check b == 42 + else: + fail() + + test "=? works with if + value binding + await -- short circuit works (and)": + if a =? assignValueFailed() and b =? (await asyncSuccess()): + fail() + check asyncSuccessCalled # should not be called if short circuiting works for statement list expressions + + test "=? works with if + await + value binding (or)": + if a =? (await asyncSuccess()) or b =? assignValue(): + check a == 42 + check b == 43 + else: + fail() + + test "=? works with if + value binding + await -- short circuit doesn't work (or)": + if a =? assignValue() or b =? (await asyncSuccess()): + check a == 43 + check b == 42 + check asyncSuccessCalled # should not be called if short circuiting worked for statement list expressions + else: + fail() + + test "=? works with if + value binding + non-statement list expression -- short circuit works (or)": + if a =? assignValue() or returnBool(true): + check a == 43 + check not returnBoolCalled + else: + fail() + + test "=? works with if + value binding failure + await (or)": + if a =? assignValueFailed() or b =? (await asyncSuccess()): + check a == default int + check b == 42 + else: + fail() + + diff --git a/testmodules/async/testChronos.nim b/testmodules/async/testChronos.nim new file mode 100644 index 0000000..3d09338 --- /dev/null +++ b/testmodules/async/testChronos.nim @@ -0,0 +1,107 @@ +import pkg/questionable/results +import pkg/chronos +import pkg/asynctest/chronos/unittest + +{.experimental: "parallel".} + +suite "chronos": + var asyncSuccessCalled: bool + var asyncFailureCalled: bool + var assignValueCalled: bool + var assignValueFailedCalled: bool + var returnBoolCalled: bool + + setup: + asyncSuccessCalled = false + asyncFailureCalled = false + assignValueCalled = false + assignValueFailedCalled = false + returnBoolCalled = false + + proc asyncSuccess(): Future[?!int] {.async.} = + asyncSuccessCalled = true + success 42 + + proc asyncFailure(): Future[?!int] {.async.} = + asyncFailureCalled = true + failure "failed to complete async operation" + + proc assignValue(): ?!int = + assignValueCalled = true + success 43 + + proc assignValueFailed(): ?!int = + assignValueFailedCalled = true + failure "failed to assign value" + + proc returnBool(val: bool): bool = + returnBoolCalled = true + return val + + test "=? works with if + await": + if a =? (await asyncSuccess()): + check a == 42 + else: + fail() + + test "=? works with if + await + value binding (and)": + if a =? (await asyncSuccess()) and b =? assignValue(): + check a == 42 + check b == 43 + else: + fail() + + test "=? works with if + await failure + value binding -- short circuit doesn't works (and)": + if a =? (await asyncFailure()) and b =? assignValue(): + fail() + + check assignValueCalled # should not be called if short circuiting works for statement list expressions + + test "=? works with if + await failure + non-statement list expression -- short circuit works (and)": + if a =? (await asyncFailure()) and returnBool(true): + fail() + + check not returnBoolCalled + + test "=? works with if + value binding + await (and)": + if a =? assignValue() and b =? (await asyncSuccess()): + check a == 43 + check b == 42 + else: + fail() + + test "=? works with if + value binding + await -- short circuit works (and)": + if a =? assignValueFailed() and b =? (await asyncSuccess()): + fail() + check asyncSuccessCalled # should not be called if short circuiting works for statement list expressions + + test "=? works with if + await + value binding (or)": + if a =? (await asyncSuccess()) or b =? assignValue(): + check a == 42 + check b == 43 + else: + fail() + + test "=? works with if + value binding + await -- short circuit doesn't work (or)": + if a =? assignValue() or b =? (await asyncSuccess()): + check a == 43 + check b == 42 + check asyncSuccessCalled # should not be called if short circuiting worked for statement list expressions + else: + fail() + + test "=? works with if + value binding + non-statement list expression -- short circuit works (or)": + if a =? assignValue() or returnBool(true): + check a == 43 + check not returnBoolCalled + else: + fail() + + test "=? works with if + value binding failure + await (or)": + if a =? assignValueFailed() or b =? (await asyncSuccess()): + check a == default int + check b == 42 + else: + fail() + +