`asyncraises` -> `async: (raises: ..., raw: ...)` (#455)
Per discussion in https://github.com/status-im/nim-chronos/pull/251#issuecomment-1559233139, `async: (parameters..)` is introduced as a way to customize the async transformation instead of relying on separate keywords (like asyncraises). Two parameters are available as of now: `raises`: controls the exception effect tracking `raw`: disables body transformation Parameters are added to `async` as a tuple allowing more params to be added easily in the future: ```nim: proc f() {.async: (name: value, ...).}` ```
This commit is contained in:
parent
be2edab3ac
commit
cd6369c048
110
README.md
110
README.md
|
@ -46,18 +46,18 @@ Submit a PR to add yours!
|
||||||
|
|
||||||
### Concepts
|
### Concepts
|
||||||
|
|
||||||
Chronos implements the async/await paradigm in a self-contained library, using
|
Chronos implements the async/await paradigm in a self-contained library using
|
||||||
macros, with no specific helpers from the compiler.
|
the macro and closure iterator transformation features provided by Nim.
|
||||||
|
|
||||||
Our event loop is called a "dispatcher" and a single instance per thread is
|
The event loop is called a "dispatcher" and a single instance per thread is
|
||||||
created, as soon as one is needed.
|
created, as soon as one is needed.
|
||||||
|
|
||||||
To trigger a dispatcher's processing step, we need to call `poll()` - either
|
To trigger a dispatcher's processing step, we need to call `poll()` - either
|
||||||
directly or through a wrapper like `runForever()` or `waitFor()`. This step
|
directly or through a wrapper like `runForever()` or `waitFor()`. Each step
|
||||||
handles any file descriptors, timers and callbacks that are ready to be
|
handles any file descriptors, timers and callbacks that are ready to be
|
||||||
processed.
|
processed.
|
||||||
|
|
||||||
`Future` objects encapsulate the result of an async procedure, upon successful
|
`Future` objects encapsulate the result of an `async` procedure upon successful
|
||||||
completion, and a list of callbacks to be scheduled after any type of
|
completion, and a list of callbacks to be scheduled after any type of
|
||||||
completion - be that success, failure or cancellation.
|
completion - be that success, failure or cancellation.
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ Exceptions inheriting from `CatchableError` are caught by hidden `try` blocks
|
||||||
and placed in the `Future.error` field, changing the future's status to
|
and placed in the `Future.error` field, changing the future's status to
|
||||||
`Failed`.
|
`Failed`.
|
||||||
|
|
||||||
When a future is awaited, that exception is re-raised, only to be caught again
|
When a future is awaited, that exception is re-raised only to be caught again
|
||||||
by a hidden `try` block in the calling async procedure. That's how these
|
by a hidden `try` block in the calling async procedure. That's how these
|
||||||
exceptions move up the async chain.
|
exceptions move up the async chain.
|
||||||
|
|
||||||
|
@ -214,57 +214,81 @@ by the transformation.
|
||||||
|
|
||||||
#### Checked exceptions
|
#### Checked exceptions
|
||||||
|
|
||||||
By specifying a `asyncraises` list to an async procedure, you can check which
|
By specifying a `raises` list to an async procedure, you can check which
|
||||||
exceptions can be thrown by it.
|
exceptions can be raised by it:
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
proc p1(): Future[void] {.async, asyncraises: [IOError].} =
|
proc p1(): Future[void] {.async: (raises: [IOError]).} =
|
||||||
assert not (compiles do: raise newException(ValueError, "uh-uh"))
|
assert not (compiles do: raise newException(ValueError, "uh-uh"))
|
||||||
raise newException(IOError, "works") # Or any child of IOError
|
raise newException(IOError, "works") # Or any child of IOError
|
||||||
```
|
|
||||||
|
|
||||||
Under the hood, the return type of `p1` will be rewritten to an internal type,
|
proc p2(): Future[void] {.async, (raises: [IOError]).} =
|
||||||
which will convey raises informations to `await`.
|
|
||||||
|
|
||||||
```nim
|
|
||||||
proc p2(): Future[void] {.async, asyncraises: [IOError].} =
|
|
||||||
await p1() # Works, because await knows that p1
|
await p1() # Works, because await knows that p1
|
||||||
# can only raise IOError
|
# can only raise IOError
|
||||||
```
|
```
|
||||||
|
|
||||||
Raw functions and callbacks that don't go through the `async` transformation but
|
Under the hood, the return type of `p1` will be rewritten to an internal type
|
||||||
still return a `Future` and interact with the rest of the framework also need to
|
which will convey raises informations to `await`.
|
||||||
be annotated with `asyncraises` to participate in the checked exception scheme:
|
|
||||||
|
### Raw functions
|
||||||
|
|
||||||
|
Raw functions are those that interact with `chronos` via the `Future` type but
|
||||||
|
whose body does not go through the async transformation.
|
||||||
|
|
||||||
|
Such functions are created by adding `raw: true` to the `async` parameters:
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
proc p3(): Future[void] {.async, asyncraises: [IOError].} =
|
proc rawAsync(): Future[void] {.async: (raw: true).} =
|
||||||
let fut: Future[void] = p1() # works
|
let future = newFuture[void]("rawAsync")
|
||||||
assert not compiles(await fut) # await lost informations about raises,
|
future.complete()
|
||||||
# so it can raise anything
|
return future
|
||||||
# Callbacks
|
|
||||||
assert not(compiles do: let cb1: proc(): Future[void] = p1) # doesn't work
|
|
||||||
let cb2: proc(): Future[void] {.async, asyncraises: [IOError].} = p1 # works
|
|
||||||
assert not(compiles do:
|
|
||||||
type c = proc(): Future[void] {.async, asyncraises: [IOError, ValueError].}
|
|
||||||
let cb3: c = p1 # doesn't work, the raises must match _exactly_
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
When `chronos` performs the `async` transformation, all code is placed in a
|
Raw functions must not raise exceptions directly - they are implicitly declared
|
||||||
a special `try/except` clause that re-routes exception handling to the `Future`.
|
as `raises: []` - instead they should store exceptions in the returned `Future`:
|
||||||
|
|
||||||
Beacuse of this re-routing, functions that return a `Future` instance manually
|
|
||||||
never directly raise exceptions themselves - instead, exceptions are handled
|
|
||||||
indirectly via `await` or `Future.read`. When writing raw async functions, they
|
|
||||||
too must not raise exceptions - instead, they must store exceptions in the
|
|
||||||
future they return:
|
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
proc p4(): Future[void] {.asyncraises: [ValueError].} =
|
proc rawFailure(): Future[void] {.async: (raw: true).} =
|
||||||
let fut = newFuture[void]
|
let future = newFuture[void]("rawAsync")
|
||||||
|
future.fail((ref ValueError)(msg: "Oh no!"))
|
||||||
|
return future
|
||||||
|
```
|
||||||
|
|
||||||
# Equivalent of `raise (ref ValueError)()` in raw async functions:
|
Raw functions can also use checked exceptions:
|
||||||
fut.fail((ref ValueError)(msg: "raising in raw async function"))
|
|
||||||
fut
|
```nim
|
||||||
|
proc rawAsyncRaises(): Future[void] {.async: (raw: true, raises: [IOError]).} =
|
||||||
|
let fut = newFuture[void]()
|
||||||
|
assert not (compiles do: fut.fail((ref ValueError)(msg: "uh-uh")))
|
||||||
|
fut.fail((ref IOError)(msg: "IO"))
|
||||||
|
return fut
|
||||||
|
```
|
||||||
|
|
||||||
|
### Callbacks and closures
|
||||||
|
|
||||||
|
Callback/closure types are declared using the `async` annotation as usual:
|
||||||
|
|
||||||
|
```nim
|
||||||
|
type MyCallback = proc(): Future[void] {.async.}
|
||||||
|
|
||||||
|
proc runCallback(cb: MyCallback) {.async: (raises: []).} =
|
||||||
|
try:
|
||||||
|
await cb()
|
||||||
|
except CatchableError:
|
||||||
|
discard # handle errors as usual
|
||||||
|
```
|
||||||
|
|
||||||
|
When calling a callback, it is important to remember that the given function
|
||||||
|
may raise and exceptions need to be handled.
|
||||||
|
|
||||||
|
Checked exceptions can be used to limit the exceptions that a callback can
|
||||||
|
raise:
|
||||||
|
|
||||||
|
```nim
|
||||||
|
type MyEasyCallback = proc: Future[void] {.async: (raises: []).}
|
||||||
|
|
||||||
|
proc runCallback(cb: MyEasyCallback) {.async: (raises: [])} =
|
||||||
|
await cb()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Platform independence
|
### Platform independence
|
||||||
|
@ -278,7 +302,7 @@ annotated as raising `CatchableError` only raise on _some_ platforms - in order
|
||||||
to work on all platforms, calling code must assume that they will raise even
|
to work on all platforms, calling code must assume that they will raise even
|
||||||
when they don't seem to do so on one platform.
|
when they don't seem to do so on one platform.
|
||||||
|
|
||||||
### Exception effects
|
### Strict exception mode
|
||||||
|
|
||||||
`chronos` currently offers minimal support for exception effects and `raises`
|
`chronos` currently offers minimal support for exception effects and `raises`
|
||||||
annotations. In general, during the `async` transformation, a generic
|
annotations. In general, during the `async` transformation, a generic
|
||||||
|
|
|
@ -131,4 +131,4 @@
|
||||||
import ./internal/[asyncengine, asyncfutures, asyncmacro, errors]
|
import ./internal/[asyncengine, asyncfutures, asyncmacro, errors]
|
||||||
|
|
||||||
export asyncfutures, asyncengine, errors
|
export asyncfutures, asyncengine, errors
|
||||||
export asyncmacro.async, asyncmacro.await, asyncmacro.awaitne, asyncraises
|
export asyncmacro.async, asyncmacro.await, asyncmacro.awaitne
|
||||||
|
|
|
@ -21,7 +21,7 @@ export Port
|
||||||
export deques, errors, futures, timer, results
|
export deques, errors, futures, timer, results
|
||||||
|
|
||||||
export
|
export
|
||||||
asyncmacro.async, asyncmacro.await, asyncmacro.awaitne, asyncmacro.asyncraises
|
asyncmacro.async, asyncmacro.await, asyncmacro.awaitne
|
||||||
|
|
||||||
const
|
const
|
||||||
MaxEventsCount* = 64
|
MaxEventsCount* = 64
|
||||||
|
|
|
@ -102,18 +102,12 @@ template newFuture*[T](fromProc: static[string] = "",
|
||||||
else:
|
else:
|
||||||
newFutureImpl[T](getSrcLocation(fromProc), flags)
|
newFutureImpl[T](getSrcLocation(fromProc), flags)
|
||||||
|
|
||||||
macro getFutureExceptions(T: typedesc): untyped =
|
template newInternalRaisesFuture*[T, E](fromProc: static[string] = ""): auto =
|
||||||
if getTypeInst(T)[1].len > 2:
|
|
||||||
getTypeInst(T)[1][2]
|
|
||||||
else:
|
|
||||||
ident"void"
|
|
||||||
|
|
||||||
template newInternalRaisesFuture*[T](fromProc: static[string] = ""): auto =
|
|
||||||
## Creates a new future.
|
## Creates a new future.
|
||||||
##
|
##
|
||||||
## Specifying ``fromProc``, which is a string specifying the name of the proc
|
## Specifying ``fromProc``, which is a string specifying the name of the proc
|
||||||
## that this future belongs to, is a good habit as it helps with debugging.
|
## that this future belongs to, is a good habit as it helps with debugging.
|
||||||
newInternalRaisesFutureImpl[T, getFutureExceptions(typeof(result))](getSrcLocation(fromProc))
|
newInternalRaisesFutureImpl[T, E](getSrcLocation(fromProc))
|
||||||
|
|
||||||
template newFutureSeq*[A, B](fromProc: static[string] = ""): FutureSeq[A, B] =
|
template newFutureSeq*[A, B](fromProc: static[string] = ""): FutureSeq[A, B] =
|
||||||
## Create a new future which can hold/preserve GC sequence until future will
|
## Create a new future which can hold/preserve GC sequence until future will
|
||||||
|
@ -476,7 +470,7 @@ macro internalCheckComplete*(f: InternalRaisesFuture): untyped =
|
||||||
let e = getTypeInst(f)[2]
|
let e = getTypeInst(f)[2]
|
||||||
let types = getType(e)
|
let types = getType(e)
|
||||||
|
|
||||||
if types.eqIdent("void"):
|
if isNoRaises(types):
|
||||||
return quote do:
|
return quote do:
|
||||||
if not(isNil(`f`.internalError)):
|
if not(isNil(`f`.internalError)):
|
||||||
raiseAssert("Unhandled future exception: " & `f`.error.msg)
|
raiseAssert("Unhandled future exception: " & `f`.error.msg)
|
||||||
|
@ -484,7 +478,6 @@ macro internalCheckComplete*(f: InternalRaisesFuture): untyped =
|
||||||
expectKind(types, nnkBracketExpr)
|
expectKind(types, nnkBracketExpr)
|
||||||
expectKind(types[0], nnkSym)
|
expectKind(types[0], nnkSym)
|
||||||
assert types[0].strVal == "tuple"
|
assert types[0].strVal == "tuple"
|
||||||
assert types.len > 1
|
|
||||||
|
|
||||||
let ifRaise = nnkIfExpr.newTree(
|
let ifRaise = nnkIfExpr.newTree(
|
||||||
nnkElifExpr.newTree(
|
nnkElifExpr.newTree(
|
||||||
|
@ -914,7 +907,7 @@ template cancel*(future: FutureBase) {.
|
||||||
cancelSoon(future, nil, nil, getSrcLocation())
|
cancelSoon(future, nil, nil, getSrcLocation())
|
||||||
|
|
||||||
proc cancelAndWait*(future: FutureBase, loc: ptr SrcLoc): Future[void] {.
|
proc cancelAndWait*(future: FutureBase, loc: ptr SrcLoc): Future[void] {.
|
||||||
asyncraises: [CancelledError].} =
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Perform cancellation ``future`` return Future which will be completed when
|
## Perform cancellation ``future`` return Future which will be completed when
|
||||||
## ``future`` become finished (completed with value, failed or cancelled).
|
## ``future`` become finished (completed with value, failed or cancelled).
|
||||||
##
|
##
|
||||||
|
@ -938,7 +931,7 @@ template cancelAndWait*(future: FutureBase): Future[void] =
|
||||||
## Cancel ``future``.
|
## Cancel ``future``.
|
||||||
cancelAndWait(future, getSrcLocation())
|
cancelAndWait(future, getSrcLocation())
|
||||||
|
|
||||||
proc noCancel*[F: SomeFuture](future: F): auto = # asyncraises: asyncraiseOf(future) - CancelledError
|
proc noCancel*[F: SomeFuture](future: F): auto = # async: (raw: true, raises: asyncraiseOf(future) - CancelledError
|
||||||
## Prevent cancellation requests from propagating to ``future`` while
|
## Prevent cancellation requests from propagating to ``future`` while
|
||||||
## forwarding its value or error when it finishes.
|
## forwarding its value or error when it finishes.
|
||||||
##
|
##
|
||||||
|
@ -978,7 +971,7 @@ proc noCancel*[F: SomeFuture](future: F): auto = # asyncraises: asyncraiseOf(fut
|
||||||
retFuture
|
retFuture
|
||||||
|
|
||||||
proc allFutures*(futs: varargs[FutureBase]): Future[void] {.
|
proc allFutures*(futs: varargs[FutureBase]): Future[void] {.
|
||||||
asyncraises: [CancelledError].} =
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Returns a future which will complete only when all futures in ``futs``
|
## Returns a future which will complete only when all futures in ``futs``
|
||||||
## will be completed, failed or canceled.
|
## will be completed, failed or canceled.
|
||||||
##
|
##
|
||||||
|
@ -1017,7 +1010,7 @@ proc allFutures*(futs: varargs[FutureBase]): Future[void] {.
|
||||||
retFuture
|
retFuture
|
||||||
|
|
||||||
proc allFutures*[T](futs: varargs[Future[T]]): Future[void] {.
|
proc allFutures*[T](futs: varargs[Future[T]]): Future[void] {.
|
||||||
asyncraises: [CancelledError].} =
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Returns a future which will complete only when all futures in ``futs``
|
## Returns a future which will complete only when all futures in ``futs``
|
||||||
## will be completed, failed or canceled.
|
## will be completed, failed or canceled.
|
||||||
##
|
##
|
||||||
|
@ -1031,7 +1024,7 @@ proc allFutures*[T](futs: varargs[Future[T]]): Future[void] {.
|
||||||
allFutures(nfuts)
|
allFutures(nfuts)
|
||||||
|
|
||||||
proc allFinished*[F: SomeFuture](futs: varargs[F]): Future[seq[F]] {.
|
proc allFinished*[F: SomeFuture](futs: varargs[F]): Future[seq[F]] {.
|
||||||
asyncraises: [CancelledError].} =
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Returns a future which will complete only when all futures in ``futs``
|
## Returns a future which will complete only when all futures in ``futs``
|
||||||
## will be completed, failed or canceled.
|
## will be completed, failed or canceled.
|
||||||
##
|
##
|
||||||
|
@ -1072,7 +1065,7 @@ proc allFinished*[F: SomeFuture](futs: varargs[F]): Future[seq[F]] {.
|
||||||
return retFuture
|
return retFuture
|
||||||
|
|
||||||
proc one*[F: SomeFuture](futs: varargs[F]): Future[F] {.
|
proc one*[F: SomeFuture](futs: varargs[F]): Future[F] {.
|
||||||
asyncraises: [ValueError, CancelledError].} =
|
async: (raw: true, raises: [ValueError, CancelledError]).} =
|
||||||
## Returns a future which will complete and return completed Future[T] inside,
|
## Returns a future which will complete and return completed Future[T] inside,
|
||||||
## when one of the futures in ``futs`` will be completed, failed or canceled.
|
## when one of the futures in ``futs`` will be completed, failed or canceled.
|
||||||
##
|
##
|
||||||
|
@ -1121,7 +1114,7 @@ proc one*[F: SomeFuture](futs: varargs[F]): Future[F] {.
|
||||||
return retFuture
|
return retFuture
|
||||||
|
|
||||||
proc race*(futs: varargs[FutureBase]): Future[FutureBase] {.
|
proc race*(futs: varargs[FutureBase]): Future[FutureBase] {.
|
||||||
asyncraises: [CancelledError].} =
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Returns a future which will complete and return completed FutureBase,
|
## Returns a future which will complete and return completed FutureBase,
|
||||||
## when one of the futures in ``futs`` will be completed, failed or canceled.
|
## when one of the futures in ``futs`` will be completed, failed or canceled.
|
||||||
##
|
##
|
||||||
|
@ -1173,7 +1166,8 @@ proc race*(futs: varargs[FutureBase]): Future[FutureBase] {.
|
||||||
when (chronosEventEngine in ["epoll", "kqueue"]) or defined(windows):
|
when (chronosEventEngine in ["epoll", "kqueue"]) or defined(windows):
|
||||||
import std/os
|
import std/os
|
||||||
|
|
||||||
proc waitSignal*(signal: int): Future[void] {.asyncraises: [AsyncError, CancelledError].} =
|
proc waitSignal*(signal: int): Future[void] {.
|
||||||
|
async: (raw: true, raises: [AsyncError, CancelledError]).} =
|
||||||
var retFuture = newFuture[void]("chronos.waitSignal()")
|
var retFuture = newFuture[void]("chronos.waitSignal()")
|
||||||
var signalHandle: Opt[SignalHandle]
|
var signalHandle: Opt[SignalHandle]
|
||||||
|
|
||||||
|
@ -1208,7 +1202,7 @@ when (chronosEventEngine in ["epoll", "kqueue"]) or defined(windows):
|
||||||
retFuture
|
retFuture
|
||||||
|
|
||||||
proc sleepAsync*(duration: Duration): Future[void] {.
|
proc sleepAsync*(duration: Duration): Future[void] {.
|
||||||
asyncraises: [CancelledError].} =
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Suspends the execution of the current async procedure for the next
|
## Suspends the execution of the current async procedure for the next
|
||||||
## ``duration`` time.
|
## ``duration`` time.
|
||||||
var retFuture = newFuture[void]("chronos.sleepAsync(Duration)")
|
var retFuture = newFuture[void]("chronos.sleepAsync(Duration)")
|
||||||
|
@ -1228,10 +1222,12 @@ proc sleepAsync*(duration: Duration): Future[void] {.
|
||||||
return retFuture
|
return retFuture
|
||||||
|
|
||||||
proc sleepAsync*(ms: int): Future[void] {.
|
proc sleepAsync*(ms: int): Future[void] {.
|
||||||
inline, deprecated: "Use sleepAsync(Duration)", asyncraises: [CancelledError].} =
|
inline, deprecated: "Use sleepAsync(Duration)",
|
||||||
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
result = sleepAsync(ms.milliseconds())
|
result = sleepAsync(ms.milliseconds())
|
||||||
|
|
||||||
proc stepsAsync*(number: int): Future[void] {.asyncraises: [CancelledError].} =
|
proc stepsAsync*(number: int): Future[void] {.
|
||||||
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Suspends the execution of the current async procedure for the next
|
## Suspends the execution of the current async procedure for the next
|
||||||
## ``number`` of asynchronous steps (``poll()`` calls).
|
## ``number`` of asynchronous steps (``poll()`` calls).
|
||||||
##
|
##
|
||||||
|
@ -1258,7 +1254,8 @@ proc stepsAsync*(number: int): Future[void] {.asyncraises: [CancelledError].} =
|
||||||
|
|
||||||
retFuture
|
retFuture
|
||||||
|
|
||||||
proc idleAsync*(): Future[void] {.asyncraises: [CancelledError].} =
|
proc idleAsync*(): Future[void] {.
|
||||||
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Suspends the execution of the current asynchronous task until "idle" time.
|
## Suspends the execution of the current asynchronous task until "idle" time.
|
||||||
##
|
##
|
||||||
## "idle" time its moment of time, when no network events were processed by
|
## "idle" time its moment of time, when no network events were processed by
|
||||||
|
@ -1277,7 +1274,7 @@ proc idleAsync*(): Future[void] {.asyncraises: [CancelledError].} =
|
||||||
retFuture
|
retFuture
|
||||||
|
|
||||||
proc withTimeout*[T](fut: Future[T], timeout: Duration): Future[bool] {.
|
proc withTimeout*[T](fut: Future[T], timeout: Duration): Future[bool] {.
|
||||||
asyncraises: [CancelledError].} =
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Returns a future which will complete once ``fut`` completes or after
|
## Returns a future which will complete once ``fut`` completes or after
|
||||||
## ``timeout`` milliseconds has elapsed.
|
## ``timeout`` milliseconds has elapsed.
|
||||||
##
|
##
|
||||||
|
|
|
@ -9,8 +9,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[algorithm, macros, sequtils],
|
std/[macros],
|
||||||
../[futures, config]
|
../[futures, config],
|
||||||
|
./raisesfutures
|
||||||
|
|
||||||
proc processBody(node, setResultSym, baseType: NimNode): NimNode {.compileTime.} =
|
proc processBody(node, setResultSym, baseType: NimNode): NimNode {.compileTime.} =
|
||||||
case node.kind
|
case node.kind
|
||||||
|
@ -32,10 +33,10 @@ proc processBody(node, setResultSym, baseType: NimNode): NimNode {.compileTime.}
|
||||||
node[i] = processBody(node[i], setResultSym, baseType)
|
node[i] = processBody(node[i], setResultSym, baseType)
|
||||||
node
|
node
|
||||||
|
|
||||||
proc wrapInTryFinally(fut, baseType, body, raisesTuple: NimNode): NimNode {.compileTime.} =
|
proc wrapInTryFinally(fut, baseType, body, raises: NimNode): NimNode {.compileTime.} =
|
||||||
# creates:
|
# creates:
|
||||||
# try: `body`
|
# try: `body`
|
||||||
# [for raise in raisesTuple]:
|
# [for raise in raises]:
|
||||||
# except `raise`: closureSucceeded = false; `castFutureSym`.fail(exc)
|
# except `raise`: closureSucceeded = false; `castFutureSym`.fail(exc)
|
||||||
# finally:
|
# finally:
|
||||||
# if closureSucceeded:
|
# if closureSucceeded:
|
||||||
|
@ -91,7 +92,17 @@ proc wrapInTryFinally(fut, baseType, body, raisesTuple: NimNode): NimNode {.comp
|
||||||
newCall(ident "fail", fut, excName)
|
newCall(ident "fail", fut, excName)
|
||||||
))
|
))
|
||||||
|
|
||||||
for exc in raisesTuple:
|
let raises = if raises == nil:
|
||||||
|
const defaultException =
|
||||||
|
when defined(chronosStrictException): "CatchableError"
|
||||||
|
else: "Exception"
|
||||||
|
nnkTupleConstr.newTree(ident(defaultException))
|
||||||
|
elif isNoRaises(raises):
|
||||||
|
nnkTupleConstr.newTree()
|
||||||
|
else:
|
||||||
|
raises
|
||||||
|
|
||||||
|
for exc in raises:
|
||||||
if exc.eqIdent("Exception"):
|
if exc.eqIdent("Exception"):
|
||||||
addCancelledError
|
addCancelledError
|
||||||
addCatchableError
|
addCatchableError
|
||||||
|
@ -182,42 +193,33 @@ proc cleanupOpenSymChoice(node: NimNode): NimNode {.compileTime.} =
|
||||||
for child in node:
|
for child in node:
|
||||||
result.add(cleanupOpenSymChoice(child))
|
result.add(cleanupOpenSymChoice(child))
|
||||||
|
|
||||||
proc getAsyncCfg(prc: NimNode): tuple[raises: bool, async: bool, raisesTuple: NimNode] =
|
proc decodeParams(params: NimNode): tuple[raw: bool, raises: NimNode] =
|
||||||
# reads the pragmas to extract the useful data
|
# decodes the parameter tuple given in `async: (name: value, ...)` to its
|
||||||
# and removes them
|
# recognised parts
|
||||||
|
params.expectKind(nnkTupleConstr)
|
||||||
|
|
||||||
var
|
var
|
||||||
foundRaises = -1
|
raw = false
|
||||||
foundAsync = -1
|
raises: NimNode = nil
|
||||||
|
|
||||||
for index, pragma in pragma(prc):
|
for param in params:
|
||||||
if pragma.kind == nnkExprColonExpr and pragma[0] == ident "asyncraises":
|
param.expectKind(nnkExprColonExpr)
|
||||||
foundRaises = index
|
|
||||||
elif pragma.eqIdent("async"):
|
|
||||||
foundAsync = index
|
|
||||||
elif pragma.kind == nnkExprColonExpr and pragma[0] == ident "raises":
|
|
||||||
warning("The raises pragma doesn't work on async procedure. " &
|
|
||||||
"Please remove it or use asyncraises instead")
|
|
||||||
|
|
||||||
result.raises = foundRaises >= 0
|
if param[0].eqIdent("raises"):
|
||||||
result.async = foundAsync >= 0
|
param[1].expectKind(nnkBracket)
|
||||||
result.raisesTuple = nnkTupleConstr.newTree()
|
if param[1].len == 0:
|
||||||
|
raises = makeNoRaises()
|
||||||
|
else:
|
||||||
|
raises = nnkTupleConstr.newTree()
|
||||||
|
for possibleRaise in param[1]:
|
||||||
|
raises.add(possibleRaise)
|
||||||
|
elif param[0].eqIdent("raw"):
|
||||||
|
# boolVal doesn't work in untyped macros it seems..
|
||||||
|
raw = param[1].eqIdent("true")
|
||||||
|
else:
|
||||||
|
warning("Unrecognised async parameter: " & repr(param[0]), param)
|
||||||
|
|
||||||
if foundRaises >= 0:
|
(raw, raises)
|
||||||
for possibleRaise in pragma(prc)[foundRaises][1]:
|
|
||||||
result.raisesTuple.add(possibleRaise)
|
|
||||||
if result.raisesTuple.len == 0:
|
|
||||||
result.raisesTuple = ident("void")
|
|
||||||
else:
|
|
||||||
when defined(chronosWarnMissingRaises):
|
|
||||||
warning("Async proc miss asyncraises")
|
|
||||||
const defaultException =
|
|
||||||
when defined(chronosStrictException): "CatchableError"
|
|
||||||
else: "Exception"
|
|
||||||
result.raisesTuple.add(ident(defaultException))
|
|
||||||
|
|
||||||
let toRemoveList = @[foundRaises, foundAsync].filterIt(it >= 0).sorted().reversed()
|
|
||||||
for toRemove in toRemoveList:
|
|
||||||
pragma(prc).del(toRemove)
|
|
||||||
|
|
||||||
proc isEmpty(n: NimNode): bool {.compileTime.} =
|
proc isEmpty(n: NimNode): bool {.compileTime.} =
|
||||||
# true iff node recursively contains only comments or empties
|
# true iff node recursively contains only comments or empties
|
||||||
|
@ -230,13 +232,18 @@ proc isEmpty(n: NimNode): bool {.compileTime.} =
|
||||||
else:
|
else:
|
||||||
false
|
false
|
||||||
|
|
||||||
proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
proc asyncSingleProc(prc, params: NimNode): NimNode {.compileTime.} =
|
||||||
## This macro transforms a single procedure into a closure iterator.
|
## This macro transforms a single procedure into a closure iterator.
|
||||||
## The ``async`` macro supports a stmtList holding multiple async procedures.
|
## The ``async`` macro supports a stmtList holding multiple async procedures.
|
||||||
if prc.kind notin {nnkProcTy, nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
|
if prc.kind notin {nnkProcTy, nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
|
||||||
error("Cannot transform " & $prc.kind & " into an async proc." &
|
error("Cannot transform " & $prc.kind & " into an async proc." &
|
||||||
" proc/method definition or lambda node expected.", prc)
|
" proc/method definition or lambda node expected.", prc)
|
||||||
|
|
||||||
|
for pragma in prc.pragma():
|
||||||
|
if pragma.kind == nnkExprColonExpr and pragma[0].eqIdent("raises"):
|
||||||
|
warning("The raises pragma doesn't work on async procedures - use " &
|
||||||
|
"`async: (raises: [...]) instead.", prc)
|
||||||
|
|
||||||
let returnType = cleanupOpenSymChoice(prc.params2[0])
|
let returnType = cleanupOpenSymChoice(prc.params2[0])
|
||||||
|
|
||||||
# Verify that the return type is a Future[T]
|
# Verify that the return type is a Future[T]
|
||||||
|
@ -254,22 +261,24 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
|
|
||||||
let
|
let
|
||||||
baseTypeIsVoid = baseType.eqIdent("void")
|
baseTypeIsVoid = baseType.eqIdent("void")
|
||||||
futureVoidType = nnkBracketExpr.newTree(ident "Future", ident "void")
|
(raw, raises) = decodeParams(params)
|
||||||
(hasRaises, isAsync, raisesTuple) = getAsyncCfg(prc)
|
internalFutureType =
|
||||||
|
if baseTypeIsVoid:
|
||||||
if hasRaises:
|
|
||||||
# Store `asyncraises` types in InternalRaisesFuture
|
|
||||||
prc.params2[0] = nnkBracketExpr.newTree(
|
|
||||||
newIdentNode("InternalRaisesFuture"),
|
|
||||||
baseType,
|
|
||||||
raisesTuple
|
|
||||||
)
|
|
||||||
elif baseTypeIsVoid:
|
|
||||||
# Adds the implicit Future[void]
|
|
||||||
prc.params2[0] =
|
|
||||||
newNimNode(nnkBracketExpr, prc).
|
newNimNode(nnkBracketExpr, prc).
|
||||||
add(newIdentNode("Future")).
|
add(newIdentNode("Future")).
|
||||||
add(newIdentNode("void"))
|
add(baseType)
|
||||||
|
else:
|
||||||
|
returnType
|
||||||
|
internalReturnType = if raises == nil:
|
||||||
|
internalFutureType
|
||||||
|
else:
|
||||||
|
nnkBracketExpr.newTree(
|
||||||
|
newIdentNode("InternalRaisesFuture"),
|
||||||
|
baseType,
|
||||||
|
raises
|
||||||
|
)
|
||||||
|
|
||||||
|
prc.params2[0] = internalReturnType
|
||||||
|
|
||||||
if prc.kind notin {nnkProcTy, nnkLambda}: # TODO: Nim bug?
|
if prc.kind notin {nnkProcTy, nnkLambda}: # TODO: Nim bug?
|
||||||
prc.addPragma(newColonExpr(ident "stackTrace", ident "off"))
|
prc.addPragma(newColonExpr(ident "stackTrace", ident "off"))
|
||||||
|
@ -282,24 +291,28 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
# https://github.com/nim-lang/RFCs/issues/435
|
# https://github.com/nim-lang/RFCs/issues/435
|
||||||
prc.addPragma(newIdentNode("gcsafe"))
|
prc.addPragma(newIdentNode("gcsafe"))
|
||||||
|
|
||||||
if isAsync == false: # `asyncraises` without `async`
|
if raw: # raw async = body is left as-is
|
||||||
# type InternalRaisesFutureRaises {.used.} = `raisesTuple`
|
if raises != nil and prc.kind notin {nnkProcTy, nnkLambda} and not isEmpty(prc.body):
|
||||||
# `body`
|
# Inject `raises` type marker that causes `newFuture` to return a raise-
|
||||||
prc.body = nnkStmtList.newTree(
|
# tracking future instead of an ordinary future:
|
||||||
nnkTypeSection.newTree(
|
#
|
||||||
nnkTypeDef.newTree(
|
# type InternalRaisesFutureRaises = `raisesTuple`
|
||||||
nnkPragmaExpr.newTree(
|
# `body`
|
||||||
ident"InternalRaisesFutureRaises",
|
prc.body = nnkStmtList.newTree(
|
||||||
nnkPragma.newTree(
|
nnkTypeSection.newTree(
|
||||||
newIdentNode("used")
|
nnkTypeDef.newTree(
|
||||||
)
|
nnkPragmaExpr.newTree(
|
||||||
),
|
ident"InternalRaisesFutureRaises",
|
||||||
newEmptyNode(),
|
nnkPragma.newTree(ident "used")),
|
||||||
raisesTuple
|
newEmptyNode(),
|
||||||
)
|
raises,
|
||||||
),
|
)
|
||||||
prc.body
|
),
|
||||||
)
|
prc.body
|
||||||
|
)
|
||||||
|
|
||||||
|
when chronosDumpAsync:
|
||||||
|
echo repr prc
|
||||||
|
|
||||||
return prc
|
return prc
|
||||||
|
|
||||||
|
@ -311,9 +324,6 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
setResultSym = ident "setResult"
|
setResultSym = ident "setResult"
|
||||||
procBody = prc.body.processBody(setResultSym, baseType)
|
procBody = prc.body.processBody(setResultSym, baseType)
|
||||||
internalFutureSym = ident "chronosInternalRetFuture"
|
internalFutureSym = ident "chronosInternalRetFuture"
|
||||||
internalFutureType =
|
|
||||||
if baseTypeIsVoid: futureVoidType
|
|
||||||
else: returnType
|
|
||||||
castFutureSym = nnkCast.newTree(internalFutureType, internalFutureSym)
|
castFutureSym = nnkCast.newTree(internalFutureType, internalFutureSym)
|
||||||
resultIdent = ident "result"
|
resultIdent = ident "result"
|
||||||
|
|
||||||
|
@ -396,7 +406,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
castFutureSym, baseType,
|
castFutureSym, baseType,
|
||||||
if baseTypeIsVoid: procBody # shortcut for non-generic `void`
|
if baseTypeIsVoid: procBody # shortcut for non-generic `void`
|
||||||
else: newCall(setResultSym, procBody),
|
else: newCall(setResultSym, procBody),
|
||||||
raisesTuple
|
raises
|
||||||
)
|
)
|
||||||
|
|
||||||
closureBody = newStmtList(resultDecl, setResultDecl, completeDecl)
|
closureBody = newStmtList(resultDecl, setResultDecl, completeDecl)
|
||||||
|
@ -431,19 +441,22 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
|
|
||||||
outerProcBody.add(closureIterator)
|
outerProcBody.add(closureIterator)
|
||||||
|
|
||||||
# -> let resultFuture = newInternalRaisesFuture[T]()
|
# -> let resultFuture = newInternalRaisesFuture[T, E]()
|
||||||
# declared at the end to be sure that the closure
|
# declared at the end to be sure that the closure
|
||||||
# doesn't reference it, avoid cyclic ref (#203)
|
# doesn't reference it, avoid cyclic ref (#203)
|
||||||
let
|
let
|
||||||
retFutureSym = ident "resultFuture"
|
retFutureSym = ident "resultFuture"
|
||||||
|
newFutProc = if raises == nil:
|
||||||
|
newTree(nnkBracketExpr, ident "newFuture", baseType)
|
||||||
|
else:
|
||||||
|
newTree(nnkBracketExpr, ident "newInternalRaisesFuture", baseType, raises)
|
||||||
retFutureSym.copyLineInfo(prc)
|
retFutureSym.copyLineInfo(prc)
|
||||||
# Do not change this code to `quote do` version because `instantiationInfo`
|
# Do not change this code to `quote do` version because `instantiationInfo`
|
||||||
# will be broken for `newFuture()` call.
|
# will be broken for `newFuture()` call.
|
||||||
outerProcBody.add(
|
outerProcBody.add(
|
||||||
newLetStmt(
|
newLetStmt(
|
||||||
retFutureSym,
|
retFutureSym,
|
||||||
newCall(newTree(nnkBracketExpr, ident "newInternalRaisesFuture", baseType),
|
newCall(newFutProc, newLit(prcName))
|
||||||
newLit(prcName))
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# -> resultFuture.internalClosure = iterator
|
# -> resultFuture.internalClosure = iterator
|
||||||
|
@ -465,6 +478,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
|
|
||||||
when chronosDumpAsync:
|
when chronosDumpAsync:
|
||||||
echo repr prc
|
echo repr prc
|
||||||
|
|
||||||
prc
|
prc
|
||||||
|
|
||||||
template await*[T](f: Future[T]): untyped =
|
template await*[T](f: Future[T]): untyped =
|
||||||
|
@ -490,32 +504,23 @@ template awaitne*[T](f: Future[T]): Future[T] =
|
||||||
else:
|
else:
|
||||||
unsupported "awaitne is only available within {.async.}"
|
unsupported "awaitne is only available within {.async.}"
|
||||||
|
|
||||||
macro async*(prc: untyped): untyped =
|
macro async*(params, prc: untyped): untyped =
|
||||||
## Macro which processes async procedures into the appropriate
|
## Macro which processes async procedures into the appropriate
|
||||||
## iterators and yield statements.
|
## iterators and yield statements.
|
||||||
if prc.kind == nnkStmtList:
|
if prc.kind == nnkStmtList:
|
||||||
result = newStmtList()
|
result = newStmtList()
|
||||||
for oneProc in prc:
|
for oneProc in prc:
|
||||||
oneProc.addPragma(ident"async")
|
result.add asyncSingleProc(oneProc, params)
|
||||||
result.add asyncSingleProc(oneProc)
|
|
||||||
else:
|
else:
|
||||||
prc.addPragma(ident"async")
|
result = asyncSingleProc(prc, params)
|
||||||
result = asyncSingleProc(prc)
|
|
||||||
|
macro async*(prc: untyped): untyped =
|
||||||
|
## Macro which processes async procedures into the appropriate
|
||||||
|
## iterators and yield statements.
|
||||||
|
|
||||||
macro asyncraises*(possibleExceptions, prc: untyped): untyped =
|
|
||||||
# Add back the pragma and let asyncSingleProc handle it
|
|
||||||
# Exerimental / subject to change and/or removal
|
|
||||||
if prc.kind == nnkStmtList:
|
if prc.kind == nnkStmtList:
|
||||||
result = newStmtList()
|
result = newStmtList()
|
||||||
for oneProc in prc:
|
for oneProc in prc:
|
||||||
oneProc.addPragma(nnkExprColonExpr.newTree(
|
result.add asyncSingleProc(oneProc, nnkTupleConstr.newTree())
|
||||||
ident"asyncraises",
|
|
||||||
possibleExceptions
|
|
||||||
))
|
|
||||||
result.add asyncSingleProc(oneProc)
|
|
||||||
else:
|
else:
|
||||||
prc.addPragma(nnkExprColonExpr.newTree(
|
result = asyncSingleProc(prc, nnkTupleConstr.newTree())
|
||||||
ident"asyncraises",
|
|
||||||
possibleExceptions
|
|
||||||
))
|
|
||||||
result = asyncSingleProc(prc)
|
|
||||||
|
|
|
@ -7,13 +7,23 @@ type
|
||||||
## Future with a tuple of possible exception types
|
## Future with a tuple of possible exception types
|
||||||
## eg InternalRaisesFuture[void, (ValueError, OSError)]
|
## eg InternalRaisesFuture[void, (ValueError, OSError)]
|
||||||
##
|
##
|
||||||
## This type gets injected by `asyncraises` and similar utilities and
|
## This type gets injected by `async: (raises: ...)` and similar utilities
|
||||||
## should not be used manually as the internal exception representation is
|
## and should not be used manually as the internal exception representation
|
||||||
## subject to change in future chronos versions.
|
## is subject to change in future chronos versions.
|
||||||
|
|
||||||
|
proc makeNoRaises*(): NimNode {.compileTime.} =
|
||||||
|
# An empty tuple would have been easier but...
|
||||||
|
# https://github.com/nim-lang/Nim/issues/22863
|
||||||
|
# https://github.com/nim-lang/Nim/issues/22865
|
||||||
|
|
||||||
|
ident"void"
|
||||||
|
|
||||||
|
proc isNoRaises*(n: NimNode): bool {.compileTime.} =
|
||||||
|
n.eqIdent("void")
|
||||||
|
|
||||||
iterator members(tup: NimNode): NimNode =
|
iterator members(tup: NimNode): NimNode =
|
||||||
# Given a typedesc[tuple] = (A, B, C), yields the tuple members (A, B C)
|
# Given a typedesc[tuple] = (A, B, C), yields the tuple members (A, B C)
|
||||||
if not tup.eqIdent("void"):
|
if not isNoRaises(tup):
|
||||||
for n in getType(getTypeInst(tup)[1])[1..^1]:
|
for n in getType(getTypeInst(tup)[1])[1..^1]:
|
||||||
yield n
|
yield n
|
||||||
|
|
||||||
|
@ -40,7 +50,7 @@ macro prepend*(tup: typedesc[tuple], typs: varargs[typed]): typedesc =
|
||||||
result.add err
|
result.add err
|
||||||
|
|
||||||
if result.len == 0:
|
if result.len == 0:
|
||||||
result = ident"void"
|
result = makeNoRaises()
|
||||||
|
|
||||||
macro remove*(tup: typedesc[tuple], typs: varargs[typed]): typedesc =
|
macro remove*(tup: typedesc[tuple], typs: varargs[typed]): typedesc =
|
||||||
result = nnkTupleConstr.newTree()
|
result = nnkTupleConstr.newTree()
|
||||||
|
@ -49,7 +59,7 @@ macro remove*(tup: typedesc[tuple], typs: varargs[typed]): typedesc =
|
||||||
result.add err
|
result.add err
|
||||||
|
|
||||||
if result.len == 0:
|
if result.len == 0:
|
||||||
result = ident"void"
|
result = makeNoRaises()
|
||||||
|
|
||||||
macro union*(tup0: typedesc[tuple], tup1: typedesc[tuple]): typedesc =
|
macro union*(tup0: typedesc[tuple], tup1: typedesc[tuple]): typedesc =
|
||||||
## Join the types of the two tuples deduplicating the entries
|
## Join the types of the two tuples deduplicating the entries
|
||||||
|
@ -65,11 +75,13 @@ macro union*(tup0: typedesc[tuple], tup1: typedesc[tuple]): typedesc =
|
||||||
|
|
||||||
for err2 in getType(getTypeInst(tup1)[1])[1..^1]:
|
for err2 in getType(getTypeInst(tup1)[1])[1..^1]:
|
||||||
result.add err2
|
result.add err2
|
||||||
|
if result.len == 0:
|
||||||
|
result = makeNoRaises()
|
||||||
|
|
||||||
proc getRaises*(future: NimNode): NimNode {.compileTime.} =
|
proc getRaises*(future: NimNode): NimNode {.compileTime.} =
|
||||||
# Given InternalRaisesFuture[T, (A, B, C)], returns (A, B, C)
|
# Given InternalRaisesFuture[T, (A, B, C)], returns (A, B, C)
|
||||||
let types = getType(getTypeInst(future)[2])
|
let types = getType(getTypeInst(future)[2])
|
||||||
if types.eqIdent("void"):
|
if isNoRaises(types):
|
||||||
nnkBracketExpr.newTree(newEmptyNode())
|
nnkBracketExpr.newTree(newEmptyNode())
|
||||||
else:
|
else:
|
||||||
expectKind(types, nnkBracketExpr)
|
expectKind(types, nnkBracketExpr)
|
||||||
|
@ -106,7 +118,7 @@ macro checkRaises*[T: CatchableError](
|
||||||
infix(error, "of", nnkBracketExpr.newTree(ident"typedesc", errorType)))
|
infix(error, "of", nnkBracketExpr.newTree(ident"typedesc", errorType)))
|
||||||
|
|
||||||
let
|
let
|
||||||
errorMsg = "`fail`: `" & repr(toMatch) & "` incompatible with `asyncraises: " & repr(raises[1..^1]) & "`"
|
errorMsg = "`fail`: `" & repr(toMatch) & "` incompatible with `raises: " & repr(raises[1..^1]) & "`"
|
||||||
warningMsg = "Can't verify `fail` exception type at compile time - expected one of " & repr(raises[1..^1]) & ", got `" & repr(toMatch) & "`"
|
warningMsg = "Can't verify `fail` exception type at compile time - expected one of " & repr(raises[1..^1]) & ", got `" & repr(toMatch) & "`"
|
||||||
# A warning from this line means exception type will be verified at runtime
|
# A warning from this line means exception type will be verified at runtime
|
||||||
warning = if warn:
|
warning = if warn:
|
||||||
|
|
|
@ -387,16 +387,16 @@ suite "Exceptions tracking":
|
||||||
check (not compiles(body))
|
check (not compiles(body))
|
||||||
test "Can raise valid exception":
|
test "Can raise valid exception":
|
||||||
proc test1 {.async.} = raise newException(ValueError, "hey")
|
proc test1 {.async.} = raise newException(ValueError, "hey")
|
||||||
proc test2 {.async, asyncraises: [ValueError].} = raise newException(ValueError, "hey")
|
proc test2 {.async: (raises: [ValueError]).} = raise newException(ValueError, "hey")
|
||||||
proc test3 {.async, asyncraises: [IOError, ValueError].} =
|
proc test3 {.async: (raises: [IOError, ValueError]).} =
|
||||||
if 1 == 2:
|
if 1 == 2:
|
||||||
raise newException(ValueError, "hey")
|
raise newException(ValueError, "hey")
|
||||||
else:
|
else:
|
||||||
raise newException(IOError, "hey")
|
raise newException(IOError, "hey")
|
||||||
|
|
||||||
proc test4 {.async, asyncraises: [], used.} = raise newException(Defect, "hey")
|
proc test4 {.async: (raises: []), used.} = raise newException(Defect, "hey")
|
||||||
proc test5 {.async, asyncraises: [].} = discard
|
proc test5 {.async: (raises: []).} = discard
|
||||||
proc test6 {.async, asyncraises: [].} = await test5()
|
proc test6 {.async: (raises: []).} = await test5()
|
||||||
|
|
||||||
expect(ValueError): waitFor test1()
|
expect(ValueError): waitFor test1()
|
||||||
expect(ValueError): waitFor test2()
|
expect(ValueError): waitFor test2()
|
||||||
|
@ -405,15 +405,15 @@ suite "Exceptions tracking":
|
||||||
|
|
||||||
test "Cannot raise invalid exception":
|
test "Cannot raise invalid exception":
|
||||||
checkNotCompiles:
|
checkNotCompiles:
|
||||||
proc test3 {.async, asyncraises: [IOError].} = raise newException(ValueError, "hey")
|
proc test3 {.async: (raises: [IOError]).} = raise newException(ValueError, "hey")
|
||||||
|
|
||||||
test "Explicit return in non-raising proc":
|
test "Explicit return in non-raising proc":
|
||||||
proc test(): Future[int] {.async, asyncraises: [].} = return 12
|
proc test(): Future[int] {.async: (raises: []).} = return 12
|
||||||
check:
|
check:
|
||||||
waitFor(test()) == 12
|
waitFor(test()) == 12
|
||||||
|
|
||||||
test "Non-raising compatibility":
|
test "Non-raising compatibility":
|
||||||
proc test1 {.async, asyncraises: [ValueError].} = raise newException(ValueError, "hey")
|
proc test1 {.async: (raises: [ValueError]).} = raise newException(ValueError, "hey")
|
||||||
let testVar: Future[void] = test1()
|
let testVar: Future[void] = test1()
|
||||||
|
|
||||||
proc test2 {.async.} = raise newException(ValueError, "hey")
|
proc test2 {.async.} = raise newException(ValueError, "hey")
|
||||||
|
@ -423,69 +423,64 @@ suite "Exceptions tracking":
|
||||||
#let testVar3: proc: Future[void] = test1
|
#let testVar3: proc: Future[void] = test1
|
||||||
|
|
||||||
test "Cannot store invalid future types":
|
test "Cannot store invalid future types":
|
||||||
proc test1 {.async, asyncraises: [ValueError].} = raise newException(ValueError, "hey")
|
proc test1 {.async: (raises: [ValueError]).} = raise newException(ValueError, "hey")
|
||||||
proc test2 {.async, asyncraises: [IOError].} = raise newException(IOError, "hey")
|
proc test2 {.async: (raises: [IOError]).} = raise newException(IOError, "hey")
|
||||||
|
|
||||||
var a = test1()
|
var a = test1()
|
||||||
checkNotCompiles:
|
checkNotCompiles:
|
||||||
a = test2()
|
a = test2()
|
||||||
|
|
||||||
test "Await raises the correct types":
|
test "Await raises the correct types":
|
||||||
proc test1 {.async, asyncraises: [ValueError].} = raise newException(ValueError, "hey")
|
proc test1 {.async: (raises: [ValueError]).} = raise newException(ValueError, "hey")
|
||||||
proc test2 {.async, asyncraises: [ValueError, CancelledError].} = await test1()
|
proc test2 {.async: (raises: [ValueError, CancelledError]).} = await test1()
|
||||||
checkNotCompiles:
|
checkNotCompiles:
|
||||||
proc test3 {.async, asyncraises: [CancelledError].} = await test1()
|
proc test3 {.async: (raises: [CancelledError]).} = await test1()
|
||||||
|
|
||||||
test "Can create callbacks":
|
test "Can create callbacks":
|
||||||
proc test1 {.async, asyncraises: [ValueError].} = raise newException(ValueError, "hey")
|
proc test1 {.async: (raises: [ValueError]).} = raise newException(ValueError, "hey")
|
||||||
let callback: proc() {.async, asyncraises: [ValueError].} = test1
|
let callback: proc() {.async: (raises: [ValueError]).} = test1
|
||||||
|
|
||||||
test "Can return values":
|
test "Can return values":
|
||||||
proc test1: Future[int] {.async, asyncraises: [ValueError].} =
|
proc test1: Future[int] {.async: (raises: [ValueError]).} =
|
||||||
if 1 == 0: raise newException(ValueError, "hey")
|
if 1 == 0: raise newException(ValueError, "hey")
|
||||||
return 12
|
return 12
|
||||||
proc test2: Future[int] {.async, asyncraises: [ValueError, IOError, CancelledError].} =
|
proc test2: Future[int] {.async: (raises: [ValueError, IOError, CancelledError]).} =
|
||||||
return await test1()
|
return await test1()
|
||||||
|
|
||||||
checkNotCompiles:
|
checkNotCompiles:
|
||||||
proc test3: Future[int] {.async, asyncraises: [CancelledError].} = await test1()
|
proc test3: Future[int] {.async: (raises: [CancelledError]).} = await test1()
|
||||||
|
|
||||||
check waitFor(test2()) == 12
|
check waitFor(test2()) == 12
|
||||||
|
|
||||||
test "Manual tracking":
|
test "Manual tracking":
|
||||||
proc test1: Future[int] {.asyncraises: [ValueError].} =
|
proc test1: Future[int] {.async: (raw: true, raises: [ValueError]).} =
|
||||||
result = newFuture[int]()
|
result = newFuture[int]()
|
||||||
result.complete(12)
|
result.complete(12)
|
||||||
check waitFor(test1()) == 12
|
check waitFor(test1()) == 12
|
||||||
|
|
||||||
proc test2: Future[int] {.asyncraises: [IOError, OSError].} =
|
proc test2: Future[int] {.async: (raw: true, raises: [IOError, OSError]).} =
|
||||||
result = newFuture[int]()
|
result = newFuture[int]()
|
||||||
result.fail(newException(IOError, "fail"))
|
result.fail(newException(IOError, "fail"))
|
||||||
result.fail(newException(OSError, "fail"))
|
result.fail(newException(OSError, "fail"))
|
||||||
checkNotCompiles:
|
checkNotCompiles:
|
||||||
result.fail(newException(ValueError, "fail"))
|
result.fail(newException(ValueError, "fail"))
|
||||||
|
|
||||||
proc test3: Future[void] {.asyncraises: [].} =
|
proc test3: Future[void] {.async: (raw: true, raises: []).} =
|
||||||
checkNotCompiles:
|
checkNotCompiles:
|
||||||
result.fail(newException(ValueError, "fail"))
|
result.fail(newException(ValueError, "fail"))
|
||||||
|
|
||||||
# Inheritance
|
# Inheritance
|
||||||
proc test4: Future[void] {.asyncraises: [CatchableError].} =
|
proc test4: Future[void] {.async: (raw: true, raises: [CatchableError]).} =
|
||||||
result.fail(newException(IOError, "fail"))
|
result.fail(newException(IOError, "fail"))
|
||||||
|
|
||||||
test "Reversed async, asyncraises":
|
|
||||||
proc test44 {.asyncraises: [ValueError], async.} = raise newException(ValueError, "hey")
|
|
||||||
checkNotCompiles:
|
|
||||||
proc test33 {.asyncraises: [IOError], async.} = raise newException(ValueError, "hey")
|
|
||||||
|
|
||||||
test "or errors":
|
test "or errors":
|
||||||
proc testit {.asyncraises: [ValueError], async.} =
|
proc testit {.async: (raises: [ValueError]).} =
|
||||||
raise (ref ValueError)()
|
raise (ref ValueError)()
|
||||||
|
|
||||||
proc testit2 {.asyncraises: [IOError], async.} =
|
proc testit2 {.async: (raises: [IOError]).} =
|
||||||
raise (ref IOError)()
|
raise (ref IOError)()
|
||||||
|
|
||||||
proc test {.async, asyncraises: [ValueError, IOError].} =
|
proc test {.async: (raises: [ValueError, IOError]).} =
|
||||||
await testit() or testit2()
|
await testit() or testit2()
|
||||||
|
|
||||||
proc noraises() {.raises: [].} =
|
proc noraises() {.raises: [].} =
|
||||||
|
@ -499,9 +494,10 @@ suite "Exceptions tracking":
|
||||||
noraises()
|
noraises()
|
||||||
|
|
||||||
test "Wait errors":
|
test "Wait errors":
|
||||||
proc testit {.asyncraises: [ValueError], async.} = raise newException(ValueError, "hey")
|
proc testit {.async: (raises: [ValueError]).} =
|
||||||
|
raise newException(ValueError, "hey")
|
||||||
|
|
||||||
proc test {.async, asyncraises: [ValueError, AsyncTimeoutError, CancelledError].} =
|
proc test {.async: (raises: [ValueError, AsyncTimeoutError, CancelledError]).} =
|
||||||
await wait(testit(), 1000.milliseconds)
|
await wait(testit(), 1000.milliseconds)
|
||||||
|
|
||||||
proc noraises() {.raises: [].} =
|
proc noraises() {.raises: [].} =
|
||||||
|
@ -513,11 +509,11 @@ suite "Exceptions tracking":
|
||||||
noraises()
|
noraises()
|
||||||
|
|
||||||
test "Nocancel errors":
|
test "Nocancel errors":
|
||||||
proc testit {.asyncraises: [ValueError, CancelledError], async.} =
|
proc testit {.async: (raises: [ValueError, CancelledError]).} =
|
||||||
await sleepAsync(5.milliseconds)
|
await sleepAsync(5.milliseconds)
|
||||||
raise (ref ValueError)()
|
raise (ref ValueError)()
|
||||||
|
|
||||||
proc test {.async, asyncraises: [ValueError].} =
|
proc test {.async: (raises: [ValueError]).} =
|
||||||
await noCancel testit()
|
await noCancel testit()
|
||||||
|
|
||||||
proc noraises() {.raises: [].} =
|
proc noraises() {.raises: [].} =
|
||||||
|
|
Loading…
Reference in New Issue