-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make await a template #12085
Make await a template #12085
Changes from all commits
48c7751
4ec022e
b48bdc0
da2c09b
8b016d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]) | ||
Comment on lines
+322
to
+327
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love it :D There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. credit to @zah's idea #11911 (comment) i think i finally remembered this is also possible |
||
|
||
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.}" | ||
Comment on lines
+349
to
+352
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You still need this, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, you removed this because you didn't want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, but it can be a bit surprising: we need to somehow hint here in the error "if you use a template, you might want to use on the other hand just a type mismatch/undefined error is very natural, |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This actually reduces the amount of signal we get from the async tracebacks. I cannot recall whether this particular line provided useful information, but if it does then that could be a blocker for these changes. Can you run this test and show us what the tracebacks are before and after your changes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC the change is similar to what is seen in the diff, basically we need to add the linedir of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to see what line the traceback points to now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. which seems to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That should be okay, I'm assuming that |
||
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) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove this comment, at this point this really doesn't have many parallels. I doubt it'll be useful for those exploring this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, I still got the "define await as a template" overall approach from there, but I am not sure what's the best way to document that stuff