Why isn’t `chronosInternalRetFuture` gensymmed?
https://github.com/status-im/nim-chronos/blob/f3b77d86615813690c5ab5e7cbe841521be2a20f/chronos/asyncmacro2.nim#L126-L133
If you take the body of a procedure after it is processed by .async, put it into a new procedure, and apply .async to it, chronosInternalRetFuture will refer to the wrong variable. std/asyncmacro does not suffer from this.
P.S. If you are curious, I’m trying to make my asyncIters library work with Chronos.
Because await is template in chronos and it uses chronosInternalRetFuture.
But you can inject a separate await template for the procedure being transformed so that it will use chronosInternalRetFuture`gensym123456. Since await is already a template, this will not incur additional code bloating.
Do you have an isolated repro fot this?
With ease. Here you are:
import std/macros
when not declared assert:
import std/assertions
when defined useChronos:
import chronos/asyncloop
else:
import std/asyncdispatch
macro wrapWithSubproc(body) =
body.expectKind nnkStmtList
body[0].expectKind nnkStmtList
let n = body[0].len - 1
let ret = body[0][n]
ret.expectKind nnkReturnStmt
body[0].del n # Detach from the body.
result = quote:
proc inner {.async, genSym.} = `body`
await inner()
`ret` # Reattach here.
echo treeRepr result
proc outer: Future[string] {.async.} =
wrapWithSubproc:
return "ok"
doAssert waitFor(outer()) == "ok"
asyncprocesses the body ofouterand replacesreturn "ok"with some code. It consists of two parts: completing the future and exiting from the procedure.- This code is passed to
wrapWithSubproc. The macro splits it into the aforementioned parts. The return part is left where it is, and the future-completion part is injected intoinner. asyncprocesses the body ofinner.
-
When you compile with asyncdispatch, you get the following output:
StmtList ProcDef Ident "inner`gensym6" Empty Empty FormalParams Empty Pragma Ident "async" Ident "genSym" Empty StmtList StmtList StmtList StmtList Call Ident "complete" Sym "retFuture" StrLit "ok" Command OpenSymChoice 2 "await" Call Ident "inner`gensym6" ReturnStmt NilLitNotice the
Sym "retFuture". It refers to aFuture[string]variable declared inouter. When run, the code completes successfully. -
When you compile with chronos, the output is instead:
StmtList ProcDef Ident "inner`gensym1" Empty Empty FormalParams Empty Pragma Ident "async" Ident "genSym" Empty StmtList StmtList StmtList Call Ident "complete" Cast BracketExpr Ident "Future" Ident "string" Ident "chronosInternalRetFuture" StrLit "ok" Command Sym "await" Call Ident "inner`gensym1" ReturnStmt NilLitHere, we see
Ident "chronosInternalRetFuture". But at step 3,asyncwill rewriteinnerand inject anotherchronosInternalRetFutureinto it, which will shadow theouter’s one. Indeed, when you run it, you get aFutureDefect: An attempt was made to complete a Future more than once.
N.B. Of course, in my library, I do not assume that return-related code has any particular shape. I traverse it and search for my own annotations I put there before.