diff --git a/chronos/asyncloop.nim b/chronos/asyncloop.nim index f5cd5415..95227ed2 100644 --- a/chronos/asyncloop.nim +++ b/chronos/asyncloop.nim @@ -221,7 +221,7 @@ const SentinelCallback = AsyncCallback(function: sentinelCallbackImpl, udata: nil) -proc isSentinel(acb: AsyncCallback): bool {.raises: [Defect].} = +proc isSentinel(acb: AsyncCallback): bool = acb == SentinelCallback proc `<`(a, b: TimerCallback): bool = @@ -896,22 +896,22 @@ proc removeTimer*(at: uint64, cb: CallbackFunc, udata: pointer = nil) {. inline, deprecated: "Use removeTimer(Duration, cb, udata)".} = removeTimer(Moment.init(int64(at), Millisecond), cb, udata) -proc callSoon*(acb: AsyncCallback) {.gcsafe, raises: [Defect].} = +proc callSoon*(acb: AsyncCallback) = ## Schedule `cbproc` to be called as soon as possible. ## The callback is called when control returns to the event loop. getThreadDispatcher().callbacks.addLast(acb) proc callSoon*(cbproc: CallbackFunc, data: pointer) {. - gcsafe, raises: [Defect].} = + gcsafe.} = ## Schedule `cbproc` to be called as soon as possible. ## The callback is called when control returns to the event loop. doAssert(not isNil(cbproc)) callSoon(AsyncCallback(function: cbproc, udata: data)) -proc callSoon*(cbproc: CallbackFunc) {.gcsafe, raises: [Defect].} = +proc callSoon*(cbproc: CallbackFunc) = callSoon(cbproc, nil) -proc callIdle*(acb: AsyncCallback) {.gcsafe, raises: [Defect].} = +proc callIdle*(acb: AsyncCallback) = ## Schedule ``cbproc`` to be called when there no pending network events ## available. ## @@ -920,8 +920,7 @@ proc callIdle*(acb: AsyncCallback) {.gcsafe, raises: [Defect].} = ## actually "idle". getThreadDispatcher().idlers.addLast(acb) -proc callIdle*(cbproc: CallbackFunc, data: pointer) {. - gcsafe, raises: [Defect].} = +proc callIdle*(cbproc: CallbackFunc, data: pointer) = ## Schedule ``cbproc`` to be called when there no pending network events ## available. ## @@ -931,7 +930,7 @@ proc callIdle*(cbproc: CallbackFunc, data: pointer) {. doAssert(not isNil(cbproc)) callIdle(AsyncCallback(function: cbproc, udata: data)) -proc callIdle*(cbproc: CallbackFunc) {.gcsafe, raises: [Defect].} = +proc callIdle*(cbproc: CallbackFunc) = callIdle(cbproc, nil) include asyncfutures2 diff --git a/chronos/asyncmacro2.nim b/chronos/asyncmacro2.nim index 22e5f646..5f3370dd 100644 --- a/chronos/asyncmacro2.nim +++ b/chronos/asyncmacro2.nim @@ -88,13 +88,12 @@ proc cleanupOpenSymChoice(node: NimNode): NimNode {.compileTime.} = proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = ## This macro transforms a single procedure into a closure iterator. ## The ``async`` macro supports a stmtList holding multiple async procedures. - if prc.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}: - error("Cannot transform this node kind into an async proc." & + if prc.kind notin {nnkProcTy, nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}: + error("Cannot transform " & $prc.kind & " into an async proc." & " proc/method definition or lambda node expected.") - let prcName = prc.name.getName - - let returnType = cleanupOpenSymChoice(prc.params[0]) + let returnType = + cleanupOpenSymChoice(if prc.kind == nnkProcTy: prc[0][0] else: prc.params[0]) var baseType: NimNode # Verify that the return type is a Future[T] if returnType.kind == nnkBracketExpr: @@ -109,150 +108,165 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = let subtypeIsVoid = returnType.kind == nnkEmpty or (baseType.kind == nnkIdent and returnType[1].eqIdent("void")) - var outerProcBody = newNimNode(nnkStmtList, prc.body) - - # Copy comment for nimdoc - if prc.body.len > 0 and prc.body[0].kind == nnkCommentStmt: - outerProcBody.add(prc.body[0]) - - - # -> iterator nameIter(chronosInternalRetFuture: Future[T]): FutureBase {.closure.} = - # -> {.push warning[resultshadowed]: off.} - # -> var result: T - # -> {.pop.} - # -> - # -> complete(chronosInternalRetFuture, result) - let internalFutureSym = ident "chronosInternalRetFuture" - var iteratorNameSym = genSym(nskIterator, $prcName) - var procBody = prc.body.processBody(internalFutureSym, subtypeIsVoid) - # don't do anything with forward bodies (empty) - if procBody.kind != nnkEmpty: - if subtypeIsVoid: - let resultTemplate = quote do: - template result: auto {.used.} = - {.fatal: "You should not reference the `result` variable inside" & - " a void async proc".} - procBody = newStmtList(resultTemplate, procBody) - - # fix #13899, `defer` should not escape its original scope - procBody = newStmtList(newTree(nnkBlockStmt, newEmptyNode(), procBody)) - - if not subtypeIsVoid: - procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"), - newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add( - newIdentNode("warning"), newIdentNode("resultshadowed")), - newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.} - - procBody.insert(1, newNimNode(nnkVarSection, prc.body).add( - newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T - - procBody.insert(2, newNimNode(nnkPragma).add( - newIdentNode("pop"))) # -> {.pop.}) - - procBody.add( - newCall(newIdentNode("complete"), - internalFutureSym, newIdentNode("result"))) # -> complete(chronosInternalRetFuture, result) - else: - # -> complete(chronosInternalRetFuture) - procBody.add(newCall(newIdentNode("complete"), internalFutureSym)) - + if prc.kind in {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}: let - internalFutureType = - if subtypeIsVoid: - newNimNode(nnkBracketExpr, prc).add(newIdentNode("Future")).add(newIdentNode("void")) - else: returnType - internalFutureParameter = nnkIdentDefs.newTree(internalFutureSym, internalFutureType, newEmptyNode()) - var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase"), internalFutureParameter], - procBody, nnkIteratorDef) - closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom=prc.body) - closureIterator.addPragma(newIdentNode("closure")) - # **Remark 435**: We generate a proc with an inner iterator which call each other - # recursively. The current Nim compiler is not smart enough to infer - # the `gcsafe`-ty aspect of this setup, so we always annotate it explicitly - # with `gcsafe`. This means that the client code is always enforced to be - # `gcsafe`. This is still **safe**, the compiler still checks for `gcsafe`-ty - # regardless, it is only helping the compiler's inference algorithm. See - # https://github.com/nim-lang/RFCs/issues/435 - # for more details. - closureIterator.addPragma(newIdentNode("gcsafe")) + prcName = prc.name.getName + outerProcBody = newNimNode(nnkStmtList, prc.body) - # TODO when push raises is active in a module, the iterator here inherits - # that annotation - here we explicitly disable it again which goes - # against the spirit of the raises annotation - one should investigate - # here the possibility of transporting more specific error types here - # for example by casting exceptions coming out of `await`.. - let raises = nnkBracket.newTree() - raises.add(newIdentNode("CatchableError")) - when not defined(chronosStrictException): - raises.add(newIdentNode("Exception")) - when (NimMajor, NimMinor) < (1, 4): - raises.add(newIdentNode("Defect")) - closureIterator.addPragma(nnkExprColonExpr.newTree( - newIdentNode("raises"), - raises - )) + # Copy comment for nimdoc + if prc.body.len > 0 and prc.body[0].kind == nnkCommentStmt: + outerProcBody.add(prc.body[0]) - # If proc has an explicit gcsafe pragma, we add it to iterator as well. - if prc.pragma.findChild(it.kind in {nnkSym, nnkIdent} and - it.strVal == "gcsafe") != nil: - closureIterator.addPragma(newIdentNode("gcsafe")) - outerProcBody.add(closureIterator) + # -> iterator nameIter(chronosInternalRetFuture: Future[T]): FutureBase {.closure.} = + # -> {.push warning[resultshadowed]: off.} + # -> var result: T + # -> {.pop.} + # -> + # -> complete(chronosInternalRetFuture, result) + let + internalFutureSym = ident "chronosInternalRetFuture" + iteratorNameSym = genSym(nskIterator, $prcName) + var + procBody = prc.body.processBody(internalFutureSym, subtypeIsVoid) - # -> var resultFuture = newFuture[T]() - # declared at the end to be sure that the closure - # doesn't reference it, avoid cyclic ref (#203) - var retFutureSym = ident "resultFuture" - var subRetType = - if returnType.kind == nnkEmpty: - newIdentNode("void") + # don't do anything with forward bodies (empty) + if procBody.kind != nnkEmpty: + if subtypeIsVoid: + let resultTemplate = quote do: + template result: auto {.used.} = + {.fatal: "You should not reference the `result` variable inside" & + " a void async proc".} + procBody = newStmtList(resultTemplate, procBody) + + # fix #13899, `defer` should not escape its original scope + procBody = newStmtList(newTree(nnkBlockStmt, newEmptyNode(), procBody)) + + if not subtypeIsVoid: + procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"), + newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add( + newIdentNode("warning"), newIdentNode("resultshadowed")), + newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.} + + procBody.insert(1, newNimNode(nnkVarSection, prc.body).add( + newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T + + procBody.insert(2, newNimNode(nnkPragma).add( + newIdentNode("pop"))) # -> {.pop.}) + + procBody.add( + newCall(newIdentNode("complete"), + internalFutureSym, newIdentNode("result"))) # -> complete(chronosInternalRetFuture, result) else: - baseType - # Do not change this code to `quote do` version because `instantiationInfo` - # will be broken for `newFuture()` call. - outerProcBody.add( - newVarStmt( - retFutureSym, - newCall(newTree(nnkBracketExpr, ident "newFuture", subRetType), - newLit(prcName)) + # -> complete(chronosInternalRetFuture) + procBody.add(newCall(newIdentNode("complete"), internalFutureSym)) + + let + internalFutureType = + if subtypeIsVoid: + newNimNode(nnkBracketExpr, prc).add(newIdentNode("Future")).add(newIdentNode("void")) + else: returnType + internalFutureParameter = nnkIdentDefs.newTree(internalFutureSym, internalFutureType, newEmptyNode()) + closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase"), internalFutureParameter], + procBody, nnkIteratorDef) + + closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom=prc.body) + closureIterator.addPragma(newIdentNode("closure")) + # **Remark 435**: We generate a proc with an inner iterator which call each other + # recursively. The current Nim compiler is not smart enough to infer + # the `gcsafe`-ty aspect of this setup, so we always annotate it explicitly + # with `gcsafe`. This means that the client code is always enforced to be + # `gcsafe`. This is still **safe**, the compiler still checks for `gcsafe`-ty + # regardless, it is only helping the compiler's inference algorithm. See + # https://github.com/nim-lang/RFCs/issues/435 + # for more details. + closureIterator.addPragma(newIdentNode("gcsafe")) + + # TODO when push raises is active in a module, the iterator here inherits + # that annotation - here we explicitly disable it again which goes + # against the spirit of the raises annotation - one should investigate + # here the possibility of transporting more specific error types here + # for example by casting exceptions coming out of `await`.. + let raises = nnkBracket.newTree() + when not defined(chronosStrictException): + raises.add(newIdentNode("Exception")) + else: + raises.add(newIdentNode("CatchableError")) + when (NimMajor, NimMinor) < (1, 4): + raises.add(newIdentNode("Defect")) + + closureIterator.addPragma(nnkExprColonExpr.newTree( + newIdentNode("raises"), + raises + )) + + # If proc has an explicit gcsafe pragma, we add it to iterator as well. + if prc.pragma.findChild(it.kind in {nnkSym, nnkIdent} and + it.strVal == "gcsafe") != nil: + closureIterator.addPragma(newIdentNode("gcsafe")) + outerProcBody.add(closureIterator) + + # -> var resultFuture = newFuture[T]() + # declared at the end to be sure that the closure + # doesn't reference it, avoid cyclic ref (#203) + var retFutureSym = ident "resultFuture" + var subRetType = + if returnType.kind == nnkEmpty: + newIdentNode("void") + else: + baseType + # Do not change this code to `quote do` version because `instantiationInfo` + # will be broken for `newFuture()` call. + outerProcBody.add( + newVarStmt( + retFutureSym, + newCall(newTree(nnkBracketExpr, ident "newFuture", subRetType), + newLit(prcName)) + ) ) - ) - - # -> resultFuture.closure = iterator - outerProcBody.add( - newAssignment( - newDotExpr(retFutureSym, newIdentNode("closure")), - iteratorNameSym) - ) - # -> futureContinue(resultFuture)) - outerProcBody.add( - newCall(newIdentNode("futureContinue"), retFutureSym) - ) + # -> resultFuture.closure = iterator + outerProcBody.add( + newAssignment( + newDotExpr(retFutureSym, newIdentNode("closure")), + iteratorNameSym) + ) - # -> return resultFuture - outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym) + # -> futureContinue(resultFuture)) + outerProcBody.add( + newCall(newIdentNode("futureContinue"), retFutureSym) + ) - if prc.kind != nnkLambda: # TODO: Nim bug? + # -> return resultFuture + outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym) + + prc.body = outerProcBody + + if prc.kind notin {nnkProcTy, nnkLambda}: # TODO: Nim bug? prc.addPragma(newColonExpr(ident "stackTrace", ident "off")) # See **Remark 435** in this file. # https://github.com/nim-lang/RFCs/issues/435 prc.addPragma(newIdentNode("gcsafe")) - result = prc + + let raises = nnkBracket.newTree() + when (NimMajor, NimMinor) < (1, 4): + raises.add(newIdentNode("Defect")) + prc.addPragma(nnkExprColonExpr.newTree( + newIdentNode("raises"), + raises + )) if subtypeIsVoid: # Add discardable pragma. if returnType.kind == nnkEmpty: # Add Future[void] - result.params[0] = + prc.params[0] = newNimNode(nnkBracketExpr, prc) .add(newIdentNode("Future")) .add(newIdentNode("void")) - if procBody.kind != nnkEmpty: - result.body = outerProcBody + + prc #echo(treeRepr(result)) - #if prcName == "recvLineInto": - # echo(toStrLit(result)) template await*[T](f: Future[T]): untyped = when declared(chronosInternalRetFuture): diff --git a/tests/testmacro.nim b/tests/testmacro.nim index 22c479af..4f79b1d7 100644 --- a/tests/testmacro.nim +++ b/tests/testmacro.nim @@ -11,6 +11,10 @@ import ../chronos when defined(nimHasUsed): {.used.} +type + RetValueType = proc(n: int): Future[int] {.async.} + RetVoidType = proc(n: int): Future[void] {.async.} + proc asyncRetValue(n: int): Future[int] {.async.} = await sleepAsync(n.milliseconds) result = n * 10 @@ -31,6 +35,7 @@ proc asyncRetExceptionVoid(n: int) {.async.} = proc testAwait(): Future[bool] {.async.} = var res: int + await asyncRetVoid(100) res = await asyncRetValue(100) if res != 1000: @@ -50,6 +55,15 @@ proc testAwait(): Future[bool] {.async.} = discard if res != 0: return false + + block: + let fn: RetVoidType = asyncRetVoid + await fn(100) + block: + let fn: RetValueType = asyncRetValue + if (await fn(100)) != 1000: + return false + return true proc testAwaitne(): Future[bool] {.async.} =