simplify asyncfutures, asyncmacro (#17633)

This commit is contained in:
Timothee Cour 2021-04-14 12:22:22 -07:00 • committed by GitHub
commit d6242d7fe1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 62 additions and 92 deletions

View file

@ -189,24 +189,22 @@ proc add(callbacks: var CallbackList, function: CallbackFunc) =
last = last.next last = last.next
last.next = newCallback last.next = newCallback
proc complete*[T](future: Future[T], val: T) = proc completeImpl[T, U](future: Future[T], val: U, isVoid: static bool) =
## Completes `future` with value `val`.
#assert(not future.finished, "Future already finished, cannot finish twice.") #assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future) checkFinished(future)
assert(future.error == nil) assert(future.error == nil)
when not isVoid:
future.value = val future.value = val
future.finished = true future.finished = true
future.callbacks.call() future.callbacks.call()
when isFutureLoggingEnabled: logFutureFinish(future) when isFutureLoggingEnabled: logFutureFinish(future)
proc complete*(future: Future[void]) = proc complete*[T](future: Future[T], val: T) =
## Completes a void `future`. ## Completes `future` with value `val`.
#assert(not future.finished, "Future already finished, cannot finish twice.") completeImpl(future, val, false)
checkFinished(future)
assert(future.error == nil) proc complete*(future: Future[void], val = Future[void].default) =
future.finished = true completeImpl(future, (), true)
future.callbacks.call()
when isFutureLoggingEnabled: logFutureFinish(future)
proc complete*[T](future: FutureVar[T]) = proc complete*[T](future: FutureVar[T]) =
## Completes a `FutureVar`. ## Completes a `FutureVar`.

View file

@ -11,7 +11,6 @@
import macros, strutils, asyncfutures import macros, strutils, asyncfutures
# TODO: Ref https://github.com/nim-lang/Nim/issues/5617 # TODO: Ref https://github.com/nim-lang/Nim/issues/5617
# TODO: Add more line infos # TODO: Add more line infos
proc newCallWithLineInfo(fromNode: NimNode; theProc: NimNode, args: varargs[NimNode]): NimNode = proc newCallWithLineInfo(fromNode: NimNode; theProc: NimNode, args: varargs[NimNode]): NimNode =
@ -65,10 +64,7 @@ proc createFutureVarCompletions(futureVarIdents: seq[NimNode], fromNode: NimNode
) )
) )
proc processBody(node, retFutureSym: NimNode, proc processBody(node, retFutureSym: NimNode, futureVarIdents: seq[NimNode]): NimNode =
subTypeIsVoid: bool,
futureVarIdents: seq[NimNode]): NimNode =
#echo(node.treeRepr)
result = node result = node
case node.kind case node.kind
of nnkReturnStmt: of nnkReturnStmt:
@ -78,14 +74,9 @@ proc processBody(node, retFutureSym: NimNode,
result.add createFutureVarCompletions(futureVarIdents, node) result.add createFutureVarCompletions(futureVarIdents, node)
if node[0].kind == nnkEmpty: if node[0].kind == nnkEmpty:
if not subTypeIsVoid: result.add newCall(newIdentNode("complete"), retFutureSym, newIdentNode("result"))
result.add newCall(newIdentNode("complete"), retFutureSym,
newIdentNode("result"))
else: else:
result.add newCall(newIdentNode("complete"), retFutureSym) let x = node[0].processBody(retFutureSym, futureVarIdents)
else:
let x = node[0].processBody(retFutureSym, subTypeIsVoid,
futureVarIdents)
if x.kind == nnkYieldStmt: result.add x if x.kind == nnkYieldStmt: result.add x
else: else:
result.add newCall(newIdentNode("complete"), retFutureSym, x) result.add newCall(newIdentNode("complete"), retFutureSym, x)
@ -98,8 +89,7 @@ proc processBody(node, retFutureSym: NimNode,
else: discard else: discard
for i in 0 ..< result.len: for i in 0 ..< result.len:
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, result[i] = processBody(result[i], retFutureSym, futureVarIdents)
futureVarIdents)
# echo result.repr # echo result.repr
@ -146,7 +136,7 @@ proc asyncSingleProc(prc: NimNode): NimNode =
if prc.kind == nnkProcTy: if prc.kind == nnkProcTy:
result = prc result = prc
if prc[0][0].kind == nnkEmpty: if prc[0][0].kind == nnkEmpty:
result[0][0] = parseExpr("Future[void]") result[0][0] = quote do: Future[void]
return result return result
if prc.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}: if prc.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
@ -180,12 +170,7 @@ proc asyncSingleProc(prc: NimNode): NimNode =
else: else:
verifyReturnType(repr(returnType), returnType) verifyReturnType(repr(returnType), returnType)
let subtypeIsVoid = returnType.kind == nnkEmpty or
(baseType.kind in {nnkIdent, nnkSym} and
baseType.eqIdent("void"))
let futureVarIdents = getFutureVarIdents(prc.params) let futureVarIdents = getFutureVarIdents(prc.params)
var outerProcBody = newNimNode(nnkStmtList, prc.body) var outerProcBody = newNimNode(nnkStmtList, prc.body)
# Extract the documentation comment from the original procedure declaration. # Extract the documentation comment from the original procedure declaration.
@ -213,42 +198,30 @@ proc asyncSingleProc(prc: NimNode): NimNode =
# -> <proc_body> # -> <proc_body>
# -> complete(retFuture, result) # -> complete(retFuture, result)
var iteratorNameSym = genSym(nskIterator, $prcName & "Iter") var iteratorNameSym = genSym(nskIterator, $prcName & "Iter")
var procBody = prc.body.processBody(retFutureSym, subtypeIsVoid, var procBody = prc.body.processBody(retFutureSym, futureVarIdents)
futureVarIdents)
# don't do anything with forward bodies (empty) # don't do anything with forward bodies (empty)
if procBody.kind != nnkEmpty: if procBody.kind != nnkEmpty:
# fix #13899, defer should not escape its original scope # fix #13899, defer should not escape its original scope
procBody = newStmtList(newTree(nnkBlockStmt, newEmptyNode(), procBody)) procBody = newStmtList(newTree(nnkBlockStmt, newEmptyNode(), procBody))
procBody.add(createFutureVarCompletions(futureVarIdents, nil)) procBody.add(createFutureVarCompletions(futureVarIdents, nil))
let resultIdent = ident"result"
if not subtypeIsVoid: procBody.insert(0): quote do:
procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"), {.push warning[resultshadowed]: off.}
newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add( when `subRetType` isnot void:
newIdentNode("warning"), newIdentNode("resultshadowed")), var `resultIdent`: `subRetType`
newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
procBody.insert(1, newNimNode(nnkVarSection, prc.body).add(
newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
procBody.insert(2, newNimNode(nnkPragma).add(
newIdentNode("pop"))) # -> {.pop.})
procBody.add(
newCall(newIdentNode("complete"),
retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
else: else:
# -> complete(retFuture) var `resultIdent`: Future[void]
procBody.add(newCall(newIdentNode("complete"), retFutureSym)) {.pop.}
procBody.add quote do:
complete(`retFutureSym`, `resultIdent`)
var closureIterator = newProc(iteratorNameSym, [parseExpr("owned(FutureBase)")], var closureIterator = newProc(iteratorNameSym, [quote do: owned(FutureBase)],
procBody, nnkIteratorDef) procBody, nnkIteratorDef)
closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom = prc.body) closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom = prc.body)
closureIterator.addPragma(newIdentNode("closure")) closureIterator.addPragma(newIdentNode("closure"))
# If proc has an explicit gcsafe pragma, we add it to iterator as well. # If proc has an explicit gcsafe pragma, we add it to iterator as well.
if prc.pragma.findChild(it.kind in {nnkSym, nnkIdent} and $it == if prc.pragma.findChild(it.kind in {nnkSym, nnkIdent} and $it == "gcsafe") != nil:
"gcsafe") != nil:
closureIterator.addPragma(newIdentNode("gcsafe")) closureIterator.addPragma(newIdentNode("gcsafe"))
outerProcBody.add(closureIterator) outerProcBody.add(closureIterator)
@ -266,12 +239,10 @@ proc asyncSingleProc(prc: NimNode): NimNode =
outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym) outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym)
result = prc result = prc
if subtypeIsVoid:
# Add discardable pragma. # Add discardable pragma.
if returnType.kind == nnkEmpty: if returnType.kind == nnkEmpty:
# Add Future[void] # xxx consider removing `owned`? it's inconsistent with non-void case
result.params[0] = parseExpr("owned(Future[void])") result.params[0] = quote do: owned(Future[void])
# based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47 # based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
if procBody.kind != nnkEmpty: if procBody.kind != nnkEmpty:
@ -279,10 +250,6 @@ proc asyncSingleProc(prc: NimNode): NimNode =
`outerProcBody` `outerProcBody`
result.body = body2 result.body = body2
#echo(treeRepr(result))
#if prcName == "recvLineInto":
# echo(toStrLit(result))
macro async*(prc: untyped): untyped = macro async*(prc: untyped): untyped =
## Macro which processes async procedures into the appropriate ## Macro which processes async procedures into the appropriate
## iterators and yield statements. ## iterators and yield statements.
@ -352,9 +319,3 @@ 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
# 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

@ -1,6 +1,8 @@
discard """ discard """
output: ''' output: '''
42 42
43
43
1 1
2 2
3 3
@ -8,16 +10,30 @@ discard """
''' '''
""" """
# xxx move to tests/async/tasyncintemplate.nim
import asyncdispatch import asyncdispatch
# bug #16159 block: # bug #16159
template foo() = template foo() =
proc temp(): Future[int] {.async.} = return 42 proc temp(): Future[int] {.async.} = return 42
proc tempVoid(): Future[void] {.async.} = echo await temp() proc tempVoid(): Future[void] {.async.} = echo await temp()
foo() foo()
waitFor tempVoid() waitFor tempVoid()
block: # aliasing `void`
template foo() =
type Foo = void
proc temp(): Future[int] {.async.} = return 43
proc tempVoid(): Future[Foo] {.async.} = echo await temp()
proc tempVoid2() {.async.} = echo await temp()
foo()
waitFor tempVoid()
waitFor tempVoid2()
block: # sanity check
template foo() =
proc bad(): int {.async.} = discard
doAssert not compiles(bad())
block: # bug #16786 block: # bug #16786
block: block:

View file

@ -6,7 +6,8 @@ tgcsafety.nim(30, 18) Error: type mismatch: got <AsyncHttpServer, Port, proc (re
but expected one of: but expected one of:
proc serve(server: AsyncHttpServer; port: Port; proc serve(server: AsyncHttpServer; port: Port;
callback: proc (request: Request): Future[void] {.closure, gcsafe.}; callback: proc (request: Request): Future[void] {.closure, gcsafe.};
address = ""; assumedDescriptorsPerRequest = -1): owned(Future[void]) address = ""; assumedDescriptorsPerRequest = -1): owned(
Future[void])
first type mismatch at position: 3 first type mismatch at position: 3
required type for callback: proc (request: Request): Future[system.void]{.closure, gcsafe.} required type for callback: proc (request: Request): Future[system.void]{.closure, gcsafe.}
but expression 'cb' is of type: proc (req: Request): Future[system.void]{.locks: <unknown>.} but expression 'cb' is of type: proc (req: Request): Future[system.void]{.locks: <unknown>.}

View file

@ -262,6 +262,10 @@ block:
doAssert input.treeRepr & "\n" == expectedRepr doAssert input.treeRepr & "\n" == expectedRepr
return input return input
macro expectedAstRepr(expectedRepr: static[string], input: untyped): untyped =
doAssert input.repr == expectedRepr
return input
const procTypeAst = """ const procTypeAst = """
ProcTy ProcTy
FormalParams FormalParams
@ -280,20 +284,10 @@ ProcTy
static: doAssert Foo is proc(x: int): Future[void] static: doAssert Foo is proc(x: int): Future[void]
const asyncProcTypeAst = """ const asyncProcTypeAst = """
ProcTy proc (s: string): Future[void] {..}"""
FormalParams # using expectedAst would show `OpenSymChoice` for Future[void], which is fragile.
BracketExpr
Ident "Future"
Ident "void"
IdentDefs
Ident "s"
Ident "string"
Empty
Pragma
"""
type type
Bar = proc (s: string) {.async, expectedAst(asyncProcTypeAst).} Bar = proc (s: string) {.async, expectedAstRepr(asyncProcTypeAst).}
static: doAssert Bar is proc(x: string): Future[void] static: doAssert Bar is proc(x: string): Future[void]