status-im / nim-chronos

Chronos - An efficient library for asynchronous programming
https://status-im.github.io/nim-chronos/docs/chronos
Apache License 2.0
353 stars 51 forks source link

Why isn’t `chronosInternalRetFuture` gensymmed? #368

Open SirNickolas opened 1 year ago

SirNickolas commented 1 year ago

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.

cheatfate commented 1 year ago

Because await is template in chronos and it uses chronosInternalRetFuture.

SirNickolas commented 1 year ago

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.

arnetheduck commented 11 months ago

Do you have an isolated repro fot this?

SirNickolas commented 11 months ago

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"
  1. async processes the body of outer and replaces return "ok" with some code. It consists of two parts: completing the future and exiting from the procedure.
  2. 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 into inner.
  3. async processes the body of inner.

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.