/ 10 Notes / Async checked (raising) exceptions.md
Async checked (raising) exceptions.md
  1  For a seasoned Nim developer a lot of things I am writing here may be obvious, but for those in a continuous learning path, it may bring some consolation.
  2  
  3  The [The Status Nim style guide](https://status-im.github.io/nim-style-guide) recommends [explicit error handling mechanisms](https://status-im.github.io/nim-style-guide/errors.result.html) and handling errors locally at each abstraction level, avoiding spurious abstraction leakage. This is in contrast to leaking exception types between layers and translating exceptions that causes high visual overhead, specially when hierarchy is used, often becoming not practical, loosing all advantages of using exceptions in the first place (read more [here](https://status-im.github.io/nim-style-guide/errors.exceptions.html)).
  4  
  5  Handling error and working with exceptions is easier to grasp when not using asynchronous code. But when you start, there are some subtle traps you may be falling into.
  6  
  7  This short note focuses on asynchronous code. It is not complete, but pragmatic, it has gaps, but provides directions if one wants to research further.
  8  
  9  In our code we often use the following patterns:
 10  1. using [nim-results](https://github.com/arnetheduck/nim-results) and [std/options](https://nim-lang.org/docs/options.html) to communicate the results and failures to the caller,
 11  2. *async* operations are annotated with `{.async: (raises: [CancelledError]).}`.
 12  
 13  Some interesting things are happening when you annotate a Nim `proc` with "async raises".
 14  
 15  Let's looks at some examples.
 16  
 17  Imagine you have the following type definitions:
 18  
 19  ```nim
 20  type
 21    MyError = object of CatchableError
 22    Handle1 = Future[void].Raising([CancelledError])
 23    Handle2 = Future[void].Raising([CancelledError, MyError])
 24  
 25    SomeType = object
 26      name: string
 27      handle1: Handle1
 28      handle2: Handle2
 29  ```
 30  
 31  `Handle1` and `Handle2` are *raising exceptions*. By using `Rasing` macro, passing the list of allowed exceptions coming out from the future as an argument, `Handle1` and `Handle2` are no longer well-known `Future[void]`, but rather a descendant of it:
 32  
 33  ```nim
 34  type
 35    InternalRaisesFuture*[T, E] = ref object of Future[T]
 36      ## Future with a tuple of possible exception types
 37      ## eg InternalRaisesFuture[void, (ValueError, OSError)]
 38      ##
 39      ## This type gets injected by `async: (raises: ...)` and similar utilities
 40      ## and should not be used manually as the internal exception representation
 41      ## is subject to change in future chronos versions.
 42      # TODO https://github.com/nim-lang/Nim/issues/23418
 43      # TODO https://github.com/nim-lang/Nim/issues/23419
 44      when E is void:
 45        dummy: E
 46      else:
 47        dummy: array[0, E]
 48  ```
 49  
 50  The comment is saying something important: if you annotate a `proc` with `async: (raises: ...)`, you are changing the type being returned by the `proc`. To see what does it mean, lets start with something easy. Let's write a constructor for `SomeType`:
 51  
 52  ```nim
 53  proc newSomeType(name: string): SomeType =
 54    let t = SomeType(
 55      name: name,
 56      # both fail
 57      handle1: newFuture[void](),
 58      handle2: newFuture[void](),
 59    )
 60    t
 61  ```
 62  
 63  Well, this will not compile. `handle1` expects `InternalRaisesFuture[system.void, (CancelledError,)]`, but instead it gets `Future[system.void]`. Yes, we are trying to cast a more generic to a less generic type. This is because `newSomeType` is not annotated with `async: (raises: ...)` and therefore every time you use `newFuture` inside it, `newFuture` returns regular `Future[void]`.
 64  
 65  So, the first time I encountered this problem I went into a rabbit hole of understanding how to create *raising* futures by solely relaying on `async: (raises: ...)` pragma. But there is actually a public (I guess) interface allowing us to create a raising future without relaying on `async: (raises: ...)` annotation:
 66  
 67  ```
 68  ```nim
 69  proc newSomeType(name: string): SomeType =
 70    let t = SomeType(
 71      name: name,
 72      # both fail
 73      handle1: Future[void].Raising([CancelledError]).init(),
 74      handle2: Future[void].Raising([CancelledError, MyError]).init(),
 75    )
 76    t
 77  ```
 78  
 79  A bit verbose, but perfectly fine otherwise, and it works as expected:
 80  
 81  ```nim
 82  let someTypeInstance = newSomeType("test")
 83  
 84  echo typeof(someTypeInstance.handle1) # outputs "Handle1"
 85  echo typeof(someTypeInstance.handle2) # outputs "Handle2"
 86  ```
 87  
 88  `init` has the following definition:
 89  
 90  ```nim
 91  template init*[T, E](
 92      F: type InternalRaisesFuture[T, E], fromProc: static[string] = ""): F =
 93    ## Creates a new pending future.
 94    ##
 95    ## Specifying ``fromProc``, which is a string specifying the name of the proc
 96    ## that this future belongs to, is a good habit as it helps with debugging.
 97    when not hasException(type(E), "CancelledError"):
 98      static:
 99        raiseAssert "Manually created futures must either own cancellation schedule or raise CancelledError"
100  
101  
102    let res = F()
103    internalInitFutureBase(res, getSrcLocation(fromProc), FutureState.Pending, {})
104    res
105  ```
106  
107  and is very similar to:
108  
109  ```nim
110  proc newInternalRaisesFutureImpl[T, E](
111      loc: ptr SrcLoc, flags: FutureFlags): InternalRaisesFuture[T, E] =
112    let fut = InternalRaisesFuture[T, E]()
113    internalInitFutureBase(fut, loc, FutureState.Pending, flags)
114    fut
115  ```
116  
117  thus, if we had exposed the internals, our example would be:
118  
119  ```nim
120  proc newSomeType(name: string): SomeType =
121    let t = SomeType(
122      name: name,
123      handle1: newInternalRaisesFuture[void, (CancelledError,)](),
124      handle2: newInternalRaisesFuture[void, (CancelledError, MyError)](),
125    )
126    t
127  ```
128  
129  
130  It is still very educational to study the chronos source code to undertstand how does the `newFuture` know which type to return: `Future[T]` or `InternalRaisesFuture[T, E]` when a proc is annotated with `async` or `async: (raises: ...)`.
131  
132  If you study `chronos/internal/asyncfutures.nim` you will see that `newFuture` is implemented with the following template:
133  
134  ```nim
135  template newFuture*[T](fromProc: static[string] = "",
136                         flags: static[FutureFlags] = {}): auto =
137    ## Creates a new future.
138    ##
139    ## Specifying ``fromProc``, which is a string specifying the name of the proc
140    ## that this future belongs to, is a good habit as it helps with debugging.
141    when declared(InternalRaisesFutureRaises): # injected by `asyncraises`
142      newInternalRaisesFutureImpl[T, InternalRaisesFutureRaises](
143        getSrcLocation(fromProc), flags)
144    else:
145      newFutureImpl[T](getSrcLocation(fromProc), flags)
146  ```
147  
148  We see the the actual implementation depends on the existence of `InternalRaisesFutureRaises`. Let's see how it is being setup...
149  
150  The `async` pragma is a macro defined in `chronos/internal/asyncmacro.nim`:
151  
152  ```nim
153  macro async*(params, prc: untyped): untyped =
154    ## Macro which processes async procedures into the appropriate
155    ## iterators and yield statements.
156    if prc.kind == nnkStmtList:
157      result = newStmtList()
158      for oneProc in prc:
159        result.add asyncSingleProc(oneProc, params)
160    else:
161      result = asyncSingleProc(prc, params)
162  ```
163  
164  The `asyncSingleProc` is where a lot of things happen. This is where the errors *The raises pragma doesn't work on async procedures* or *Expected return type of 'Future' got ...* come from. The place where the return type is determined is interesting:
165  
166  ```nim
167  let baseType =
168    if returnType.kind == nnkEmpty:
169      ident "void"
170    elif not (
171        returnType.kind == nnkBracketExpr and
172        (eqIdent(returnType[0], "Future") or eqIdent(returnType[0], "InternalRaisesFuture"))):
173      error(
174        "Expected return type of 'Future' got '" & repr(returnType) & "'", prc)
175      return
176    else:
177      returnType[1]
178  ```
179  
180  An async proc can have two (explicit) return types: `Future[baseType]` or `InternalRaisesFuture[baseType]`. If no return type is specified for an async proc, the return base type is concluded to be `void`. Now the crucial part: the internal return type (we are still inside of `asyncSingleProc`):
181  
182  ```nim
183  baseTypeIsVoid = baseType.eqIdent("void")
184  (raw, raises, handleException) = decodeParams(params)
185  internalFutureType =
186    if baseTypeIsVoid:
187      newNimNode(nnkBracketExpr, prc).
188        add(newIdentNode("Future")).
189        add(baseType)
190    else:
191      returnType
192  internalReturnType = if raises == nil:
193    internalFutureType
194  else:
195    nnkBracketExpr.newTree(
196      newIdentNode("InternalRaisesFuture"),
197      baseType,
198      raises
199    )
200  ```
201  
202  To focus on the most important part, at the end we see that if `raises` attribute is present and set (`async: (raises: [])` means it does not raise, but the attribute is still present and detected), the `internalReturnType` will be set to:
203  
204  ```nim
205  nnkBracketExpr.newTree(
206    newIdentNode("InternalRaisesFuture"),
207    baseType,
208    raises
209  )
210  ```
211  
212  Thus, for `async: (raises: [CancelledError, ValueError])`, the return type will be `InternalRaisesFuture[baseType, (CancelledError, ValueError,)`.
213  
214  If the `async` has `raw: true` param set, e.g. `async: (raw: true, raises: [CancelledError, ValueError])`, then `prc.body` gets prepended with the type definition we already recognize from `newFuture` above: `InternalRaisesFutureRaises`
215  
216  ```nim
217  if raw: # raw async = body is left as-is
218    if raises != nil and prc.kind notin {nnkProcTy, nnkLambda} and not isEmpty(prc.body):
219      # Inject `raises` type marker that causes `newFuture` to return a raise-
220      # tracking future instead of an ordinary future:
221      #
222      # type InternalRaisesFutureRaises = `raisesTuple`
223      # `body`
224      prc.body = nnkStmtList.newTree(
225        nnkTypeSection.newTree(
226          nnkTypeDef.newTree(
227            nnkPragmaExpr.newTree(
228              ident"InternalRaisesFutureRaises",
229              nnkPragma.newTree(ident "used")),
230            newEmptyNode(),
231            raises,
232          )
233        ),
234        prc.body
235      )
236  ```
237  
238  For our example of  `async: (raw: true, raises: [CancelledError, ValueError])`, this will be:
239  
240  ```nim
241  type InternalRaisesFutureRaises {.used.} = (CancelledError, ValueError,)
242  ```
243  
244  This allows the `newFuture` template to recognize it has to use `InternalRaisesFuture` as the return type.
245  
246  ### Experimenting with *Raising Futures*
247  
248  With the `Future[...].Raising(...).init()` construct we can quite elegantly create new raising futures in regular proc not annotated with `async: (raises: ...)`.  But to get more intuition, let's play a bit with creating our own version of `Future[...].Raising(...).init()` that will be built on top of `async: (raises: ...)` pragma.
249  
250  > [!info]
251  > This is just an exercise. It reveals some interesting details about how `async` is implemented. I also used it to learn some basics about using macros and how they can help where generics have limitations.
252  
253  Let's start with creating a proc that returns type `Handle1`?
254  
255  Recall that `Handle1` is defined as follows:
256  
257  ```nim
258  type
259    Handle1 = Future[void].Raising([CancelledError])
260  ```
261  
262  ```nim
263  proc newHandle1(): Handle1 {.async: (raw: true, [CancelledError]).} =
264    newFuture[void]()
265  
266  proc newSomeType(name: string): SomeType =
267    let t = SomeType(
268      name: name,
269      handle1: newHandle1(),
270      handle2: Future[void].Raising([CancelledError, MyError]).init(),
271    )
272    t
273  ```
274  
275  That would be nice an concise, yet, you remember now the "Expected return type of 'Future' got ..." error from `asyncSingleProc`, right? This is what we will get:
276  
277  ```bash
278  Error: Expected return type of 'Future' got 'Handle1'
279  ```
280  
281  Knowing the implementation of the `asyncSingleProc` macro, we know that `InternalRaisesFuture[void, (CancelledError,)]` would work just fine as the return type:
282  
283  ```nim
284  proc newHandle1(): InternalRaisesFuture[void, (CancelledError,)] {.async: (raw: true, raises: [CancelledError]).} =
285    newFuture[void]()
286  ```
287  
288  but not:
289  
290  ```nim
291  proc newHandle1(): Future[void].Raises(CancelledError) {.async: (raw: true, raises: [CancelledError]).} =
292    newFuture[void]()
293  ```
294  
295  Thus we have to stick to `Future` as the return type if we want to stick to the public interface:
296  
297  ```nim
298  proc newHandle1(): Future[void]
299      {.async: (raw: true, raises: [CancelledError]).} =
300    newFuture[void]()
301  ```
302  
303  It actually does not matter that we specify `Future[void]` as the return type (yet, it has to be `Future`): the actual return type of `newFuture` and of the `newHandle1` proc  will be `InternalRaisesFuture[void, (CancelledError,)]` thanks to the `assert: (raw: true, raises: [CancelledError])`.
304  
305  It would be nice if we can create a more generic version of `newHandle`, so that we do not have to create a new one for each single raising future type. Ideally, we would like this generic to also allow us handling the raised exceptions accordingly.
306  
307  Using just plain generic does not seem to allow us passing the list of exception types so that it lands nicely in the `raises: [...]` attribute:
308  
309  
310  ```nim
311  proc newRaisingFuture[T, E](): Future[T] {.async: (raw: true, raises: [E]).} =
312    newFuture[T]()
313  ```
314  
315  With this we can pass a single exception type as E. To pass a list of exceptions we can use a template:
316  
317  ```nim
318  template newRaisingFuture[T](raising: typed): untyped =
319    block:
320      proc wrapper(): Future[T] {.async: (raw: true, raises: raising).} =
321        newFuture[T]()
322      wrapper()
323  ```
324  
325  With the `newRaisingFuture` template we can simplify our example to get:
326  
327  ```nim
328  proc newSomeType(name: string): SomeType =
329    let t = SomeType(
330      name: name,
331      handle1: newRaisingFuture[void]([CancelledError]),
332      handle2: newRaisingFuture[void]([CancelledError, MyError]),
333    )
334    t
335  ```
336  
337  Perhaps, a more elegant solution would be to use an IIFE (Immediately Invoked Function Expression), e.g.:
338  
339  ```nim
340  (proc (): Future[void]
341      {.async: (raw: true, raises: [CancelledError, MyError]).} =
342    newFuture[void]())()
343  ```
344  
345  so that we can create a raising future instance like this:
346  
347  ```nim
348  let ff = (
349    proc (): Future[void]
350        {.async: (raw: true, raises: [CancelledError, MyError]).} =
351      newFuture[void]())()
352  )()
353  ```
354  
355  Unfortunately, this will fail with error similar to this one:
356  
357  ```bash
358  Error: type mismatch: got 'Future[system.void]' for '
359  newFutureImpl(srcLocImpl("", (filename: "raisingfutures.nim", line: 264,
360      column: 19).filename, (filename: "raisingfutures.nim", line: 264,
361      column: 19).line), {})' but expected 
362      'InternalRaisesFuture[system.void, (CancelledError, MyError)]'
363  ```
364  
365  To see what happened, we can use the `-d:nimDumpAsync` option when compiling, e.g.:
366  
367  ```bash
368  nim c -r -o:build/ --NimblePath:.nimble/pkgs2 -d:nimDumpAsync raisingfutures.nim 
369  ```
370  
371  This option will print us the expanded `async` macro, where we can find that our call expanded to:
372  
373  ```nim
374  proc (): InternalRaisesFuture[void, (CancelledError, MyError)]
375      {.raises: [], gcsafe.} =
376    newFuture[void]()
377  ```
378  
379  This obviously misses the definition of the `InternalRaisesFutureRaises` type before calling `newFuture`, which would change the behavior of the `newFuture` call so that instead of returning a regular `Future[void]` it would return  `InternalRaisesFuture[system.void, (CancelledError, MyError)]`. The same function, evaluated as regular proc (and not as lambda call) would take the following form: 
380  
381  ```nim
382  proc (): InternalRaisesFuture[seq[int], (CancelledError, MyError)]
383      {.raises: [], gcsafe.} =
384    type InternalRaisesFutureRaises {.used.} = (CancelledError, ValueError,)
385    newFuture[seq[int]]()
386  ```
387  
388  Looking again into the `chronos/internal/asyncmacro.nim`:
389  
390  ```nim
391  if raw: # raw async = body is left as-is
392    if raises != nil and prc.kind notin {nnkProcTy, nnkLambda} and not isEmpty(prc.body):
393      # Inject `raises` type marker that causes `newFuture` to return a raise-
394      # tracking future instead of an ordinary future:
395      #
396      # type InternalRaisesFutureRaises = `raisesTuple`
397      # `body`
398      prc.body = nnkStmtList.newTree(
399        nnkTypeSection.newTree(
400          nnkTypeDef.newTree(
401            nnkPragmaExpr.newTree(
402              ident"InternalRaisesFutureRaises",
403              nnkPragma.newTree(ident "used")),
404            newEmptyNode(),
405            raises,
406          )
407        ),
408        prc.body
409      )
410  
411  ```
412  
413  we see the condition:
414  
415  ```nim
416  if raises != nil and prc.kind notin {nnkProcTy, nnkLambda} and not isEmpty(prc.body):
417  ```
418  
419  Unfortunately, in our case `prc.kind` is `nnkLambda`, and so the above mentioned type infusion will not happen...
420  
421  > I do not know why it is chosen to be like this...
422  
423  Thus, if we would like to use IIFE, we do have to use an internal function from `chronos/internal/asyncfutures.nim`:
424  
425  ```nim
426  (proc (): Future[void]
427      {.async: (raw: true, raises: [CancelledError, MyError]).} =
428    newInternalRaisesFuture[void, (CancelledError, MyError)]())()
429  ```
430  
431  This call will work, and we can then "hide" the internal primitive in a macro. Below I show the macro, we can use to conveniently create *raising futures* using the IIFE: 
432  
433  ```nim
434  macro newRaisingFuture(T: typedesc, E: typed): untyped =
435    let 
436      baseType = T.strVal
437      e =
438        case E.getTypeInst().typeKind()
439        of ntyTypeDesc: @[E]
440        of ntyArray:
441          for x in E:
442            if x.getTypeInst().typeKind != ntyTypeDesc:
443              error("Expected typedesc, got " & repr(x), x)
444          E.mapIt(it)
445        else:
446          error("Expected typedesc, got " & repr(E), E)
447  
448    let raises = if e.len == 0:
449      nnkBracket.newTree()
450    else:
451      nnkBracket.newTree(e)
452    let raisesTuple = if e.len == 0:
453      makeNoRaises()
454    else:
455      nnkTupleConstr.newTree(e)
456    
457    result = nnkStmtList.newTree(
458      nnkCall.newTree(
459        nnkPar.newTree(
460          nnkLambda.newTree(
461            newEmptyNode(),
462            newEmptyNode(),
463            newEmptyNode(),
464            nnkFormalParams.newTree(
465              nnkBracketExpr.newTree(
466                newIdentNode("Future"),
467                newIdentNode(baseType)
468              )
469            ),
470            nnkPragma.newTree(
471              nnkExprColonExpr.newTree(
472                newIdentNode("async"),
473                nnkTupleConstr.newTree(
474                  nnkExprColonExpr.newTree(
475                    newIdentNode("raw"),
476                    newIdentNode("true")
477                  ),
478                  nnkExprColonExpr.newTree(
479                    newIdentNode("raises"),
480                    raises
481                  )
482                )
483              )
484            ),
485            newEmptyNode(),
486            nnkStmtList.newTree(
487              nnkCall.newTree(
488                nnkBracketExpr.newTree(
489                  newIdentNode("newInternalRaisesFuture"),
490                  newIdentNode(baseType),
491                  raisesTuple
492                )
493              )
494            )
495          )
496        )
497      )
498    )
499  ```
500  
501  Now, creating a raising future is quite elegant:
502  
503  ```nim
504  proc newSomeType(name: string): SomeType =
505    let t = SomeType(
506      name: name,
507      handle1: newRaisingFuture(void, CancelledError),
508      handle2: newRaisingFuture(void, [CancelledError, MyError]),
509    )
510    t
511  ```
512  
513  ### Using raising futures types
514  
515  While `Future[...].Raising(...).init()` provides us with quite elegant (although verbose) interface to create raising futures, it seems to display some subtle limitations.
516  
517  To demonstrate them, let start with the following, quite innocent looking proc:
518  
519  ```nim
520  proc waitHandle[T](h: Future[T]): Future[T]
521      {.async: (raises: [CancelledError]).} =
522    await h
523  ```
524  
525  Now, let's try to call it passing a raising future as an argument:
526  
527  ```nim
528  let handle = newRaisingFuture(int, [CancelledError])
529  handle.complete(42)
530  echo waitFor waitHandle(handle)
531  ```
532  
533  > [!info]
534  > In the examples I am using our macro - just as an example, and it is also shorter to type than `Future[...].Raising(...).init()`
535  
536  The compilation will fail with the following error:
537  
538  ```bash
539  Error: cast[type(h)](chronosInternalRetFuture.internalChild).internalError can raise an unlisted exception: ref CatchableError
540  ```
541  
542  First, realize that we are passing `InternalRaisesFuture[void, (CancelledError,)]` as `Future[void]`. Because we have that:
543  
544  ```nim
545  type InternalRaisesFuture*[T, E] = ref object of Future[T]
546  ```
547  
548  it will not cause any troubles. For `Future[T]`, the following version of `await` will be called:
549  
550  ```nim
551  template await*[T](f: Future[T]): T =
552    ## Ensure that the given `Future` is finished, then return its value.
553    ##
554    ## If the `Future` failed or was cancelled, the corresponding exception will
555    ## be raised instead.
556    ##
557    ## If the `Future` is pending, execution of the current `async` procedure
558    ## will be suspended until the `Future` is finished.
559    when declared(chronosInternalRetFuture):
560      chronosInternalRetFuture.internalChild = f
561      # `futureContinue` calls the iterator generated by the `async`
562      # transformation - `yield` gives control back to `futureContinue` which is
563      # responsible for resuming execution once the yielded future is finished
564      yield chronosInternalRetFuture.internalChild
565      # `child` released by `futureContinue`
566      cast[type(f)](chronosInternalRetFuture.internalChild).internalRaiseIfError(f)
567      when T isnot void:
568        cast[type(f)](chronosInternalRetFuture.internalChild).value()
569    else:
570      unsupported "await is only available within {.async.}"
571  ```
572  
573  The reason for exception is:
574  
575  ```nim
576  cast[type(f)](chronosInternalRetFuture.internalChild).internalRaiseIfError(f)
577  ```
578  
579  which is:
580  
581  ```nim
582  macro internalRaiseIfError*(fut: FutureBase, info: typed) =
583    # Check the error field of the given future and raise if it's set to non-nil.
584    # This is a macro so we can capture the line info from the original call and
585    # report the correct line number on exception effect violation
586    let
587      info = info.lineInfoObj()
588      res = quote do:
589        if not(isNil(`fut`.internalError)):
590          when chronosStackTrace:
591            injectStacktrace(`fut`.internalError)
592          raise `fut`.internalError
593    res.deepLineInfo(info)
594    res
595  ```
596  
597  Thus, this will cause the error we see above.
598  
599  >[!info]
600  > Notice that it is not *casting* that causes an error.
601  
602  We could cast to `InternalRaisesFuture` before calling `await`, but although we can often be pretty sure that what we are doing is right, it would be better to avoid downcasting if possible.
603  
604  Thus, in our `waitHandle` it would be better that we capture the correct type. Fortunately, this is possible, although not obvious.
605  
606  Unfortunately, the following will not work as the type of the argument `h`:
607  
608  ```nim
609  Future[T].Raising([CancelledError])
610  Raising[T](Future[T],[CancelledError])
611  Raising(Future[T],[CancelledError])
612  ```
613  
614  Sadly, original `Raising` macro is not flexible enough to handle complications of the generics.
615  
616  We may like to define a custom type, e.g.:
617  
618  ```nim
619  type
620    Handle[T] = Future[T].Raising([CancelledError])
621  ```
622  
623  Unfortunately, `Raising` macro is again not flexible enough to handle this. Doing:
624  
625  ```nim
626  type
627    Handle[T] = Raising(Future[T], [CancelledError])
628  ```
629  
630  looks more promising, but trying to use `Handle[T]` as type for `h` in our `waitHandle` fails.
631  
632  What works is `auto`:
633  
634  ```nim
635  proc waitHandle[T](_: typedesc[Future[T]], h: auto): Future[T] {.async: (raises: [CancelledError]).} =
636    await h
637  
638  let handle = newRaisingFuture(int, [CancelledError])
639  handle.complete(42)
640  echo waitFor Future[int].waitHandle(handle)
641  ```
642  
643  Finally, I have experimented a bit with modifying the original `Rasing` macro from chronos, to see if I can make it a bit more permissive. In particular, where `Future[...].Raising(...)` seem to have limitations are generic types, e.g.:
644  
645  ```nim
646  SomeType2[T] = object
647    name: string
648    # will not compile
649    handle: Future[T].Raising([CancelledError])
650  ```
651  
652  Here is a version that works:
653  
654  ```nim
655  from pkg/chronos/internal/raisesfutures import makeNoRaises
656  
657  macro RaisingFuture(T: typedesc, E: typed): untyped =
658    let 
659      baseType = T.strVal
660      e =
661        case E.getTypeInst().typeKind()
662        of ntyTypeDesc: @[E]
663        of ntyArray:
664          for x in E:
665            if x.getTypeInst().typeKind != ntyTypeDesc:
666              error("Expected typedesc, got " & repr(x), x)
667          E.mapIt(it)
668        else:
669          error("Expected typedesc, got " & repr(E), E)
670          # @[]
671  
672    let raises = if e.len == 0:
673      makeNoRaises()
674    else:
675      nnkTupleConstr.newTree(e)
676  
677    result = nnkBracketExpr.newTree(
678      ident "InternalRaisesFuture",
679      newIdentNode(baseType),
680      raises
681    )
682  ```
683  
684  We can then do:
685  
686  ```nim
687  SomeType2[T] = object
688    name: string
689    # will not compile
690    handle: RaisingFuture(T, [CancelledError])
691  ```
692  
693  We finish this note with some examples of using the `RasingFuture` and `newRaisingFuture` macros:
694  
695  ```nim
696  type
697    MyError = object of CatchableError
698  
699    SomeType = object
700      name: string  
701      handle1: RaisingFuture(void, CancelledError)
702      handle2: RaisingFuture(void, [CancelledError, MyError])
703      handle3: RaisingFuture(int, [CancelledError])
704    
705    SomeType2[T] = object
706      name: string
707      handle: RaisingFuture(T, [CancelledError])
708      
709  
710  proc newSomeType(name: string): SomeType =
711    let t = SomeType(
712      name: name,
713      handle1: newRaisingFuture(void, CancelledError),
714      handle2: newRaisingFuture(void, [CancelledError, MyError]),
715      handle3: newRaisingFuture(int, [CancelledError]),
716    )
717    t
718  
719  proc newSomeType2[T](name: string): SomeType2[T] =
720    let t = SomeType2[T](
721      name: name,
722      handle: newRaisingFuture(T, CancelledError),
723    )
724    t
725  
726  let someTypeInstance = newSomeType("test")
727  
728  echo typeof(someTypeInstance.handle1)
729  echo typeof(someTypeInstance.handle2)
730  echo typeof(someTypeInstance.handle3)
731  
732  let someType2Instance = newSomeType2[int]("test2")
733  echo typeof(someType2Instance.handle)
734  
735  proc waitHandle[T](_: typedesc[Future[T]], h: auto): Future[T] {.async: (raises: [CancelledError]).} =
736    await h
737  
738  proc waitHandle2[T](_: typedesc[Future[T]], h: RaisingFuture(T, [CancelledError])): Future[T] {.async: (raises: [CancelledError]).} =
739    return await h
740  
741  
742  someTypeInstance.handle1.complete()
743  
744  waitFor Future[void].waitHandle(someTypeInstance.handle1)
745  echo "done 1"  
746  
747  someType2Instance.handle.complete(42)
748  
749  echo waitFor Future[int].waitHandle(someType2Instance.handle)
750  echo "done 2"
751  
752  let handle = newRaisingFuture(int, CancelledError)
753  handle.complete(43)
754  echo waitFor Future[int].waitHandle2(handle)
755  echo "done 3"
756  ```
757  
758  You can also find the source files on GitHub:
759  
760  - [raisingfutures.nim](https://gist.github.com/marcinczenko/cb48898d24314fbdebe57fd815c3c1be#file-raisingfutures-nim)
761  - [raisingfutures2.nim](https://gist.github.com/marcinczenko/c667fad0b70718d6f157275a2a7e7a93#file-raisingfutures2-nim)