summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/asyncdispatch.nim30
-rw-r--r--lib/pure/asyncmacro.nim51
-rw-r--r--tests/async/tfuturevar.nim47
-rw-r--r--web/news/version_0_15_released.rst3
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
 ------------------