mirror of
https://github.com/status-im/nim-chronos.git
synced 2025-01-20 16:29:12 +00:00
async
proc
types (#346)
Allow `type = proc(...) {.async.}` for pre-declaring async callback types - in this case, the macro applies the correct transformation to the return type and adds the appropriate exception / gcsafe annotations
This commit is contained in:
parent
945c304197
commit
c65cc4c136
@ -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
|
||||
|
@ -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.}
|
||||
# -> <proc_body>
|
||||
# -> 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.}
|
||||
# -> <proc_body>
|
||||
# -> 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):
|
||||
|
@ -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.} =
|
||||
|
Loading…
x
Reference in New Issue
Block a user