From e2fc60caa5002b924302572b3bb267fe85feaccc Mon Sep 17 00:00:00 2001 From: nitely Date: Fri, 27 Dec 2024 17:36:47 -0300 Subject: [PATCH 1/7] async error tracking --- lib/pure/asyncfutures.nim | 37 +++++++++++++++++-- lib/pure/asyncmacro.nim | 53 +++++++++++++++++++++++---- tests/async/tasync_error_tracking.nim | 51 ++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 tests/async/tasync_error_tracking.nim diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index 41f1f70a37521..0f5e332a4ad2f 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -import std/[os, sets, tables, strutils, times, heapqueue, options, deques, cstrutils, typetraits] +import std/[os, macros, sets, tables, strutils, times, heapqueue, options, deques, cstrutils, typetraits] import system/stacktraces @@ -39,6 +39,10 @@ type FutureVar*[T] = distinct Future[T] + FutureUntracked*[T] = Future[T] + + FutureTracked*[T, E] = distinct Future[T] + FutureError* = object of Defect cause*: FutureBase @@ -136,6 +140,9 @@ proc newFutureVar*[T](fromProc = "unspecified"): owned(FutureVar[T]) = result = typeof(result)(fo) when isFutureLoggingEnabled: logFutureStart(Future[T](result)) +proc newFutureUntracked*[T](fromProc = "unspecified"): FutureUntracked[T] = + newFuture[T](fromProc) + proc clean*[T](future: FutureVar[T]) = ## Resets the `finished` status of `future`. Future[T](future).finished = false @@ -162,11 +169,13 @@ proc checkFinished[T](future: Future[T]) = err.cause = future raise err -proc call(callbacks: var CallbackList) = +proc call(callbacks: var CallbackList) {.raises: [].} = var current = callbacks while true: if not current.function.isNil: - callSoon(current.function) + # XXX make callbacks {.raise: [].} + {.cast(raises: []).}: + callSoon(current.function) if current.next.isNil: break @@ -384,6 +393,28 @@ proc read*[T](future: Future[T] | FutureVar[T]): lent T = proc read*(future: Future[void] | FutureVar[void]) = readImpl(future, void) +macro readTrackedImpl(future: FutureTracked): untyped = + # XXX refactor readImpl + let t = getTypeInst(future)[1] + let e = getTypeInst(future)[2] + let types = getType(e) + var raisesList = newNimNode(nnkBracket) + for r in types[1..^1]: + raisesList.add(r) + #echo repr raisesList + #echo repr t + let theCast = quote do: + cast(raises: `raisesList`) + quote do: + {.`theCast`.}: + readImpl(`future`, `t`) + +proc read*[T, E](future: FutureTracked[T, E]): lent T = + readTrackedImpl(future) + +proc read*[E](future: FutureTracked[void, E]) = + readTrackedImpl(future) + proc readError*[T](future: Future[T]): ref Exception = ## Retrieves the exception stored in `future`. ## diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 951d98bd39dd4..b44962b7e927b 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -9,7 +9,7 @@ ## Implements the `async` and `multisync` macros for `asyncdispatch`. -import std/[macros, strutils, asyncfutures] +import std/[macros, strutils, asyncfutures, effecttraits] type Context = ref object @@ -49,9 +49,10 @@ template createCb(futTyp, strName, identName, futureVarCompletions: untyped) = except: futureVarCompletions if fut.finished: - # Take a look at tasyncexceptions for the bug which this fixes. - # That test explains it better than I can here. - raise + {.cast(raises: []).}: + # Take a look at tasyncexceptions for the bug which this fixes. + # That test explains it better than I can here. + raise else: fut.fail(getCurrentException()) {.pop.} @@ -158,6 +159,9 @@ proc verifyReturnType(typeName: string, node: NimNode = nil) = error("Expected return type of 'Future' got '$1'" % typeName, node) +template isUntracked[T](f: Future[T]): bool = + getTypeInst(getType(typeof f))[1][0] == getTypeInst(getType(FutureUntracked[T]))[1][0] + template await*(f: typed): untyped {.used.} = static: error "await expects Future[T], got " & $typeof(f) @@ -171,7 +175,11 @@ template await*[T](f: Future[T]): auto {.used.} = var internalTmpFuture: FutureBase = f yield internalTmpFuture {.line: instantiationInfo(fullPaths = true).}: - (cast[typeof(f)](internalTmpFuture)).read() + when f.isUntracked: + {.cast(raises: []).}: + cast[typeof(f)](internalTmpFuture).read() + else: + cast[typeof(f)](internalTmpFuture).read() else: macro errorAsync(futureError: Future[T]) = error( @@ -180,6 +188,16 @@ template await*[T](f: Future[T]): auto {.used.} = futureError) errorAsync(f) +template await*[T, E](f: FutureTracked[T, E]): untyped = + template yieldFuture = yield FutureBase() + when compiles(yieldFuture): + var internalTmpFuture: FutureBase = Future[T](f) + yield internalTmpFuture + {.line: instantiationInfo(fullPaths = true).}: + cast[typeof(f)](internalTmpFuture).read() + else: + {.error: "await is only available within {.async.}".} + proc asyncSingleProc(prc: NimNode): NimNode = ## This macro transforms a single procedure into a closure iterator. ## The `async` macro supports a stmtList holding multiple async procedures. @@ -303,7 +321,7 @@ proc asyncSingleProc(prc: NimNode): NimNode = newVarStmt(retFutureSym, newCall( newNimNode(nnkBracketExpr, prc.body).add( - newIdentNode("newFuture"), + newIdentNode("newFutureUntracked"), subRetType), newLit(prcName)))) # Get type from return type of this proc @@ -317,7 +335,7 @@ proc asyncSingleProc(prc: NimNode): NimNode = # Add discardable pragma. if returnType.kind == nnkEmpty: # xxx consider removing `owned`? it's inconsistent with non-void case - result.params[0] = quote do: owned(Future[void]) + result.params[0] = quote do: owned(FutureUntracked[void]) # based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47 if procBody.kind != nnkEmpty: @@ -394,3 +412,24 @@ macro multisync*(prc: untyped): untyped = result = newStmtList() result.add(asyncSingleProc(asyncPrc)) result.add(sync) + +macro trackFuture*(prc: typed): untyped = + # XXX error instead of asserts + #echo repr getRaisesList(prc[0]) + #assert prc.kind == nnkCall + let procImpl = getTypeImpl(prc[0]) + #assert procImpl.kind == nnkProcTy + let retTyp = procImpl.params[0] + #assert retTyp.kind == nnkBracketExpr + let fut = repr(retTyp[0]) + #assert fut == "FutureUntracked", fut + let baseTyp = retTyp[1] + let raisesList = getRaisesList(prc[0]) + let exTyp = if raisesList.len == 0: + newIdentNode("void") + else: + newNimNode(nnkTupleConstr) + for r in raisesList: + exTyp.add r + result = quote do: + FutureTracked[`baseTyp`, `exTyp`](`prc`) diff --git a/tests/async/tasync_error_tracking.nim b/tests/async/tasync_error_tracking.nim new file mode 100644 index 0000000000000..c15a41ce81d97 --- /dev/null +++ b/tests/async/tasync_error_tracking.nim @@ -0,0 +1,51 @@ +import std/asyncdispatch + +type MyError = object of ValueError + +proc err(throw: bool) = + if throw: + raise newException(MyError, "myerr") + +block: + proc bar() {.async.} = + err(false) + + proc foo() {.async.} = + await bar() + + proc main {.async, raises: [MyError].} = + await foo() + + waitFor main() + +block: + proc foo() {.async, raises: [MyError].} = + err(false) + + proc main {.async, raises: [MyError].} = + await foo() + + waitFor main() + +block: + proc foo() {.async, raises: [MyError].} = + err(false) + + proc bar(fut: FutureTracked[void, (MyError,)]) {.async, raises: [MyError].} = + await fut + + proc main {.async, raises: [MyError].} = + let fooFut = trackFuture foo() + await bar(fooFut) + + waitFor main() + +block: + template good = + proc foo() {.async, raises: [MyError].} = + err(false) + template missingRaise = + proc foo() {.async, raises: [].} = + err(false) + doAssert compiles(good()) + doAssert not compiles(missingRaise()) From 79c1ee4bd9434ad1d4fadaa24ece9d33d6345682 Mon Sep 17 00:00:00 2001 From: nitely Date: Tue, 31 Dec 2024 21:48:22 -0300 Subject: [PATCH 2/7] error typed result --- lib/pure/asyncfutures.nim | 13 +++----- lib/pure/asyncmacro.nim | 65 ++++++++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index 0f5e332a4ad2f..9cf2b1b86a0f7 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -39,9 +39,7 @@ type FutureVar*[T] = distinct Future[T] - FutureUntracked*[T] = Future[T] - - FutureTracked*[T, E] = distinct Future[T] + FutureEx*[T, E] = ref object of Future[T] FutureError* = object of Defect cause*: FutureBase @@ -140,9 +138,6 @@ proc newFutureVar*[T](fromProc = "unspecified"): owned(FutureVar[T]) = result = typeof(result)(fo) when isFutureLoggingEnabled: logFutureStart(Future[T](result)) -proc newFutureUntracked*[T](fromProc = "unspecified"): FutureUntracked[T] = - newFuture[T](fromProc) - proc clean*[T](future: FutureVar[T]) = ## Resets the `finished` status of `future`. Future[T](future).finished = false @@ -393,7 +388,7 @@ proc read*[T](future: Future[T] | FutureVar[T]): lent T = proc read*(future: Future[void] | FutureVar[void]) = readImpl(future, void) -macro readTrackedImpl(future: FutureTracked): untyped = +macro readTrackedImpl(future: FutureEx): untyped = # XXX refactor readImpl let t = getTypeInst(future)[1] let e = getTypeInst(future)[2] @@ -409,10 +404,10 @@ macro readTrackedImpl(future: FutureTracked): untyped = {.`theCast`.}: readImpl(`future`, `t`) -proc read*[T, E](future: FutureTracked[T, E]): lent T = +proc read*[T, E](future: FutureEx[T, E]): lent T = readTrackedImpl(future) -proc read*[E](future: FutureTracked[void, E]) = +proc read*[E](future: FutureEx[void, E]) = readTrackedImpl(future) proc readError*[T](future: Future[T]): ref Exception = diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index b44962b7e927b..78323f5c2d487 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -159,9 +159,6 @@ proc verifyReturnType(typeName: string, node: NimNode = nil) = error("Expected return type of 'Future' got '$1'" % typeName, node) -template isUntracked[T](f: Future[T]): bool = - getTypeInst(getType(typeof f))[1][0] == getTypeInst(getType(FutureUntracked[T]))[1][0] - template await*(f: typed): untyped {.used.} = static: error "await expects Future[T], got " & $typeof(f) @@ -175,11 +172,7 @@ template await*[T](f: Future[T]): auto {.used.} = var internalTmpFuture: FutureBase = f yield internalTmpFuture {.line: instantiationInfo(fullPaths = true).}: - when f.isUntracked: - {.cast(raises: []).}: - cast[typeof(f)](internalTmpFuture).read() - else: - cast[typeof(f)](internalTmpFuture).read() + cast[typeof(f)](internalTmpFuture).read() else: macro errorAsync(futureError: Future[T]) = error( @@ -188,7 +181,7 @@ template await*[T](f: Future[T]): auto {.used.} = futureError) errorAsync(f) -template await*[T, E](f: FutureTracked[T, E]): untyped = +template await*[T, E](f: FutureEx[T, E]): untyped = template yieldFuture = yield FutureBase() when compiles(yieldFuture): var internalTmpFuture: FutureBase = Future[T](f) @@ -321,7 +314,7 @@ proc asyncSingleProc(prc: NimNode): NimNode = newVarStmt(retFutureSym, newCall( newNimNode(nnkBracketExpr, prc.body).add( - newIdentNode("newFutureUntracked"), + newIdentNode("newFuture"), subRetType), newLit(prcName)))) # Get type from return type of this proc @@ -335,13 +328,19 @@ proc asyncSingleProc(prc: NimNode): NimNode = # Add discardable pragma. if returnType.kind == nnkEmpty: # xxx consider removing `owned`? it's inconsistent with non-void case - result.params[0] = quote do: owned(FutureUntracked[void]) + result.params[0] = quote do: owned(Future[void]) # based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47 if procBody.kind != nnkEmpty: + result.name = newIdentNode($result.name & "_asyncinternal") + let prcName = result.name body2.add quote do: `outerProcBody` result.body = body2 + result = quote do: + asyncraises: + `result` + `prcName` macro async*(prc: untyped): untyped = ## Macro which processes async procedures into the appropriate @@ -413,23 +412,39 @@ macro multisync*(prc: untyped): untyped = result.add(asyncSingleProc(asyncPrc)) result.add(sync) -macro trackFuture*(prc: typed): untyped = - # XXX error instead of asserts - #echo repr getRaisesList(prc[0]) - #assert prc.kind == nnkCall - let procImpl = getTypeImpl(prc[0]) - #assert procImpl.kind == nnkProcTy - let retTyp = procImpl.params[0] - #assert retTyp.kind == nnkBracketExpr - let fut = repr(retTyp[0]) - #assert fut == "FutureUntracked", fut - let baseTyp = retTyp[1] - let raisesList = getRaisesList(prc[0]) +proc asyncraisesImpl(body: NimNode): NimNode = + expectKind(body, nnkStmtListExpr) + expectKind(body[0], nnkProcDef) + expectKind(body[1], nnkSym) + var prc = copyNimTree(body[0]) # XXX ? + expectKind(prc.params[0], nnkCall) # owned(Future[T]) + let retTyp = prc.params[0][1] + let baseTyp = if retTyp.kind == nnkBracketExpr: + retTyp[1] + else: # Future[void] + expectKind(retTyp, nnkCall) + retTyp[2] + let raisesList = getRaisesList(body[1]) let exTyp = if raisesList.len == 0: newIdentNode("void") else: newNimNode(nnkTupleConstr) for r in raisesList: exTyp.add r - result = quote do: - FutureTracked[`baseTyp`, `exTyp`](`prc`) + prc.params[0] = quote do: FutureEx[`baseTyp`, `exTyp`] + #echo repr prc.params[0] + let prcName = $prc.name + let suffix = "_asyncinternal" + prc.name = newIdentNode(prcName[0 .. ^(suffix.len+1)]) + expectKind(prc.body, nnkStmtList) + let prcBody = if prc.body[^1].kind == nnkStmtList: + prc.body[^1] + else: + prc.body + expectKind(prcBody[^1], nnkReturnStmt) + let returnValue = prcBody[^1][0][1] + prcBody[^1][0][1] = quote do: cast[FutureEx[`baseTyp`, `exTyp`]](`returnValue`) + return prc + +macro asyncraises*(prc: typed): untyped = + asyncraisesImpl(prc) From ee2d276dfbe512c5b4f411f0beb215a5cdb7ef00 Mon Sep 17 00:00:00 2001 From: nitely Date: Wed, 1 Jan 2025 03:32:05 -0300 Subject: [PATCH 3/7] dead end gives uncomputedEffects for generics async proc --- lib/pure/asyncfutures.nim | 6 ++- lib/pure/asyncmacro.nim | 58 +++++++++++++++++---------- tests/async/tasync_error_tracking.nim | 5 +-- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index 9cf2b1b86a0f7..bb7df0cc42f48 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -394,8 +394,10 @@ macro readTrackedImpl(future: FutureEx): untyped = let e = getTypeInst(future)[2] let types = getType(e) var raisesList = newNimNode(nnkBracket) - for r in types[1..^1]: - raisesList.add(r) + if types.len > 0: + for r in types[1..^1]: + raisesList.add(r) + # else check repr(types) == "void" #echo repr raisesList #echo repr t let theCast = quote do: diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 78323f5c2d487..dc715de52a10a 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -182,7 +182,9 @@ template await*[T](f: Future[T]): auto {.used.} = errorAsync(f) template await*[T, E](f: FutureEx[T, E]): untyped = - template yieldFuture = yield FutureBase() + when not defined(nimHasTemplateRedefinitionPragma): + {.pragma: redefine.} + template yieldFuture {.redefine.} = yield FutureBase() when compiles(yieldFuture): var internalTmpFuture: FutureBase = Future[T](f) yield internalTmpFuture @@ -231,7 +233,7 @@ proc asyncSingleProc(prc: NimNode): NimNode = verifyReturnType(fut, returnType[0]) baseType = returnType[2] elif returnType.kind == nnkEmpty: - baseType = returnType + baseType = newIdentNode("void") else: baseType = nil verifyReturnType(repr(returnType), returnType) @@ -244,9 +246,7 @@ proc asyncSingleProc(prc: NimNode): NimNode = # transformation even more complex. let body2 = extractDocCommentsAndRunnables(prc.body) - var subRetType = - if returnType.kind == nnkEmpty: newIdentNode("void") - else: baseType + let subRetType = baseType let retFutParamSym = genSym(nskParam, "retFutParamSym") # -> iterator nameIter(retFutParam: Future[T]): FutureBase {.closure.} = @@ -332,15 +332,18 @@ proc asyncSingleProc(prc: NimNode): NimNode = # based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47 if procBody.kind != nnkEmpty: - result.name = newIdentNode($result.name & "_asyncinternal") - let prcName = result.name body2.add quote do: `outerProcBody` result.body = body2 + let prcCopy = copyNimTree(result) + prcCopy.name = newIdentNode($prcCopy.name & "_asyncinternal") + let prcName = prcCopy.name result = quote do: asyncraises: `result` + `prcCopy` `prcName` + #echo treeRepr result macro async*(prc: untyped): untyped = ## Macro which processes async procedures into the appropriate @@ -413,18 +416,28 @@ macro multisync*(prc: untyped): untyped = result.add(sync) proc asyncraisesImpl(body: NimNode): NimNode = + #echo repr body expectKind(body, nnkStmtListExpr) - expectKind(body[0], nnkProcDef) - expectKind(body[1], nnkSym) - var prc = copyNimTree(body[0]) # XXX ? - expectKind(prc.params[0], nnkCall) # owned(Future[T]) - let retTyp = prc.params[0][1] + var prc = if body[0].kind == nnkStmtList: # XXX ? + expectKind(body[0][1], nnkProcDef) + body[0][1] + else: + expectKind(body[0], nnkProcDef) + body[0] + expectKind(body[2], nnkSym) + var retTyp = prc.params[0] + if retTyp.kind in nnkCallKinds and retTyp[0].eqIdent("owned") and retTyp.len == 2: + retTyp = retTyp[1] let baseTyp = if retTyp.kind == nnkBracketExpr: retTyp[1] - else: # Future[void] - expectKind(retTyp, nnkCall) + elif retTyp.kind in nnkCallKinds and retTyp[0].eqIdent("[]"): retTyp[2] - let raisesList = getRaisesList(body[1]) + else: + expectKind(retTyp, nnkEmpty) + newIdentNode("void") + echo repr body[2] + let raisesList = getRaisesList(body[2]) + echo repr raisesList let exTyp = if raisesList.len == 0: newIdentNode("void") else: @@ -432,18 +445,21 @@ proc asyncraisesImpl(body: NimNode): NimNode = for r in raisesList: exTyp.add r prc.params[0] = quote do: FutureEx[`baseTyp`, `exTyp`] - #echo repr prc.params[0] - let prcName = $prc.name - let suffix = "_asyncinternal" - prc.name = newIdentNode(prcName[0 .. ^(suffix.len+1)]) expectKind(prc.body, nnkStmtList) let prcBody = if prc.body[^1].kind == nnkStmtList: prc.body[^1] else: prc.body expectKind(prcBody[^1], nnkReturnStmt) - let returnValue = prcBody[^1][0][1] - prcBody[^1][0][1] = quote do: cast[FutureEx[`baseTyp`, `exTyp`]](`returnValue`) + echo treeRepr prcBody[^1] + echo repr prcBody[^1][0].kind + if prcBody[^1][0].kind == nnkAsgn: + let returnValue = prcBody[^1][0][1] + prcBody[^1][0][1] = quote do: cast[FutureEx[`baseTyp`, `exTyp`]](`returnValue`) + else: + expectKind(prcBody[^1][0], nnkSym) + let returnValue = prcBody[^1][0] + prcBody[^1][0] = quote do: cast[FutureEx[`baseTyp`, `exTyp`]](`returnValue`) return prc macro asyncraises*(prc: typed): untyped = diff --git a/tests/async/tasync_error_tracking.nim b/tests/async/tasync_error_tracking.nim index c15a41ce81d97..9edc2f50a3e07 100644 --- a/tests/async/tasync_error_tracking.nim +++ b/tests/async/tasync_error_tracking.nim @@ -31,12 +31,11 @@ block: proc foo() {.async, raises: [MyError].} = err(false) - proc bar(fut: FutureTracked[void, (MyError,)]) {.async, raises: [MyError].} = + proc bar(fut: FutureEx[void, (MyError,)]) {.async, raises: [MyError].} = await fut proc main {.async, raises: [MyError].} = - let fooFut = trackFuture foo() - await bar(fooFut) + await bar(foo()) waitFor main() From 9fbf0c0aed4e1606555af38dbc5c30255bc90bd7 Mon Sep 17 00:00:00 2001 From: nitely Date: Thu, 2 Jan 2025 17:02:53 -0300 Subject: [PATCH 4/7] new approach --- lib/pure/asyncfutures.nim | 25 ++--- lib/pure/asyncmacro.nim | 107 +++++++++---------- nimdoc/testproject/expected/testproject.html | 7 +- tests/async/tasync_error_tracking.nim | 87 +++++++++++++-- 4 files changed, 141 insertions(+), 85 deletions(-) diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index bb7df0cc42f48..c53c134376584 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -import std/[os, macros, sets, tables, strutils, times, heapqueue, options, deques, cstrutils, typetraits] +import std/[os, macros, sets, tables, strutils, strformat, times, heapqueue, options, deques, cstrutils, typetraits] import system/stacktraces @@ -39,7 +39,7 @@ type FutureVar*[T] = distinct Future[T] - FutureEx*[T, E] = ref object of Future[T] + FutureEx*[T, E] = distinct Future[T] FutureError* = object of Defect cause*: FutureBase @@ -143,7 +143,7 @@ proc clean*[T](future: FutureVar[T]) = Future[T](future).finished = false Future[T](future).error = nil -proc checkFinished[T](future: Future[T]) = +proc checkFinished[T](future: Future[T]) {.raises: [].} = ## Checks whether `future` is finished. If it is then raises a ## `FutureError`. when not defined(release): @@ -297,12 +297,11 @@ template getFilenameProcname(entry: StackTraceEntry): (string, string) = else: ($entry.filename, $entry.procname) -proc format(entry: StackTraceEntry): string = +proc format(entry: StackTraceEntry): string {.raises: [].} = let (filename, procname) = getFilenameProcname(entry) - let left = "$#($#)" % [filename, $entry.line] - result = spaces(2) & "$# $#\n" % [left, procname] + result = &"{spaces(2)}{filename}({$entry.line}) {procname}\n" -proc isInternal(entry: StackTraceEntry): bool = +proc isInternal(entry: StackTraceEntry): bool {.raises: [].} = # --excessiveStackTrace:off const internals = [ "asyncdispatch.nim", @@ -315,7 +314,7 @@ proc isInternal(entry: StackTraceEntry): bool = return true return false -proc `$`*(stackTraceEntries: seq[StackTraceEntry]): string = +proc `$`*(stackTraceEntries: seq[StackTraceEntry]): string {.raises: [].} = result = "" when defined(nimStackTraceOverride): let entries = addDebuggingInfo(stackTraceEntries) @@ -394,16 +393,14 @@ macro readTrackedImpl(future: FutureEx): untyped = let e = getTypeInst(future)[2] let types = getType(e) var raisesList = newNimNode(nnkBracket) - if types.len > 0: - for r in types[1..^1]: - raisesList.add(r) - # else check repr(types) == "void" + for r in types[1..^1]: + raisesList.add(r) #echo repr raisesList #echo repr t - let theCast = quote do: + let raisesCast = quote do: cast(raises: `raisesList`) quote do: - {.`theCast`.}: + {.`raisesCast`.}: readImpl(`future`, `t`) proc read*[T, E](future: FutureEx[T, E]): lent T = diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index dc715de52a10a..308be7cba8781 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -159,6 +159,34 @@ proc verifyReturnType(typeName: string, node: NimNode = nil) = error("Expected return type of 'Future' got '$1'" % typeName, node) +macro withRaises[T](f: Future[T], body: untyped): untyped = + #echo repr f.kind + # XXX support more cases + let prcSym = case f.kind + of nnkSym: + if f.getImpl.kind == nnkIdentDefs and f.getImpl[^1].kind == nnkCall: + f.getImpl[^1][0] + else: + nil + of nnkCall: + if f[0].kind == nnkSym: f[0] else: nil + else: + nil + #echo repr prcSym + if prcSym != nil: + let raisesList = getRaisesList(prcSym) + var raisesListTyp = newNimNode(nnkBracket) + if raisesList.len > 0: + for r in raisesList[1..^1]: + raisesListTyp.add(r) + let raisesCast = quote do: + cast(raises: `raisesListTyp`) + quote do: + {.`raisesCast`.}: + `body` + else: + body + template await*(f: typed): untyped {.used.} = static: error "await expects Future[T], got " & $typeof(f) @@ -172,7 +200,8 @@ template await*[T](f: Future[T]): auto {.used.} = var internalTmpFuture: FutureBase = f yield internalTmpFuture {.line: instantiationInfo(fullPaths = true).}: - cast[typeof(f)](internalTmpFuture).read() + withRaises f: + cast[typeof(f)](internalTmpFuture).read() else: macro errorAsync(futureError: Future[T]) = error( @@ -182,9 +211,7 @@ template await*[T](f: Future[T]): auto {.used.} = errorAsync(f) template await*[T, E](f: FutureEx[T, E]): untyped = - when not defined(nimHasTemplateRedefinitionPragma): - {.pragma: redefine.} - template yieldFuture {.redefine.} = yield FutureBase() + template yieldFuture = yield FutureBase() when compiles(yieldFuture): var internalTmpFuture: FutureBase = Future[T](f) yield internalTmpFuture @@ -233,7 +260,7 @@ proc asyncSingleProc(prc: NimNode): NimNode = verifyReturnType(fut, returnType[0]) baseType = returnType[2] elif returnType.kind == nnkEmpty: - baseType = newIdentNode("void") + baseType = returnType else: baseType = nil verifyReturnType(repr(returnType), returnType) @@ -246,7 +273,9 @@ proc asyncSingleProc(prc: NimNode): NimNode = # transformation even more complex. let body2 = extractDocCommentsAndRunnables(prc.body) - let subRetType = baseType + var subRetType = + if returnType.kind == nnkEmpty: newIdentNode("void") + else: baseType let retFutParamSym = genSym(nskParam, "retFutParamSym") # -> iterator nameIter(retFutParam: Future[T]): FutureBase {.closure.} = @@ -335,15 +364,6 @@ proc asyncSingleProc(prc: NimNode): NimNode = body2.add quote do: `outerProcBody` result.body = body2 - let prcCopy = copyNimTree(result) - prcCopy.name = newIdentNode($prcCopy.name & "_asyncinternal") - let prcName = prcCopy.name - result = quote do: - asyncraises: - `result` - `prcCopy` - `prcName` - #echo treeRepr result macro async*(prc: untyped): untyped = ## Macro which processes async procedures into the appropriate @@ -415,52 +435,23 @@ macro multisync*(prc: untyped): untyped = result.add(asyncSingleProc(asyncPrc)) result.add(sync) -proc asyncraisesImpl(body: NimNode): NimNode = - #echo repr body - expectKind(body, nnkStmtListExpr) - var prc = if body[0].kind == nnkStmtList: # XXX ? - expectKind(body[0][1], nnkProcDef) - body[0][1] - else: - expectKind(body[0], nnkProcDef) - body[0] - expectKind(body[2], nnkSym) - var retTyp = prc.params[0] - if retTyp.kind in nnkCallKinds and retTyp[0].eqIdent("owned") and retTyp.len == 2: - retTyp = retTyp[1] - let baseTyp = if retTyp.kind == nnkBracketExpr: - retTyp[1] - elif retTyp.kind in nnkCallKinds and retTyp[0].eqIdent("[]"): - retTyp[2] - else: - expectKind(retTyp, nnkEmpty) - newIdentNode("void") - echo repr body[2] - let raisesList = getRaisesList(body[2]) - echo repr raisesList +macro toFutureEx*(prc: typed): untyped = + # XXX error instead of asserts + #echo repr getRaisesList(prc[0]) + #assert prc.kind == nnkCall + let procImpl = getTypeImpl(prc[0]) + #assert procImpl.kind == nnkProcTy + let retTyp = procImpl.params[0] + #assert retTyp.kind == nnkBracketExpr + #let fut = repr(retTyp[0]) + #assert fut == "FutureUntracked", fut + let baseTyp = retTyp[1] + let raisesList = getRaisesList(prc[0]) let exTyp = if raisesList.len == 0: newIdentNode("void") else: newNimNode(nnkTupleConstr) for r in raisesList: exTyp.add r - prc.params[0] = quote do: FutureEx[`baseTyp`, `exTyp`] - expectKind(prc.body, nnkStmtList) - let prcBody = if prc.body[^1].kind == nnkStmtList: - prc.body[^1] - else: - prc.body - expectKind(prcBody[^1], nnkReturnStmt) - echo treeRepr prcBody[^1] - echo repr prcBody[^1][0].kind - if prcBody[^1][0].kind == nnkAsgn: - let returnValue = prcBody[^1][0][1] - prcBody[^1][0][1] = quote do: cast[FutureEx[`baseTyp`, `exTyp`]](`returnValue`) - else: - expectKind(prcBody[^1][0], nnkSym) - let returnValue = prcBody[^1][0] - prcBody[^1][0] = quote do: cast[FutureEx[`baseTyp`, `exTyp`]](`returnValue`) - return prc - -macro asyncraises*(prc: typed): untyped = - asyncraisesImpl(prc) + result = quote do: + FutureEx[`baseTyp`, `exTyp`](`prc`) diff --git a/nimdoc/testproject/expected/testproject.html b/nimdoc/testproject/expected/testproject.html index 43a72d99db0c1..a269412ca3db2 100644 --- a/nimdoc/testproject/expected/testproject.html +++ b/nimdoc/testproject/expected/testproject.html @@ -559,8 +559,7 @@

Procs

-
proc asyncFun1(): Future[int] {....stackTrace: false,
-                                raises: [Exception, ValueError],
+  
proc asyncFun1(): Future[int] {....stackTrace: false, raises: [],
                                 tags: [RootEffect], forbids: [].}
@@ -572,7 +571,7 @@

Procs

-
proc asyncFun2(): owned(Future[void]) {....stackTrace: false, raises: [Exception],
+  
proc asyncFun2(): owned(Future[void]) {....stackTrace: false, raises: [],
                                         tags: [RootEffect], forbids: [].}
@@ -584,7 +583,7 @@

Procs

-
proc asyncFun3(): owned(Future[void]) {....stackTrace: false, raises: [Exception],
+  
proc asyncFun3(): owned(Future[void]) {....stackTrace: false, raises: [],
                                         tags: [RootEffect], forbids: [].}
diff --git a/tests/async/tasync_error_tracking.nim b/tests/async/tasync_error_tracking.nim index 9edc2f50a3e07..395f7aa9e38b3 100644 --- a/tests/async/tasync_error_tracking.nim +++ b/tests/async/tasync_error_tracking.nim @@ -7,10 +7,10 @@ proc err(throw: bool) = raise newException(MyError, "myerr") block: - proc bar() {.async.} = + proc bar {.async.} = err(false) - proc foo() {.async.} = + proc foo {.async.} = await bar() proc main {.async, raises: [MyError].} = @@ -19,7 +19,7 @@ block: waitFor main() block: - proc foo() {.async, raises: [MyError].} = + proc foo {.async, raises: [MyError].} = err(false) proc main {.async, raises: [MyError].} = @@ -28,23 +28,92 @@ block: waitFor main() block: - proc foo() {.async, raises: [MyError].} = + proc foo {.async, raises: [MyError].} = err(false) proc bar(fut: FutureEx[void, (MyError,)]) {.async, raises: [MyError].} = await fut proc main {.async, raises: [MyError].} = - await bar(foo()) + let fooFut = toFutureEx foo() + await bar(fooFut) waitFor main() +block: + proc foo: Future[int] {.async, raises: [].} = + discard + +block: + # XXX raises: [] + type FooBar = object + fcb: proc(): Future[void] {.closure, gcsafe.} + + proc bar {.async.} = + discard + + proc foo {.async.} = + var f = FooBar(fcb: bar) + await f.fcb() + + template good = + proc main {.async, raises: [Exception].} = + await foo() + template bad = + proc main {.async, raises: [].} = + await foo() + doAssert compiles(good()) + doAssert not compiles(bad()) + +block: + template good = + proc foo {.async, raises: [MyError].} = + err(false) + template bad = + proc foo {.async, raises: [].} = + err(false) + doAssert compiles(good()) + doAssert not compiles(bad()) + block: template good = - proc foo() {.async, raises: [MyError].} = + proc bar {.async.} = err(false) - template missingRaise = - proc foo() {.async, raises: [].} = + proc foo {.async.} = + await bar() + proc main {.async, raises: [MyError].} = + await foo() + template bad = + proc bar {.async.} = err(false) + proc foo {.async.} = + await bar() + proc main {.async, raises: [].} = + await foo() doAssert compiles(good()) - doAssert not compiles(missingRaise()) + doAssert not compiles(bad()) + +block: + template good = + proc foo(fut: FutureEx[void, (MyError,)]) {.async, raises: [MyError].} = + await fut + template bad = + proc foo(fut: FutureEx[void, (MyError,)]) {.async, raises: [].} = + await fut + doAssert compiles(good()) + doAssert not compiles(bad()) + +# XXX this should not compile; +# add Future.isInternal field? +when false: + proc foo {.async, raises: [].} = + let f = newFuture[void]() + f.complete() + await f +when false: + proc fut: Future[void] = + newFuture[void]() + proc main {.async, raises: [].} = + let f = fut() + f.complete() + await f From 1ed9ea6c4a289aba68cea2995804036acf483f6a Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 4 Jan 2025 16:56:27 -0300 Subject: [PATCH 5/7] detect async procs --- lib/pure/asyncmacro.nim | 38 +++++++++++---- tests/async/tasync_error_tracking.nim | 68 ++++++++++++++++++--------- 2 files changed, 76 insertions(+), 30 deletions(-) diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 308be7cba8781..5783bde23071b 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -159,12 +159,29 @@ proc verifyReturnType(typeName: string, node: NimNode = nil) = error("Expected return type of 'Future' got '$1'" % typeName, node) +proc isAsyncPrc0(n: NimNode): bool = + if n.kind == nnkBlockStmt and n[0].strVal == "asynctrack": + return true + if n.kind in RoutineNodes: + return false + for i in 0 .. n.len-1: + if isAsyncPrc0(n[i]): + return true + return false + +proc isAsyncPrc(n: NimNode): bool = + for i in 0 .. n.len-1: + if isAsyncPrc0(n[i]): + return true + return false + macro withRaises[T](f: Future[T], body: untyped): untyped = #echo repr f.kind # XXX support more cases let prcSym = case f.kind of nnkSym: if f.getImpl.kind == nnkIdentDefs and f.getImpl[^1].kind == nnkCall: + #echo repr f.getImpl[^1][0] f.getImpl[^1][0] else: nil @@ -173,7 +190,8 @@ macro withRaises[T](f: Future[T], body: untyped): untyped = else: nil #echo repr prcSym - if prcSym != nil: + #echo repr prcSym.getImpl + if prcSym != nil and isAsyncPrc(prcSym.getImpl): let raisesList = getRaisesList(prcSym) var raisesListTyp = newNimNode(nnkBracket) if raisesList.len > 0: @@ -361,8 +379,10 @@ proc asyncSingleProc(prc: NimNode): NimNode = # based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47 if procBody.kind != nnkEmpty: + let asynctrack = ident"asynctrack" body2.add quote do: - `outerProcBody` + block `asynctrack`: + `outerProcBody` result.body = body2 macro async*(prc: untyped): untyped = @@ -436,15 +456,15 @@ macro multisync*(prc: untyped): untyped = result.add(sync) macro toFutureEx*(prc: typed): untyped = - # XXX error instead of asserts - #echo repr getRaisesList(prc[0]) - #assert prc.kind == nnkCall + template check(cond: untyped): untyped = + if not cond: + error("async proc call expected", prc) + check prc.kind == nnkCall + check prc[0].kind == nnkSym + check isAsyncPrc(prc[0].getImpl) let procImpl = getTypeImpl(prc[0]) - #assert procImpl.kind == nnkProcTy + check procImpl.kind == nnkProcTy let retTyp = procImpl.params[0] - #assert retTyp.kind == nnkBracketExpr - #let fut = repr(retTyp[0]) - #assert fut == "FutureUntracked", fut let baseTyp = retTyp[1] let raisesList = getRaisesList(prc[0]) let exTyp = if raisesList.len == 0: diff --git a/tests/async/tasync_error_tracking.nim b/tests/async/tasync_error_tracking.nim index 395f7aa9e38b3..54e5e8cf1867a 100644 --- a/tests/async/tasync_error_tracking.nim +++ b/tests/async/tasync_error_tracking.nim @@ -45,7 +45,9 @@ block: discard block: - # XXX raises: [] + # we cannot tell if fcb is an async proc + # or a closure that returns a user created newFuture() + # that can raise anything type FooBar = object fcb: proc(): Future[void] {.closure, gcsafe.} @@ -76,18 +78,16 @@ block: doAssert not compiles(bad()) block: + proc bar {.async.} = + err(false) + + proc foo {.async.} = + await bar() + template good = - proc bar {.async.} = - err(false) - proc foo {.async.} = - await bar() proc main {.async, raises: [MyError].} = await foo() template bad = - proc bar {.async.} = - err(false) - proc foo {.async.} = - await bar() proc main {.async, raises: [].} = await foo() doAssert compiles(good()) @@ -103,17 +103,43 @@ block: doAssert compiles(good()) doAssert not compiles(bad()) -# XXX this should not compile; -# add Future.isInternal field? -when false: - proc foo {.async, raises: [].} = - let f = newFuture[void]() - f.complete() - await f -when false: +block: + template good = + proc foo {.async, raises: [Exception].} = + await newFuture[void]() + template bad = + proc foo {.async, raises: [].} = + await newFuture[void]() + doAssert compiles(good()) + doAssert not compiles(bad()) + +block: proc fut: Future[void] = newFuture[void]() - proc main {.async, raises: [].} = - let f = fut() - f.complete() - await f + + template good = + proc main {.async, raises: [Exception].} = + await fut() + template bad = + proc main {.async, raises: [].} = + await fut() + doAssert compiles(good()) + doAssert not compiles(bad()) + +block: + proc bar() {.async.} = + err(false) + + # XXX We could check all returns are from async procs + # and if so use the inferred proc raises + proc foo(): Future[void] = + bar() + + template good = + proc main {.async, raises: [Exception].} = + await foo() + template bad = + proc main {.async, raises: [MyError].} = + await foo() + doAssert compiles(good()) + doAssert not compiles(bad()) From c06c87dd83f4d2c940255e9b0421a15258f748a4 Mon Sep 17 00:00:00 2001 From: nitely Date: Thu, 16 Jan 2025 05:39:53 -0300 Subject: [PATCH 6/7] wip --- lib/pure/asyncfutures.nim | 5 +++ lib/pure/asyncmacro.nim | 61 ++++++++++++++++----------- tests/async/tasync_error_tracking.nim | 16 +++++-- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index c53c134376584..d1f82a02f97b5 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -41,6 +41,8 @@ type FutureEx*[T, E] = distinct Future[T] + InternalFuture*[T] = ref object of Future[T] + FutureError* = object of Defect cause*: FutureBase @@ -128,6 +130,9 @@ proc newFuture*[T](fromProc: string = "unspecified"): owned(Future[T]) = setupFutureBase(fromProc) when isFutureLoggingEnabled: logFutureStart(result) +proc newInternalFuture*[T](fromProc: string = "unspecified"): owned(InternalFuture[T]) = + setupFutureBase(fromProc) + proc newFutureVar*[T](fromProc = "unspecified"): owned(FutureVar[T]) = ## Create a new `FutureVar`. This Future type is ideally suited for ## situations where you want to avoid unnecessary allocations of Futures. diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 5783bde23071b..d148c2b01366e 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -159,21 +159,15 @@ proc verifyReturnType(typeName: string, node: NimNode = nil) = error("Expected return type of 'Future' got '$1'" % typeName, node) -proc isAsyncPrc0(n: NimNode): bool = - if n.kind == nnkBlockStmt and n[0].strVal == "asynctrack": - return true - if n.kind in RoutineNodes: +proc isAsyncPrc(n: NimNode): bool = + if n.getTypeInst.kind notin {nnkProcTy} + RoutineNodes: return false - for i in 0 .. n.len-1: - if isAsyncPrc0(n[i]): - return true - return false + return repr(n.getTypeInst.params[0][0]) == "InternalFuture" -proc isAsyncPrc(n: NimNode): bool = - for i in 0 .. n.len-1: - if isAsyncPrc0(n[i]): - return true - return false +proc hasRaises(n: NimNode): bool = + if n.getTypeInst.kind notin {nnkProcTy} + RoutineNodes: + return false + return repr(getRaisesList(n)) != "[UncomputedEffects]" macro withRaises[T](f: Future[T], body: untyped): untyped = #echo repr f.kind @@ -186,12 +180,16 @@ macro withRaises[T](f: Future[T], body: untyped): untyped = else: nil of nnkCall: - if f[0].kind == nnkSym: f[0] else: nil + if f[0].kind == nnkSym: + f[0] + elif f[0].kind == nnkDotExpr and f[0][1].kind == nnkSym: + f[0][1] + else: + nil else: nil #echo repr prcSym - #echo repr prcSym.getImpl - if prcSym != nil and isAsyncPrc(prcSym.getImpl): + if prcSym != nil and isAsyncPrc(prcSym) and hasRaises(prcSym): let raisesList = getRaisesList(prcSym) var raisesListTyp = newNimNode(nnkBracket) if raisesList.len > 0: @@ -213,13 +211,10 @@ template await*[T](f: Future[T]): auto {.used.} = when not defined(nimHasTemplateRedefinitionPragma): {.pragma: redefine.} template yieldFuture {.redefine.} = yield FutureBase() - when compiles(yieldFuture): var internalTmpFuture: FutureBase = f yield internalTmpFuture - {.line: instantiationInfo(fullPaths = true).}: - withRaises f: - cast[typeof(f)](internalTmpFuture).read() + (cast[typeof(f)](internalTmpFuture)).read() else: macro errorAsync(futureError: Future[T]) = error( @@ -238,13 +233,24 @@ template await*[T, E](f: FutureEx[T, E]): untyped = else: {.error: "await is only available within {.async.}".} +template await*[T](f: InternalFuture[T]): auto {.used.} = + template yieldFuture = yield FutureBase() + when compiles(yieldFuture): + var internalTmpFuture: FutureBase = f + yield internalTmpFuture + {.line: instantiationInfo(fullPaths = true).}: + withRaises f: + cast[typeof(f)](internalTmpFuture).read() + else: + {.error: "await is only available within {.async.}".} + proc asyncSingleProc(prc: NimNode): NimNode = ## This macro transforms a single procedure into a closure iterator. ## The `async` macro supports a stmtList holding multiple async procedures. if prc.kind == nnkProcTy: result = prc if prc[0][0].kind == nnkEmpty: - result[0][0] = quote do: Future[void] + result[0][0] = quote do: asynced Future[void] return result if prc.kind in RoutineNodes and prc.name.kind != nnkEmpty: @@ -303,6 +309,7 @@ proc asyncSingleProc(prc: NimNode): NimNode = # -> # -> complete(retFutParam, result) var iteratorNameSym = genSym(nskIterator, $prcName & " (Async)") + iteratorNameSym.copyLineInfo(prc) var needsCompletionSym = genSym(nskVar, "needsCompletion") var ctx = Context() var procBody = processBody(ctx, prc.body, needsCompletionSym, retFutParamSym, futureVarIdents) @@ -361,7 +368,7 @@ proc asyncSingleProc(prc: NimNode): NimNode = newVarStmt(retFutureSym, newCall( newNimNode(nnkBracketExpr, prc.body).add( - newIdentNode("newFuture"), + newIdentNode("newInternalFuture"), subRetType), newLit(prcName)))) # Get type from return type of this proc @@ -374,8 +381,9 @@ proc asyncSingleProc(prc: NimNode): NimNode = result = prc # Add discardable pragma. if returnType.kind == nnkEmpty: - # xxx consider removing `owned`? it's inconsistent with non-void case - result.params[0] = quote do: owned(Future[void]) + result.params[0] = quote do: asynced Future[void] + else: + result.params[0] = quote do: asynced `returnType` # based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47 if procBody.kind != nnkEmpty: @@ -461,7 +469,7 @@ macro toFutureEx*(prc: typed): untyped = error("async proc call expected", prc) check prc.kind == nnkCall check prc[0].kind == nnkSym - check isAsyncPrc(prc[0].getImpl) + check isAsyncPrc(prc[0]) let procImpl = getTypeImpl(prc[0]) check procImpl.kind == nnkProcTy let retTyp = procImpl.params[0] @@ -475,3 +483,6 @@ macro toFutureEx*(prc: typed): untyped = exTyp.add r result = quote do: FutureEx[`baseTyp`, `exTyp`](`prc`) + +template asynced*[T](f: typedesc[Future[T]]): untyped = + InternalFuture[T] diff --git a/tests/async/tasync_error_tracking.nim b/tests/async/tasync_error_tracking.nim index 54e5e8cf1867a..4cb5588d77c62 100644 --- a/tests/async/tasync_error_tracking.nim +++ b/tests/async/tasync_error_tracking.nim @@ -45,11 +45,19 @@ block: discard block: - # we cannot tell if fcb is an async proc - # or a closure that returns a user created newFuture() - # that can raise anything type FooBar = object - fcb: proc(): Future[void] {.closure, gcsafe.} + fcb: proc(): synced Future[void] {.closure, gcsafe, raises: [].} + + proc bar {.async.} = + discard + + proc foo {.async, raises: [].} = + var f = FooBar(fcb: bar) + await f.fcb() + +block: + type FooBar = object + fcb: proc(): synced Future[void] {.closure, gcsafe.} proc bar {.async.} = discard From 08d2e293fdd34dc671510c79a4b993aed69eed7d Mon Sep 17 00:00:00 2001 From: nitely Date: Thu, 16 Jan 2025 05:47:04 -0300 Subject: [PATCH 7/7] wip --- tests/async/tasync_error_tracking.nim | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/async/tasync_error_tracking.nim b/tests/async/tasync_error_tracking.nim index 4cb5588d77c62..24296e433298f 100644 --- a/tests/async/tasync_error_tracking.nim +++ b/tests/async/tasync_error_tracking.nim @@ -46,7 +46,7 @@ block: block: type FooBar = object - fcb: proc(): synced Future[void] {.closure, gcsafe, raises: [].} + fcb: proc: asynced Future[void] {.closure, gcsafe, raises: [].} proc bar {.async.} = discard @@ -57,7 +57,7 @@ block: block: type FooBar = object - fcb: proc(): synced Future[void] {.closure, gcsafe.} + fcb: proc: asynced Future[void] {.closure, gcsafe.} proc bar {.async.} = discard @@ -135,12 +135,12 @@ block: doAssert not compiles(bad()) block: - proc bar() {.async.} = + proc bar {.async.} = err(false) # XXX We could check all returns are from async procs # and if so use the inferred proc raises - proc foo(): Future[void] = + proc foo: Future[void] = bar() template good = @@ -151,3 +151,13 @@ block: await foo() doAssert compiles(good()) doAssert not compiles(bad()) + +block: + proc bar {.async.} = + err(false) + + proc foo: asynced Future[void] = + bar() + + proc main {.async, raises: [MyError].} = + await foo()