diff options
-rw-r--r-- | lib/pure/asyncdispatch.nim | 30 | ||||
-rw-r--r-- | lib/pure/asyncmacro.nim | 51 | ||||
-rw-r--r-- | tests/async/tfuturevar.nim | 47 | ||||
-rw-r--r-- | web/news/version_0_15_released.rst | 3 |
4 files changed, 114 insertions, 17 deletions
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index b4e28d9bc..17d0cf86c 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -156,7 +156,6 @@ export Port, SocketFlag ## * Can't await in a ``except`` body ## * Forward declarations for async procs are broken, ## link includes workaround: https://github.com/nim-lang/Nim/issues/3182. -## * FutureVar[T] needs to be completed manually. # TODO: Check if yielded future is nil and throw a more meaningful exception @@ -263,6 +262,18 @@ proc complete*[T](future: FutureVar[T]) = if fut.cb != nil: fut.cb() +proc complete*[T](future: FutureVar[T], val: T) = + ## Completes a ``FutureVar`` with value ``val``. + ## + ## Any previously stored value will be overwritten. + template fut: expr = Future[T](future) + checkFinished(fut) + assert(fut.error == nil) + fut.finished = true + fut.value = val + if fut.cb != nil: + fut.cb() + proc fail*[T](future: Future[T], error: ref Exception) = ## Completes ``future`` with ``error``. #assert(not future.finished, "Future already finished, cannot finish twice.") @@ -311,17 +322,18 @@ proc injectStacktrace[T](future: Future[T]) = msg.add("\n Empty or nil stack trace.") future.error.msg.add(msg) -proc read*[T](future: Future[T]): T = +proc read*[T](future: Future[T] | FutureVar[T]): T = ## Retrieves the value of ``future``. Future must be finished otherwise ## this function will fail with a ``ValueError`` exception. ## ## If the result of the future is an error then that error will be raised. - if future.finished: - if future.error != nil: - injectStacktrace(future) - raise future.error + let fut = Future[T](future) + if fut.finished: + if fut.error != nil: + injectStacktrace(fut) + raise fut.error when T isnot void: - return future.value + return fut.value else: # TODO: Make a custom exception type for this? raise newException(ValueError, "Future still in progress.") @@ -342,11 +354,11 @@ proc mget*[T](future: FutureVar[T]): var T = ## Future has not been finished. result = Future[T](future).value -proc finished*[T](future: Future[T]): bool = +proc finished*[T](future: Future[T] | FutureVar[T]): bool = ## Determines whether ``future`` has completed. ## ## ``True`` may indicate an error or a value. Use ``failed`` to distinguish. - future.finished + (Future[T](future)).finished proc failed*(future: FutureBase): bool = ## Determines whether ``future`` completed with an error. diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index f70afaafa..3d004e84c 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -25,7 +25,7 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} = result = node[0] template createCb(retFutureSym, iteratorNameSym, - name: untyped) = + name, futureVarCompletions: untyped) = var nameIterVar = iteratorNameSym #{.push stackTrace: off.} proc cb {.closure,gcsafe.} = @@ -44,6 +44,8 @@ template createCb(retFutureSym, iteratorNameSym, raise else: retFutureSym.fail(getCurrentException()) + + futureVarCompletions cb() #{.pop.} proc generateExceptionCheck(futSym, @@ -119,8 +121,22 @@ template createVar(result: var NimNode, futSymName: string, result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y useVar(result, futSym, valueReceiver, rootReceiver, fromNode) +proc createFutureVarCompletions(futureVarIdents: seq[NimNode]): NimNode + {.compileTime.} = + result = newStmtList() + # Add calls to complete each FutureVar parameter. + for ident in futureVarIdents: + # Only complete them if they have not been completed already by the user. + result.add newIfStmt( + ( + newCall(newIdentNode("not"), + newDotExpr(ident, newIdentNode("finished"))), + newCall(newIdentNode("complete"), ident) + ) + ) + proc processBody(node, retFutureSym: NimNode, - subTypeIsVoid: bool, + subTypeIsVoid: bool, futureVarIdents: seq[NimNode], tryStmt: NimNode): NimNode {.compileTime.} = #echo(node.treeRepr) result = node @@ -134,11 +150,14 @@ proc processBody(node, retFutureSym: NimNode, else: result.add newCall(newIdentNode("complete"), retFutureSym) else: - let x = node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt) + let x = node[0].processBody(retFutureSym, subTypeIsVoid, + futureVarIdents, tryStmt) if x.kind == nnkYieldStmt: result.add x else: result.add newCall(newIdentNode("complete"), retFutureSym, x) + result.add createFutureVarCompletions(futureVarIdents) + result.add newNimNode(nnkReturnStmt, node).add(newNilLit()) return # Don't process the children of this return stmt of nnkCommand, nnkCall: @@ -196,7 +215,8 @@ proc processBody(node, retFutureSym: NimNode, # Transform ``except`` body. # TODO: Could we perform some ``await`` transformation here to get it # working in ``except``? - tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, nil) + tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, + futureVarIdents, nil) proc processForTry(n: NimNode, i: var int, res: NimNode): bool {.compileTime.} = @@ -207,7 +227,7 @@ proc processBody(node, retFutureSym: NimNode, var skipped = n.skipStmtList() while i < skipped.len: var processed = processBody(skipped[i], retFutureSym, - subTypeIsVoid, n) + subTypeIsVoid, futureVarIdents, n) # Check if we transformed the node into an exception check. # This suggests skipped[i] contains ``await``. @@ -239,7 +259,8 @@ proc processBody(node, retFutureSym: NimNode, else: discard for i in 0 .. <result.len: - result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, nil) + result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, + futureVarIdents, nil) proc getName(node: NimNode): string {.compileTime.} = case node.kind @@ -252,6 +273,14 @@ proc getName(node: NimNode): string {.compileTime.} = else: error("Unknown name.") +proc getFutureVarIdents(params: NimNode): seq[NimNode] {.compileTime.} = + result = @[] + for i in 1 .. <len(params): + expectKind(params[i], nnkIdentDefs) + if params[i][1].kind == nnkBracketExpr and + ($params[i][1][0].ident).normalize == "futurevar": + result.add(params[i][0]) + 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. @@ -282,6 +311,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = let subtypeIsVoid = returnType.kind == nnkEmpty or (baseType.kind == nnkIdent and returnType[1].ident == !"void") + let futureVarIdents = getFutureVarIdents(prc[3]) + var outerProcBody = newNimNode(nnkStmtList, prc[6]) # -> var retFuture = newFuture[T]() @@ -304,7 +335,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = # -> <proc_body> # -> complete(retFuture, result) var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter") - var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil) + var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, + futureVarIdents, nil) # don't do anything with forward bodies (empty) if procBody.kind != nnkEmpty: if not subtypeIsVoid: @@ -326,6 +358,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = # -> complete(retFuture) procBody.add(newCall(newIdentNode("complete"), retFutureSym)) + procBody.add(createFutureVarCompletions(futureVarIdents)) + var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")], procBody, nnkIteratorDef) closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure")) @@ -334,7 +368,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = # -> createCb(retFuture) #var cbName = newIdentNode("cb") var procCb = getAst createCb(retFutureSym, iteratorNameSym, - newStrLitNode(prc[0].getName)) + newStrLitNode(prc[0].getName), + createFutureVarCompletions(futureVarIdents)) outerProcBody.add procCb # -> return retFuture diff --git a/tests/async/tfuturevar.nim b/tests/async/tfuturevar.nim new file mode 100644 index 000000000..73c0fddf7 --- /dev/null +++ b/tests/async/tfuturevar.nim @@ -0,0 +1,47 @@ +import asyncdispatch + +proc completeOnReturn(fut: FutureVar[string], x: bool) {.async.} = + if x: + fut.mget() = "" + fut.mget.add("foobar") + return + +proc completeOnImplicitReturn(fut: FutureVar[string], x: bool) {.async.} = + if x: + fut.mget() = "" + fut.mget.add("foobar") + +proc failureTest(fut: FutureVar[string], x: bool) {.async.} = + if x: + raise newException(Exception, "Test") + +proc manualComplete(fut: FutureVar[string], x: bool) {.async.} = + if x: + fut.mget() = "Hello World" + fut.complete() + return + +proc main() {.async.} = + var fut: FutureVar[string] + + fut = newFutureVar[string]() + await completeOnReturn(fut, true) + doAssert(fut.read() == "foobar") + + fut = newFutureVar[string]() + await completeOnImplicitReturn(fut, true) + doAssert(fut.read() == "foobar") + + fut = newFutureVar[string]() + let retFut = failureTest(fut, true) + yield retFut + doAssert(fut.read().isNil) + doAssert(fut.finished) + + fut = newFutureVar[string]() + await manualComplete(fut, true) + doAssert(fut.read() == "Hello World") + + +waitFor main() + diff --git a/web/news/version_0_15_released.rst b/web/news/version_0_15_released.rst index a5d2e77bd..490b5ca37 100644 --- a/web/news/version_0_15_released.rst +++ b/web/news/version_0_15_released.rst @@ -104,6 +104,9 @@ Library Additions - Added a new macro called ``multisync`` allowing you to write procedures for synchronous and asynchronous sockets with no duplication. +- The ``async`` macro will now complete ``FutureVar[T]`` parameters + automatically unless they have been completed already. + Compiler Additions ------------------ |