Fixes compilation issues in v3 compatibility mode (`-d:chronosHandleException`) (#545)

* add missing calls to await

* add test run in v3 compatibility

* fix semantics for chronosHandleException so it does not override local raises/handleException annotations

* distinguish between explicit override and default setting; fix test

* re-enable wrongly disabled check

* make implementation simpler/clearer

* update docs

* reflow long line

* word swap
This commit is contained in:
Giuliano Mega 2024-06-10 05:18:42 -03:00 committed by GitHub
parent c44406594f
commit 7630f39471
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 61 additions and 13 deletions

View File

@ -60,6 +60,14 @@ task test, "Run all tests":
run args & " --mm:refc", "tests/testall"
run args, "tests/testall"
task test_v3_compat, "Run all tests in v3 compatibility mode":
for args in testArguments:
if (NimMajor, NimMinor) > (1, 6):
# First run tests with `refc` memory manager.
run args & " --mm:refc -d:chronosHandleException", "tests/testall"
run args & " -d:chronosHandleException", "tests/testall"
task test_libbacktrace, "test with libbacktrace":
if platform != "x86":
let allArgs = @[

View File

@ -219,12 +219,14 @@ proc decodeParams(params: NimNode): AsyncParams =
var
raw = false
raises: NimNode = nil
handleException = chronosHandleException
handleException = false
hasLocalAnnotations = false
for param in params:
param.expectKind(nnkExprColonExpr)
if param[0].eqIdent("raises"):
hasLocalAnnotations = true
param[1].expectKind(nnkBracket)
if param[1].len == 0:
raises = makeNoRaises()
@ -236,10 +238,14 @@ proc decodeParams(params: NimNode): AsyncParams =
# boolVal doesn't work in untyped macros it seems..
raw = param[1].eqIdent("true")
elif param[0].eqIdent("handleException"):
hasLocalAnnotations = true
handleException = param[1].eqIdent("true")
else:
warning("Unrecognised async parameter: " & repr(param[0]), param)
if not hasLocalAnnotations:
handleException = chronosHandleException
(raw, raises, handleException)
proc isEmpty(n: NimNode): bool {.compileTime.} =

View File

@ -720,7 +720,7 @@ proc newDatagramTransportCommon(cbproc: UnsafeDatagramCallback,
proc wrap(transp: DatagramTransport,
remote: TransportAddress) {.async: (raises: []).} =
try:
cbproc(transp, remote)
await cbproc(transp, remote)
except CatchableError as exc:
raiseAssert "Unexpected exception from stream server cbproc: " & exc.msg

View File

@ -2197,7 +2197,7 @@ proc createStreamServer*(host: TransportAddress,
proc wrap(server: StreamServer,
client: StreamTransport) {.async: (raises: []).} =
try:
cbproc(server, client)
await cbproc(server, client)
except CatchableError as exc:
raiseAssert "Unexpected exception from stream server cbproc: " & exc.msg

View File

@ -110,7 +110,7 @@ sometimes lead to compile errors around forward declarations, methods and
closures as Nim conservatively asssumes that any `Exception` might be raised
from those.
Make sure to excplicitly annotate these with `{.raises.}`:
Make sure to explicitly annotate these with `{.raises.}`:
```nim
# Forward declarations need to explicitly include a raises list:
@ -124,11 +124,12 @@ proc myfunction() =
let closure: MyClosure = myfunction
```
## Compatibility modes
For compatibility, `async` functions can be instructed to handle `Exception` as
well, specifying `handleException: true`. `Exception` that is not a `Defect` and
not a `CatchableError` will then be caught and remapped to
`AsyncExceptionError`:
**Individual functions.** For compatibility, `async` functions can be instructed
to handle `Exception` as well, specifying `handleException: true`. Any
`Exception` that is not a `Defect` and not a `CatchableError` will then be
caught and remapped to `AsyncExceptionError`:
```nim
proc raiseException() {.async: (handleException: true, raises: [AsyncExceptionError]).} =
@ -136,14 +137,32 @@ proc raiseException() {.async: (handleException: true, raises: [AsyncExceptionEr
proc callRaiseException() {.async: (raises: []).} =
try:
raiseException()
await raiseException()
except AsyncExceptionError as exc:
# The original Exception is available from the `parent` field
echo exc.parent.msg
```
This mode can be enabled globally with `-d:chronosHandleException` as a help
when porting code to `chronos` but should generally be avoided as global
configuration settings may interfere with libraries that use `chronos` leading
to unexpected behavior.
**Global flag.** This mode can be enabled globally with
`-d:chronosHandleException` as a help when porting code to `chronos`. The
behavior in this case will be that:
1. old-style functions annotated with plain `async` will behave as if they had
been annotated with `async: (handleException: true)`.
This is functionally equivalent to
`async: (handleException: true, raises: [CatchableError])` and will, as
before, remap any `Exception` that is not `Defect` into
`AsyncExceptionError`, while also allowing any `CatchableError` (including
`AsyncExceptionError`) to get through without compilation errors.
2. New-style functions with `async: (raises: [...])` annotations or their own
`handleException` annotations will not be affected.
The rationale here is to allow one to incrementally introduce exception
annotations and get compiler feedback while not requiring that every bit of
legacy code is updated at once.
This should be used sparingly and with care, however, as global configuration
settings may interfere with libraries that use `chronos` leading to unexpected
behavior.

View File

@ -8,6 +8,7 @@
import std/[macros, strutils]
import unittest2
import ../chronos
import ../chronos/config
{.used.}
@ -586,6 +587,20 @@ suite "Exceptions tracking":
waitFor(callCatchAll())
test "Global handleException does not override local annotations":
when chronosHandleException:
proc unnanotated() {.async.} = raise (ref CatchableError)()
checkNotCompiles:
proc annotated() {.async: (raises: [ValueError]).} =
raise (ref CatchableError)()
checkNotCompiles:
proc noHandleException() {.async: (handleException: false).} =
raise (ref Exception)()
else:
skip()
test "Results compatibility":
proc returnOk(): Future[Result[int, string]] {.async: (raises: []).} =
ok(42)