Make await a template (#12085)
* Make await a template * Generate await inside async/multisync
This commit is contained in:
parent
caf30e7cb5
commit
56584414f3
5 changed files with 69 additions and 134 deletions
|
|
@ -11,17 +11,6 @@
|
||||||
|
|
||||||
import macros, strutils, asyncfutures
|
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,
|
template createCb(retFutureSym, iteratorNameSym,
|
||||||
strName, identName, futureVarCompletions: untyped) =
|
strName, identName, futureVarCompletions: untyped) =
|
||||||
bind finished
|
bind finished
|
||||||
|
|
@ -58,31 +47,6 @@ template createCb(retFutureSym, iteratorNameSym,
|
||||||
retFutUnown.fail(getCurrentException())
|
retFutUnown.fail(getCurrentException())
|
||||||
identName()
|
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],
|
proc createFutureVarCompletions(futureVarIdents: seq[NimNode],
|
||||||
fromNode: NimNode): NimNode {.compileTime.} =
|
fromNode: NimNode): NimNode {.compileTime.} =
|
||||||
result = newNimNode(nnkStmtList, fromNode)
|
result = newNimNode(nnkStmtList, fromNode)
|
||||||
|
|
@ -128,51 +92,6 @@ proc processBody(node, retFutureSym: NimNode,
|
||||||
|
|
||||||
result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
|
result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
|
||||||
return # Don't process the children of this return stmt
|
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}:
|
of RoutineNodes-{nnkTemplateDef}:
|
||||||
# skip all the nested procedure definitions
|
# skip all the nested procedure definitions
|
||||||
return
|
return
|
||||||
|
|
@ -182,6 +101,8 @@ proc processBody(node, retFutureSym: NimNode,
|
||||||
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid,
|
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid,
|
||||||
futureVarIdents)
|
futureVarIdents)
|
||||||
|
|
||||||
|
# echo result.repr
|
||||||
|
|
||||||
proc getName(node: NimNode): string {.compileTime.} =
|
proc getName(node: NimNode): string {.compileTime.} =
|
||||||
case node.kind
|
case node.kind
|
||||||
of nnkPostfix:
|
of nnkPostfix:
|
||||||
|
|
@ -332,8 +253,24 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
if returnType.kind == nnkEmpty:
|
if returnType.kind == nnkEmpty:
|
||||||
# Add Future[void]
|
# Add Future[void]
|
||||||
result.params[0] = parseExpr("owned(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:
|
if procBody.kind != nnkEmpty:
|
||||||
result.body = outerProcBody
|
result.body = quote:
|
||||||
|
`awaitDefinition`
|
||||||
|
`outerProcBody`
|
||||||
|
|
||||||
#echo(treeRepr(result))
|
#echo(treeRepr(result))
|
||||||
#if prcName == "recvLineInto":
|
#if prcName == "recvLineInto":
|
||||||
# echo(toStrLit(result))
|
# echo(toStrLit(result))
|
||||||
|
|
@ -350,50 +287,6 @@ macro async*(prc: untyped): untyped =
|
||||||
when defined(nimDumpAsync):
|
when defined(nimDumpAsync):
|
||||||
echo repr result
|
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 =
|
proc splitParamType(paramType: NimNode, async: bool): NimNode =
|
||||||
result = paramType
|
result = paramType
|
||||||
if paramType.kind == nnkInfix and paramType[0].strVal in ["|", "or"]:
|
if paramType.kind == nnkInfix and paramType[0].strVal in ["|", "or"]:
|
||||||
|
|
@ -427,7 +320,11 @@ proc splitProc(prc: NimNode): (NimNode, NimNode) =
|
||||||
# Sync proc (0) -> FormalParams (3) -> IdentDefs, the parameter (i) ->
|
# Sync proc (0) -> FormalParams (3) -> IdentDefs, the parameter (i) ->
|
||||||
# parameter type (1).
|
# parameter type (1).
|
||||||
result[0][3][i][1] = splitParamType(result[0][3][i][1], async=false)
|
result[0][3][i][1] = splitParamType(result[0][3][i][1], async=false)
|
||||||
result[0][6] = stripAwait(result[0][6])
|
var multisyncAwait = quote:
|
||||||
|
template await(value: typed): untyped =
|
||||||
|
value
|
||||||
|
|
||||||
|
result[0][^1] = nnkStmtList.newTree(multisyncAwait, result[0][^1])
|
||||||
|
|
||||||
result[1] = prc.copyNimTree()
|
result[1] = prc.copyNimTree()
|
||||||
if result[1][3][0].kind == nnkBracketExpr:
|
if result[1][3][0].kind == nnkBracketExpr:
|
||||||
|
|
@ -447,8 +344,9 @@ macro multisync*(prc: untyped): untyped =
|
||||||
result = newStmtList()
|
result = newStmtList()
|
||||||
result.add(asyncSingleProc(asyncPrc))
|
result.add(asyncSingleProc(asyncPrc))
|
||||||
result.add(sync)
|
result.add(sync)
|
||||||
|
# echo result.repr
|
||||||
|
|
||||||
proc await*[T](x: T) =
|
# overload for await as a fallback handler, based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
|
||||||
## The 'await' keyword is also defined here for technical
|
# template await*(f: typed): untyped =
|
||||||
## reasons. (Generic symbol lookup prepass.)
|
# static:
|
||||||
{.error: "Await only available within .async".}
|
# error "await only available within {.async.}"
|
||||||
|
|
@ -53,4 +53,13 @@ block: # nkCheckedFieldExpr
|
||||||
|
|
||||||
waitFor foo()
|
waitFor foo()
|
||||||
|
|
||||||
|
block: # 12743
|
||||||
|
|
||||||
|
template templ = await sleepAsync 0
|
||||||
|
|
||||||
|
proc prc {.async.} = templ
|
||||||
|
|
||||||
|
waitFor prc()
|
||||||
|
|
||||||
|
|
||||||
echo "ok"
|
echo "ok"
|
||||||
|
|
|
||||||
15
tests/async/tasync_noasync.nim
Normal file
15
tests/async/tasync_noasync.nim
Normal 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
|
||||||
11
tests/async/tasync_nofuture.nim
Normal file
11
tests/async/tasync_nofuture.nim
Normal 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()
|
||||||
|
|
@ -82,7 +82,7 @@ Async traceback:
|
||||||
asyncmacro\.nim\(\d+?\)\s+?a
|
asyncmacro\.nim\(\d+?\)\s+?a
|
||||||
asyncmacro\.nim\(\d+?\)\s+?aNimAsyncContinue
|
asyncmacro\.nim\(\d+?\)\s+?aNimAsyncContinue
|
||||||
## Resumes an async procedure
|
## Resumes an async procedure
|
||||||
tasync_traceback\.nim\(\d+?\)\s+?aIter
|
asyncmacro\.nim\(\d+?\)\s+?aIter
|
||||||
asyncfutures\.nim\(\d+?\)\s+?read
|
asyncfutures\.nim\(\d+?\)\s+?read
|
||||||
\]#
|
\]#
|
||||||
Exception message: b failure
|
Exception message: b failure
|
||||||
|
|
@ -110,13 +110,15 @@ Async traceback:
|
||||||
## Executes pending callbacks
|
## Executes pending callbacks
|
||||||
asyncmacro\.nim\(\d+?\)\s+?fooNimAsyncContinue
|
asyncmacro\.nim\(\d+?\)\s+?fooNimAsyncContinue
|
||||||
## Resumes an async procedure
|
## Resumes an async procedure
|
||||||
tasync_traceback\.nim\(\d+?\)\s+?fooIter
|
asyncmacro\.nim\(\d+?\)\s+?fooIter
|
||||||
asyncfutures\.nim\(\d+?\)\s+?read
|
asyncfutures\.nim\(\d+?\)\s+?read
|
||||||
\]#
|
\]#
|
||||||
Exception message: bar failure
|
Exception message: bar failure
|
||||||
Exception type:
|
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 resLines = splitLines(result.strip)
|
||||||
let expLines = splitLines(expected.strip)
|
let expLines = splitLines(expected.strip)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue