Make await a template (#12085)

* Make await a template
* Generate await inside async/multisync
This commit is contained in:
Alexander Ivanov
2020-04-25 21:27:03 +03:00
committed by GitHub
parent caf30e7cb5
commit 56584414f3
5 changed files with 69 additions and 134 deletions

View File

@@ -11,17 +11,6 @@
import macros, strutils, asyncfutures
proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
# Skips a nest of StmtList's.
result = node
if node[0].kind == nnkStmtList:
result = skipUntilStmtList(node[0])
proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
result = node
if node[0].kind == nnkStmtList:
result = node[0]
template createCb(retFutureSym, iteratorNameSym,
strName, identName, futureVarCompletions: untyped) =
bind finished
@@ -58,31 +47,6 @@ template createCb(retFutureSym, iteratorNameSym,
retFutUnown.fail(getCurrentException())
identName()
template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver,
rootReceiver: untyped, fromNode: NimNode) =
## Params:
## futureVarNode: The NimNode which is a symbol identifying the Future[T]
## variable to yield.
## fromNode: Used for better debug information (to give context).
## valueReceiver: The node which defines an expression that retrieves the
## future's value.
##
## rootReceiver: ??? TODO
# -> yield future<x>
result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode)
# -> future<x>.read
valueReceiver = newDotExpr(futureVarNode, newIdentNode("read"))
result.add rootReceiver
template createVar(result: var NimNode, futSymName: string,
asyncProc: NimNode,
valueReceiver, rootReceiver: untyped,
fromNode: NimNode) =
result = newNimNode(nnkStmtList, fromNode)
var futSym = genSym(nskVar, "future")
result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
proc createFutureVarCompletions(futureVarIdents: seq[NimNode],
fromNode: NimNode): NimNode {.compileTime.} =
result = newNimNode(nnkStmtList, fromNode)
@@ -128,51 +92,6 @@ proc processBody(node, retFutureSym: NimNode,
result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
return # Don't process the children of this return stmt
of nnkCommand, nnkCall:
if node[0].eqIdent("await"):
case node[1].kind
of nnkIdent, nnkInfix, nnkDotExpr, nnkCall, nnkCommand:
# await x
# await x or y
# await foo(p, x)
# await foo p, x
var futureValue: NimNode
result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue,
futureValue, node)
else:
error("Invalid node kind in 'await', got: " & $node[1].kind)
elif node.len > 1 and node[1].kind == nnkCommand and
node[1][0].eqIdent("await"):
# foo await x
var newCommand = node
result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1],
newCommand, node)
of nnkVarSection, nnkLetSection:
case node[0][^1].kind
of nnkCommand:
if node[0][^1][0].eqIdent("await"):
# var x = await y
var newVarSection = node # TODO: Should this use copyNimNode?
result.createVar("future" & node[0][0].strVal, node[0][^1][1],
newVarSection[0][^1], newVarSection, node)
else: discard
of nnkAsgn:
case node[1].kind
of nnkCommand:
if node[1][0].eqIdent("await"):
# x = await y
var newAsgn = node
result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1],
newAsgn, node)
else: discard
of nnkDiscardStmt:
# discard await x
if node[0].kind == nnkCommand and
node[0][0].eqIdent("await"):
var newDiscard = node
result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1],
newDiscard[0], newDiscard, node)
of RoutineNodes-{nnkTemplateDef}:
# skip all the nested procedure definitions
return
@@ -182,6 +101,8 @@ proc processBody(node, retFutureSym: NimNode,
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid,
futureVarIdents)
# echo result.repr
proc getName(node: NimNode): string {.compileTime.} =
case node.kind
of nnkPostfix:
@@ -332,8 +253,24 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
if returnType.kind == nnkEmpty:
# Add Future[void]
result.params[0] = parseExpr("owned(Future[void])")
# based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
# however here the overloads are placed inside each expanded async
var awaitDefinition = quote:
template await(f: typed): untyped =
static:
error "await expects Future[T], got " & $typeof(f)
template await[T](f: Future[T]): auto =
var internalTmpFuture: FutureBase = f
yield internalTmpFuture
(cast[type(f)](internalTmpFuture)).read()
if procBody.kind != nnkEmpty:
result.body = outerProcBody
result.body = quote:
`awaitDefinition`
`outerProcBody`
#echo(treeRepr(result))
#if prcName == "recvLineInto":
# echo(toStrLit(result))
@@ -350,50 +287,6 @@ macro async*(prc: untyped): untyped =
when defined(nimDumpAsync):
echo repr result
# Multisync
proc emptyNoop[T](x: T): T =
# The ``await``s are replaced by a call to this for simplicity.
when T isnot void:
return x
proc stripAwait(node: NimNode): NimNode =
## Strips out all ``await`` commands from a procedure body, replaces them
## with ``emptyNoop`` for simplicity.
result = node
let emptyNoopSym = bindSym("emptyNoop")
case node.kind
of nnkCommand, nnkCall:
if node[0].eqIdent("await"):
node[0] = emptyNoopSym
elif node.len > 1 and node[1].kind == nnkCommand and node[1][0].eqIdent("await"):
# foo await x
node[1][0] = emptyNoopSym
of nnkVarSection, nnkLetSection:
case node[0][^1].kind
of nnkCommand:
if node[0][^1][0].eqIdent("await"):
# var x = await y
node[0][^1][0] = emptyNoopSym
else: discard
of nnkAsgn:
case node[1].kind
of nnkCommand:
if node[1][0].eqIdent("await"):
# x = await y
node[1][0] = emptyNoopSym
else: discard
of nnkDiscardStmt:
# discard await x
if node[0].kind == nnkCommand and node[0][0].eqIdent("await"):
node[0][0] = emptyNoopSym
else: discard
for i in 0 ..< result.len:
result[i] = stripAwait(result[i])
proc splitParamType(paramType: NimNode, async: bool): NimNode =
result = paramType
if paramType.kind == nnkInfix and paramType[0].strVal in ["|", "or"]:
@@ -426,8 +319,12 @@ proc splitProc(prc: NimNode): (NimNode, NimNode) =
for i in 1 ..< result[0][3].len:
# Sync proc (0) -> FormalParams (3) -> IdentDefs, the parameter (i) ->
# parameter type (1).
result[0][3][i][1] = splitParamType(result[0][3][i][1], async = false)
result[0][6] = stripAwait(result[0][6])
result[0][3][i][1] = splitParamType(result[0][3][i][1], async=false)
var multisyncAwait = quote:
template await(value: typed): untyped =
value
result[0][^1] = nnkStmtList.newTree(multisyncAwait, result[0][^1])
result[1] = prc.copyNimTree()
if result[1][3][0].kind == nnkBracketExpr:
@@ -447,8 +344,9 @@ macro multisync*(prc: untyped): untyped =
result = newStmtList()
result.add(asyncSingleProc(asyncPrc))
result.add(sync)
# echo result.repr
proc await*[T](x: T) =
## The 'await' keyword is also defined here for technical
## reasons. (Generic symbol lookup prepass.)
{.error: "Await only available within .async".}
# overload for await as a fallback handler, based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
# template await*(f: typed): untyped =
# static:
# error "await only available within {.async.}"

View File

@@ -53,4 +53,13 @@ block: # nkCheckedFieldExpr
waitFor foo()
block: # 12743
template templ = await sleepAsync 0
proc prc {.async.} = templ
waitFor prc()
echo "ok"

View File

@@ -0,0 +1,15 @@
discard """
errormsg: "undeclared identifier: 'await'"
cmd: "nim c $file"
file: "tasync_noasync.nim"
"""
import async
proc a {.async.} =
discard
await a()
# if we overload a fallback handler to get
# await only available within {.async.}
# we would need `{.dirty.}` templates for await

View File

@@ -0,0 +1,11 @@
discard """
errormsg: "await expects Future[T], got int"
cmd: "nim c $file"
file: "asyncmacro.nim"
"""
import async
proc a {.async.} =
await 0
waitFor a()

View File

@@ -82,7 +82,7 @@ Async traceback:
asyncmacro\.nim\(\d+?\)\s+?a
asyncmacro\.nim\(\d+?\)\s+?aNimAsyncContinue
## Resumes an async procedure
tasync_traceback\.nim\(\d+?\)\s+?aIter
asyncmacro\.nim\(\d+?\)\s+?aIter
asyncfutures\.nim\(\d+?\)\s+?read
\]#
Exception message: b failure
@@ -110,13 +110,15 @@ Async traceback:
## Executes pending callbacks
asyncmacro\.nim\(\d+?\)\s+?fooNimAsyncContinue
## Resumes an async procedure
tasync_traceback\.nim\(\d+?\)\s+?fooIter
asyncmacro\.nim\(\d+?\)\s+?fooIter
asyncfutures\.nim\(\d+?\)\s+?read
\]#
Exception message: bar failure
Exception type:
"""
# TODO: is asyncmacro good enough location for fooIter traceback/debugging? just put the callsite info for all?
let resLines = splitLines(result.strip)
let expLines = splitLines(expected.strip)