Support implicit returns (#401)
* based on https://github.com/nim-lang/Nim/pull/21898 * also fixes generic Future[T] where T ends up being `void`
This commit is contained in:
parent
2c7774d982
commit
157ca4fea5
|
@ -9,46 +9,73 @@
|
|||
|
||||
import std/[macros]
|
||||
|
||||
proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
|
||||
# Skips a nest of StmtList's.
|
||||
if node[0].kind == nnkStmtList:
|
||||
skipUntilStmtList(node[0])
|
||||
# `quote do` will ruin line numbers so we avoid it using these helpers
|
||||
proc completeWithResult(fut, baseType: NimNode): NimNode {.compileTime.} =
|
||||
# when `baseType` is void:
|
||||
# complete(`fut`)
|
||||
# else:
|
||||
# complete(`fut`, result)
|
||||
if baseType.eqIdent("void"):
|
||||
# Shortcut if we know baseType at macro expansion time
|
||||
newCall(ident "complete", fut)
|
||||
else:
|
||||
node
|
||||
# `baseType` might be generic and resolve to `void`
|
||||
nnkWhenStmt.newTree(
|
||||
nnkElifExpr.newTree(
|
||||
nnkInfix.newTree(ident "is", baseType, ident "void"),
|
||||
newCall(ident "complete", fut)
|
||||
),
|
||||
nnkElseExpr.newTree(
|
||||
newCall(ident "complete", fut, ident "result")
|
||||
)
|
||||
)
|
||||
|
||||
proc processBody(node, retFutureSym: NimNode,
|
||||
baseTypeIsVoid: bool): NimNode {.compileTime.} =
|
||||
proc completeWithNode(fut, baseType, node: NimNode): NimNode {.compileTime.} =
|
||||
# when typeof(`node`) is void:
|
||||
# `node` # statement / explicit return
|
||||
# -> completeWithResult(fut, baseType)
|
||||
# else: # expression / implicit return
|
||||
# complete(`fut`, `node`)
|
||||
if node.kind == nnkEmpty: # shortcut when known at macro expanstion time
|
||||
completeWithResult(fut, baseType)
|
||||
else:
|
||||
# Handle both expressions and statements - since the type is not know at
|
||||
# macro expansion time, we delegate this choice to a later compilation stage
|
||||
# with `when`.
|
||||
nnkWhenStmt.newTree(
|
||||
nnkElifExpr.newTree(
|
||||
nnkInfix.newTree(
|
||||
ident "is", nnkTypeOfExpr.newTree(node), ident "void"),
|
||||
newStmtList(
|
||||
node,
|
||||
completeWithResult(fut, baseType)
|
||||
)
|
||||
),
|
||||
nnkElseExpr.newTree(
|
||||
newCall(ident "complete", fut, node)
|
||||
)
|
||||
)
|
||||
|
||||
proc processBody(node, fut, baseType: NimNode): NimNode {.compileTime.} =
|
||||
#echo(node.treeRepr)
|
||||
result = node
|
||||
case node.kind
|
||||
of nnkReturnStmt:
|
||||
result = newNimNode(nnkStmtList, node)
|
||||
let
|
||||
res = newNimNode(nnkStmtList, node)
|
||||
res.add completeWithNode(fut, baseType, processBody(node[0], fut, baseType))
|
||||
res.add newNimNode(nnkReturnStmt, node).add(newNilLit())
|
||||
|
||||
# As I've painfully found out, the order here really DOES matter.
|
||||
if node[0].kind == nnkEmpty:
|
||||
if not baseTypeIsVoid:
|
||||
result.add newCall(newIdentNode("complete"), retFutureSym,
|
||||
newIdentNode("result"))
|
||||
else:
|
||||
result.add newCall(newIdentNode("complete"), retFutureSym)
|
||||
else:
|
||||
let x = node[0].processBody(retFutureSym, baseTypeIsVoid)
|
||||
if x.kind == nnkYieldStmt: result.add x
|
||||
else:
|
||||
result.add newCall(newIdentNode("complete"), retFutureSym, x)
|
||||
|
||||
result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
|
||||
return # Don't process the children of this return stmt
|
||||
res
|
||||
of RoutineNodes-{nnkTemplateDef}:
|
||||
# skip all the nested procedure definitions
|
||||
return node
|
||||
else: discard
|
||||
|
||||
for i in 0 ..< result.len:
|
||||
# We must not transform nested procedures of any form, otherwise
|
||||
# `retFutureSym` will be used for all nested procedures as their own
|
||||
# `retFuture`.
|
||||
result[i] = processBody(result[i], retFutureSym, baseTypeIsVoid)
|
||||
node
|
||||
else:
|
||||
for i in 0 ..< node.len:
|
||||
# We must not transform nested procedures of any form, otherwise
|
||||
# `fut` will be used for all nested procedures as their own
|
||||
# `retFuture`.
|
||||
node[i] = processBody(node[i], fut, baseType)
|
||||
node
|
||||
|
||||
proc getName(node: NimNode): string {.compileTime.} =
|
||||
case node.kind
|
||||
|
@ -63,10 +90,6 @@ proc getName(node: NimNode): string {.compileTime.} =
|
|||
else:
|
||||
error("Unknown name.")
|
||||
|
||||
proc verifyReturnType(typeName: string) {.compileTime.} =
|
||||
if typeName != "Future":
|
||||
error("Expected return type of 'Future' got '" & typeName & "'")
|
||||
|
||||
macro unsupported(s: static[string]): untyped =
|
||||
error s
|
||||
|
||||
|
@ -95,22 +118,25 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||
## The ``async`` macro supports a stmtList holding multiple async procedures.
|
||||
if prc.kind notin {nnkProcTy, nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
|
||||
error("Cannot transform " & $prc.kind & " into an async proc." &
|
||||
" proc/method definition or lambda node expected.")
|
||||
" proc/method definition or lambda node expected.", prc)
|
||||
|
||||
let returnType = cleanupOpenSymChoice(prc.params2[0])
|
||||
|
||||
# Verify that the return type is a Future[T]
|
||||
let baseType =
|
||||
if returnType.kind == nnkBracketExpr:
|
||||
let fut = repr(returnType[0])
|
||||
verifyReturnType(fut)
|
||||
returnType[1]
|
||||
elif returnType.kind == nnkEmpty:
|
||||
ident("void")
|
||||
if returnType.kind == nnkEmpty:
|
||||
ident "void"
|
||||
elif not (
|
||||
returnType.kind == nnkBracketExpr and eqIdent(returnType[0], "Future")):
|
||||
error(
|
||||
"Expected return type of 'Future' got '" & repr(returnType) & "'", prc)
|
||||
return
|
||||
else:
|
||||
raiseAssert("Unhandled async return type: " & $prc.kind)
|
||||
returnType[1]
|
||||
|
||||
let baseTypeIsVoid = baseType.eqIdent("void")
|
||||
let
|
||||
baseTypeIsVoid = baseType.eqIdent("void")
|
||||
futureVoidType = nnkBracketExpr.newTree(ident "Future", ident "void")
|
||||
|
||||
if prc.kind in {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
|
||||
let
|
||||
|
@ -124,63 +150,50 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||
let
|
||||
internalFutureSym = ident "chronosInternalRetFuture"
|
||||
internalFutureType =
|
||||
if baseTypeIsVoid:
|
||||
newNimNode(nnkBracketExpr, prc).add(newIdentNode("Future")).add(newIdentNode("void"))
|
||||
if baseTypeIsVoid: futureVoidType
|
||||
else: returnType
|
||||
castFutureSym = quote do:
|
||||
cast[`internalFutureType`](`internalFutureSym`)
|
||||
procBody = prc.body.processBody(castFutureSym, baseTypeIsVoid)
|
||||
castFutureSym = nnkCast.newTree(internalFutureType, internalFutureSym)
|
||||
|
||||
procBody = prc.body.processBody(castFutureSym, baseType)
|
||||
|
||||
# don't do anything with forward bodies (empty)
|
||||
if procBody.kind != nnkEmpty:
|
||||
# fix #13899, `defer` should not escape its original scope
|
||||
let procBodyBlck =
|
||||
newStmtList(newTree(nnkBlockStmt, newEmptyNode(), procBody))
|
||||
|
||||
# Avoid too much quote do to not lose original line numbers
|
||||
let closureBody = if baseTypeIsVoid:
|
||||
let resultTemplate = quote do:
|
||||
template result: auto {.used.} =
|
||||
{.fatal: "You should not reference the `result` variable inside" &
|
||||
" a void async proc".}
|
||||
# -> complete(chronosInternalRetFuture)
|
||||
let complete =
|
||||
newCall(newIdentNode("complete"), castFutureSym)
|
||||
|
||||
newStmtList(resultTemplate, procBodyBlck, complete)
|
||||
else:
|
||||
# -> iterator nameIter(chronosInternalRetFuture: Future[T]): FutureBase {.closure.} =
|
||||
# -> {.push warning[resultshadowed]: off.}
|
||||
# -> var result: T
|
||||
# -> {.pop.}
|
||||
# -> <proc_body>
|
||||
# -> complete(chronosInternalRetFuture, result)
|
||||
newStmtList(
|
||||
# -> {.push warning[resultshadowed]: off.}
|
||||
newNimNode(nnkPragma).add(newIdentNode("push"),
|
||||
newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
|
||||
newIdentNode("warning"), newIdentNode("resultshadowed")),
|
||||
newIdentNode("off"))),
|
||||
|
||||
# -> var result: T
|
||||
newNimNode(nnkVarSection, prc.body).add(
|
||||
newIdentDefs(newIdentNode("result"), baseType)),
|
||||
|
||||
# -> {.pop.})
|
||||
newNimNode(nnkPragma).add(
|
||||
newIdentNode("pop")),
|
||||
|
||||
procBodyBlck,
|
||||
|
||||
# -> complete(chronosInternalRetFuture, result)
|
||||
newCall(newIdentNode("complete"),
|
||||
castFutureSym, newIdentNode("result")))
|
||||
|
||||
let
|
||||
internalFutureParameter = nnkIdentDefs.newTree(internalFutureSym, newIdentNode("FutureBase"), newEmptyNode())
|
||||
# fix #13899, `defer` should not escape its original scope
|
||||
procBodyBlck = nnkBlockStmt.newTree(newEmptyNode(), procBody)
|
||||
|
||||
resultDecl = nnkWhenStmt.newTree(
|
||||
# when `baseType` is void:
|
||||
nnkElifExpr.newTree(
|
||||
nnkInfix.newTree(ident "is", baseType, ident "void"),
|
||||
quote do:
|
||||
template result: auto {.used.} =
|
||||
{.fatal: "You should not reference the `result` variable inside" &
|
||||
" a void async proc".}
|
||||
),
|
||||
# else:
|
||||
nnkElseExpr.newTree(
|
||||
newStmtList(
|
||||
quote do: {.push warning[resultshadowed]: off.},
|
||||
# var result: `baseType`
|
||||
nnkVarSection.newTree(
|
||||
nnkIdentDefs.newTree(ident "result", baseType, newEmptyNode())),
|
||||
quote do: {.pop.},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
completeDecl = completeWithNode(castFutureSym, baseType, procBodyBlck)
|
||||
|
||||
closureBody = newStmtList(resultDecl, completeDecl)
|
||||
|
||||
internalFutureParameter = nnkIdentDefs.newTree(
|
||||
internalFutureSym, newIdentNode("FutureBase"), newEmptyNode())
|
||||
iteratorNameSym = genSym(nskIterator, $prcName)
|
||||
closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase"), internalFutureParameter],
|
||||
closureBody, nnkIteratorDef)
|
||||
closureIterator = newProc(
|
||||
iteratorNameSym,
|
||||
[newIdentNode("FutureBase"), internalFutureParameter],
|
||||
closureBody, nnkIteratorDef)
|
||||
|
||||
iteratorNameSym.copyLineInfo(prc)
|
||||
|
||||
|
@ -221,6 +234,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||
# doesn't reference it, avoid cyclic ref (#203)
|
||||
let
|
||||
retFutureSym = ident "resultFuture"
|
||||
retFutureSym.copyLineInfo(prc)
|
||||
# Do not change this code to `quote do` version because `instantiationInfo`
|
||||
# will be broken for `newFuture()` call.
|
||||
outerProcBody.add(
|
||||
|
@ -230,7 +244,6 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||
newLit(prcName))
|
||||
)
|
||||
)
|
||||
|
||||
# -> resultFuture.closure = iterator
|
||||
outerProcBody.add(
|
||||
newAssignment(
|
||||
|
@ -266,10 +279,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||
if baseTypeIsVoid:
|
||||
if returnType.kind == nnkEmpty:
|
||||
# Add Future[void]
|
||||
prc.params2[0] =
|
||||
newNimNode(nnkBracketExpr, prc)
|
||||
.add(newIdentNode("Future"))
|
||||
.add(newIdentNode("void"))
|
||||
prc.params2[0] = futureVoidType
|
||||
|
||||
prc
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
import std/[macros, strutils]
|
||||
import unittest2
|
||||
import macros
|
||||
import ../chronos
|
||||
|
||||
{.used.}
|
||||
|
@ -123,6 +123,76 @@ suite "Macro transformations test suite":
|
|||
macroAsync2(testMacro2, seq, Opt, Result, OpenObject, cstring)
|
||||
check waitFor(testMacro2()).len == 0
|
||||
|
||||
test "Future with generics":
|
||||
proc gen(T: typedesc): Future[T] {.async.} =
|
||||
proc testproc(): Future[T] {.async.} =
|
||||
when T is void:
|
||||
return
|
||||
else:
|
||||
return default(T)
|
||||
await testproc()
|
||||
|
||||
waitFor gen(void)
|
||||
check:
|
||||
waitFor(gen(int)) == default(int)
|
||||
|
||||
test "Implicit return":
|
||||
proc implicit(): Future[int] {.async.} =
|
||||
42
|
||||
|
||||
proc implicit2(): Future[int] {.async.} =
|
||||
block:
|
||||
42
|
||||
|
||||
proc implicit3(): Future[int] {.async.} =
|
||||
try:
|
||||
parseInt("error")
|
||||
except ValueError:
|
||||
42
|
||||
|
||||
proc implicit4(v: bool): Future[int] {.async.} =
|
||||
case v
|
||||
of false: 5
|
||||
of true: 42
|
||||
|
||||
proc implicit5(v: bool): Future[int] {.async.} =
|
||||
if v: 42
|
||||
else: 5
|
||||
|
||||
proc implicit6(v: ref int): Future[int] {.async.} =
|
||||
try:
|
||||
parseInt("error")
|
||||
except ValueError:
|
||||
42
|
||||
finally:
|
||||
v[] = 42
|
||||
|
||||
proc implicit7(v: bool): Future[int] {.async.} =
|
||||
case v
|
||||
of false: return 33
|
||||
of true: 42
|
||||
|
||||
proc implicit8(v: bool): Future[int] {.async.} =
|
||||
case v
|
||||
of false: await implicit7(v)
|
||||
of true: 42
|
||||
|
||||
let fin = new int
|
||||
check:
|
||||
waitFor(implicit()) == 42
|
||||
waitFor(implicit2()) == 42
|
||||
waitFor(implicit3()) == 42
|
||||
waitFor(implicit4(true)) == 42
|
||||
waitFor(implicit5(true)) == 42
|
||||
waitFor(implicit5(false)) == 5
|
||||
waitFor(implicit6(fin)) == 42
|
||||
fin[] == 42
|
||||
waitFor(implicit7(true)) == 42
|
||||
waitFor(implicit7(false)) == 33
|
||||
|
||||
waitFor(implicit8(true)) == 42
|
||||
waitFor(implicit8(false)) == 33
|
||||
|
||||
suite "Closure iterator's exception transformation issues":
|
||||
test "Nested defer/finally not called on return":
|
||||
# issue #288
|
||||
|
|
Loading…
Reference in New Issue