asyncmacro.nim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Dominik Picheta
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Implements the `async` and `multisync` macros for `asyncdispatch`.
  10. import std/[macros, strutils, asyncfutures]
  11. type
  12. Context = ref object
  13. inTry: int
  14. hasRet: bool
  15. # TODO: Ref https://github.com/nim-lang/Nim/issues/5617
  16. # TODO: Add more line infos
  17. proc newCallWithLineInfo(fromNode: NimNode; theProc: NimNode, args: varargs[NimNode]): NimNode =
  18. result = newCall(theProc, args)
  19. result.copyLineInfo(fromNode)
  20. type
  21. ClosureIt[T] = iterator(f: Future[T]): owned(FutureBase)
  22. template createCb(futTyp, strName, identName, futureVarCompletions: untyped) =
  23. bind finished
  24. {.push stackTrace: off.}
  25. proc identName(fut: Future[futTyp], it: ClosureIt[futTyp]) {.effectsOf: it.} =
  26. try:
  27. if not it.finished:
  28. var next = it(fut)
  29. # Continue while the yielded future is already finished.
  30. while (not next.isNil) and next.finished:
  31. next = it(fut)
  32. if it.finished:
  33. break
  34. if next == nil:
  35. if not fut.finished:
  36. let msg = "Async procedure ($1) yielded `nil`, are you await'ing a `nil` Future?"
  37. raise newException(AssertionDefect, msg % strName)
  38. else:
  39. {.gcsafe.}:
  40. next.addCallback(cast[proc() {.closure, gcsafe.}](proc =
  41. identName(fut, it)))
  42. except:
  43. futureVarCompletions
  44. if fut.finished:
  45. # Take a look at tasyncexceptions for the bug which this fixes.
  46. # That test explains it better than I can here.
  47. raise
  48. else:
  49. fut.fail(getCurrentException())
  50. {.pop.}
  51. proc createFutureVarCompletions(futureVarIdents: seq[NimNode], fromNode: NimNode): NimNode =
  52. result = newNimNode(nnkStmtList, fromNode)
  53. # Add calls to complete each FutureVar parameter.
  54. for ident in futureVarIdents:
  55. # Only complete them if they have not been completed already by the user.
  56. # In the meantime, this was really useful for debugging :)
  57. #result.add(newCall(newIdentNode("echo"), newStrLitNode(fromNode.lineinfo)))
  58. result.add newIfStmt(
  59. (
  60. newCall(newIdentNode("not"),
  61. newDotExpr(ident, newIdentNode("finished"))),
  62. newCallWithLineInfo(fromNode, newIdentNode("complete"), ident)
  63. )
  64. )
  65. proc processBody(ctx: Context; node, needsCompletionSym, retFutParamSym: NimNode, futureVarIdents: seq[NimNode]): NimNode =
  66. result = node
  67. case node.kind
  68. of nnkReturnStmt:
  69. result = newNimNode(nnkStmtList, node)
  70. # As I've painfully found out, the order here really DOES matter.
  71. result.add createFutureVarCompletions(futureVarIdents, node)
  72. ctx.hasRet = true
  73. if node[0].kind == nnkEmpty:
  74. if ctx.inTry == 0:
  75. result.add newCallWithLineInfo(node, newIdentNode("complete"), retFutParamSym, newIdentNode("result"))
  76. else:
  77. result.add newAssignment(needsCompletionSym, newLit(true))
  78. else:
  79. let x = processBody(ctx, node[0], needsCompletionSym, retFutParamSym, futureVarIdents)
  80. if x.kind == nnkYieldStmt: result.add x
  81. elif ctx.inTry == 0:
  82. result.add newCallWithLineInfo(node, newIdentNode("complete"), retFutParamSym, x)
  83. else:
  84. result.add newAssignment(newIdentNode("result"), x)
  85. result.add newAssignment(needsCompletionSym, newLit(true))
  86. result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
  87. return # Don't process the children of this return stmt
  88. of RoutineNodes-{nnkTemplateDef}:
  89. # skip all the nested procedure definitions
  90. return
  91. of nnkTryStmt:
  92. if result[^1].kind == nnkFinally:
  93. inc ctx.inTry
  94. result[0] = processBody(ctx, result[0], needsCompletionSym, retFutParamSym, futureVarIdents)
  95. dec ctx.inTry
  96. for i in 1 ..< result.len:
  97. result[i] = processBody(ctx, result[i], needsCompletionSym, retFutParamSym, futureVarIdents)
  98. if ctx.inTry == 0 and ctx.hasRet:
  99. let finallyNode = copyNimNode(result[^1])
  100. let stmtNode = newNimNode(nnkStmtList)
  101. for child in result[^1]:
  102. stmtNode.add child
  103. stmtNode.add newIfStmt(
  104. ( needsCompletionSym,
  105. newCallWithLineInfo(node, newIdentNode("complete"), retFutParamSym,
  106. newIdentNode("result")
  107. )
  108. )
  109. )
  110. finallyNode.add stmtNode
  111. result[^1] = finallyNode
  112. else:
  113. for i in 0 ..< result.len:
  114. result[i] = processBody(ctx, result[i], needsCompletionSym, retFutParamSym, futureVarIdents)
  115. else:
  116. for i in 0 ..< result.len:
  117. result[i] = processBody(ctx, result[i], needsCompletionSym, retFutParamSym, futureVarIdents)
  118. # echo result.repr
  119. proc getName(node: NimNode): string =
  120. case node.kind
  121. of nnkPostfix:
  122. return node[1].strVal
  123. of nnkIdent, nnkSym:
  124. return node.strVal
  125. of nnkEmpty:
  126. return "anonymous"
  127. else:
  128. error("Unknown name.", node)
  129. proc getFutureVarIdents(params: NimNode): seq[NimNode] =
  130. result = @[]
  131. for i in 1 ..< len(params):
  132. expectKind(params[i], nnkIdentDefs)
  133. if params[i][1].kind == nnkBracketExpr and
  134. params[i][1][0].eqIdent(FutureVar.astToStr):
  135. ## eqIdent: first char is case sensitive!!!
  136. result.add(params[i][0])
  137. proc isInvalidReturnType(typeName: string): bool =
  138. return typeName notin ["Future"] #, "FutureStream"]
  139. proc verifyReturnType(typeName: string, node: NimNode = nil) =
  140. if typeName.isInvalidReturnType:
  141. error("Expected return type of 'Future' got '$1'" %
  142. typeName, node)
  143. template await*(f: typed): untyped {.used.} =
  144. static:
  145. error "await expects Future[T], got " & $typeof(f)
  146. template await*[T](f: Future[T]): auto {.used.} =
  147. when not defined(nimHasTemplateRedefinitionPragma):
  148. {.pragma: redefine.}
  149. template yieldFuture {.redefine.} = yield FutureBase()
  150. when compiles(yieldFuture):
  151. var internalTmpFuture: FutureBase = f
  152. yield internalTmpFuture
  153. {.line: instantiationInfo(fullPaths = true).}:
  154. (cast[typeof(f)](internalTmpFuture)).read()
  155. else:
  156. macro errorAsync(futureError: Future[T]) =
  157. error(
  158. "Can only 'await' inside a proc marked as 'async'. Use " &
  159. "'waitFor' when calling an 'async' proc in a non-async scope instead",
  160. futureError)
  161. errorAsync(f)
  162. proc asyncSingleProc(prc: NimNode): NimNode =
  163. ## This macro transforms a single procedure into a closure iterator.
  164. ## The `async` macro supports a stmtList holding multiple async procedures.
  165. if prc.kind == nnkProcTy:
  166. result = prc
  167. if prc[0][0].kind == nnkEmpty:
  168. result[0][0] = quote do: Future[void]
  169. return result
  170. if prc.kind in RoutineNodes and prc.name.kind != nnkEmpty:
  171. # Only non anonymous functions need/can have stack trace disabled
  172. prc.addPragma(nnkExprColonExpr.newTree(ident"stackTrace", ident"off"))
  173. if prc.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
  174. error("Cannot transform this node kind into an async proc." &
  175. " proc/method definition or lambda node expected.", prc)
  176. if prc[4].kind != nnkEmpty:
  177. for prag in prc[4]:
  178. if prag.eqIdent("discardable"):
  179. error("Cannot make async proc discardable. Futures have to be " &
  180. "checked with `asyncCheck` instead of discarded", prag)
  181. let prcName = prc.name.getName
  182. var returnType = prc.params[0]
  183. var baseType: NimNode
  184. if returnType.kind in nnkCallKinds and returnType[0].eqIdent("owned") and
  185. returnType.len == 2:
  186. returnType = returnType[1]
  187. # Verify that the return type is a Future[T]
  188. if returnType.kind == nnkBracketExpr:
  189. let fut = repr(returnType[0])
  190. verifyReturnType(fut, returnType[0])
  191. baseType = returnType[1]
  192. elif returnType.kind in nnkCallKinds and returnType[0].eqIdent("[]"):
  193. let fut = repr(returnType[1])
  194. verifyReturnType(fut, returnType[0])
  195. baseType = returnType[2]
  196. elif returnType.kind == nnkEmpty:
  197. baseType = returnType
  198. else:
  199. baseType = nil
  200. verifyReturnType(repr(returnType), returnType)
  201. let futureVarIdents = getFutureVarIdents(prc.params)
  202. var outerProcBody = newNimNode(nnkStmtList, prc.body)
  203. # Extract the documentation comment from the original procedure declaration.
  204. # Note that we're not removing it from the body in order not to make this
  205. # transformation even more complex.
  206. let body2 = extractDocCommentsAndRunnables(prc.body)
  207. var subRetType =
  208. if returnType.kind == nnkEmpty: newIdentNode("void")
  209. else: baseType
  210. let retFutParamSym = genSym(nskParam, "retFutParamSym")
  211. # -> iterator nameIter(retFutParam: Future[T]): FutureBase {.closure.} =
  212. # -> {.push warning[resultshadowed]: off.}
  213. # -> var result: T
  214. # -> {.pop.}
  215. # -> <proc_body>
  216. # -> complete(retFutParam, result)
  217. var iteratorNameSym = genSym(nskIterator, $prcName & " (Async)")
  218. var needsCompletionSym = genSym(nskVar, "needsCompletion")
  219. var ctx = Context()
  220. var procBody = processBody(ctx, prc.body, needsCompletionSym, retFutParamSym, futureVarIdents)
  221. # don't do anything with forward bodies (empty)
  222. if procBody.kind != nnkEmpty:
  223. # fix #13899, defer should not escape its original scope
  224. let blockStmt = newStmtList(newTree(nnkBlockStmt, newEmptyNode(), procBody))
  225. procBody = newStmtList()
  226. let resultIdent = ident"result"
  227. procBody.add quote do:
  228. # Check whether there is an implicit return
  229. when typeof(`blockStmt`) is void:
  230. `blockStmt`
  231. else:
  232. `resultIdent` = `blockStmt`
  233. procBody.add(createFutureVarCompletions(futureVarIdents, nil))
  234. procBody.insert(0): quote do:
  235. {.push warning[resultshadowed]: off.}
  236. when `subRetType` isnot void:
  237. var `resultIdent`: `subRetType` = default(`subRetType`)
  238. else:
  239. var `resultIdent`: Future[void] = nil
  240. {.pop.}
  241. var `needsCompletionSym` = false
  242. procBody.add quote do:
  243. complete(`retFutParamSym`, `resultIdent`)
  244. var retFutureTyp = newNimNode(nnkBracketExpr, prc).add(newIdentNode("Future")).add(subRetType)
  245. var retFutureParam = newNimNode(nnkIdentDefs, prc).add(retFutParamSym).add(retFutureTyp).add(newEmptyNode())
  246. var closureIterator = newProc(iteratorNameSym, [quote do: owned(FutureBase), retFutureParam],
  247. procBody, nnkIteratorDef)
  248. closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom = prc.body)
  249. closureIterator.addPragma(newIdentNode("closure"))
  250. # If proc has an explicit gcsafe pragma, we add it to iterator as well.
  251. if prc.pragma.findChild(it.kind in {nnkSym, nnkIdent} and $it == "gcsafe") != nil:
  252. closureIterator.addPragma(newIdentNode("gcsafe"))
  253. outerProcBody.add(closureIterator)
  254. # -> createCb()
  255. # NOTE: The NimAsyncContinueSuffix is checked for in asyncfutures.nim to produce
  256. # friendlier stack traces:
  257. var cbName = genSym(nskProc, prcName & NimAsyncContinueSuffix)
  258. var procCb = getAst createCb(
  259. subRetType,
  260. newStrLitNode(prcName),
  261. cbName,
  262. createFutureVarCompletions(futureVarIdents, nil)
  263. )
  264. outerProcBody.add procCb
  265. # -> var retFuture = newFuture[T]()
  266. let retFutureSym = genSym(nskVar, "retFuture")
  267. outerProcBody.add(
  268. newVarStmt(retFutureSym,
  269. newCall(
  270. newNimNode(nnkBracketExpr, prc.body).add(
  271. newIdentNode("newFuture"),
  272. subRetType),
  273. newLit(prcName)))) # Get type from return type of this proc
  274. # -> cb(retFuture, nameIter)
  275. outerProcBody.add newCall(cbName, retFutureSym, iteratorNameSym)
  276. # -> return retFuture
  277. outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym)
  278. result = prc
  279. # Add discardable pragma.
  280. if returnType.kind == nnkEmpty:
  281. # xxx consider removing `owned`? it's inconsistent with non-void case
  282. result.params[0] = quote do: owned(Future[void])
  283. # based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
  284. if procBody.kind != nnkEmpty:
  285. body2.add quote do:
  286. `outerProcBody`
  287. result.body = body2
  288. macro async*(prc: untyped): untyped =
  289. ## Macro which processes async procedures into the appropriate
  290. ## iterators and yield statements.
  291. if prc.kind == nnkStmtList:
  292. result = newStmtList()
  293. for oneProc in prc:
  294. result.add asyncSingleProc(oneProc)
  295. else:
  296. result = asyncSingleProc(prc)
  297. when defined(nimDumpAsync):
  298. echo repr result
  299. proc splitParamType(paramType: NimNode, async: bool): NimNode =
  300. result = paramType
  301. if paramType.kind == nnkInfix and paramType[0].strVal in ["|", "or"]:
  302. let firstAsync = "async" in paramType[1].toStrLit().strVal.normalize
  303. let secondAsync = "async" in paramType[2].toStrLit().strVal.normalize
  304. if firstAsync:
  305. result = paramType[if async: 1 else: 2]
  306. elif secondAsync:
  307. result = paramType[if async: 2 else: 1]
  308. proc stripReturnType(returnType: NimNode): NimNode =
  309. # Strip out the 'Future' from 'Future[T]'.
  310. result = returnType
  311. if returnType.kind == nnkBracketExpr:
  312. let fut = repr(returnType[0])
  313. verifyReturnType(fut, returnType)
  314. result = returnType[1]
  315. proc splitProc(prc: NimNode): (NimNode, NimNode) =
  316. ## Takes a procedure definition which takes a generic union of arguments,
  317. ## for example: proc (socket: Socket | AsyncSocket).
  318. ## It transforms them so that `proc (socket: Socket)` and
  319. ## `proc (socket: AsyncSocket)` are returned.
  320. result[0] = prc.copyNimTree()
  321. # Retrieve the `T` inside `Future[T]`.
  322. let returnType = stripReturnType(result[0][3][0])
  323. result[0][3][0] = splitParamType(returnType, async = false)
  324. for i in 1 ..< result[0][3].len:
  325. # Sync proc (0) -> FormalParams (3) -> IdentDefs, the parameter (i) ->
  326. # parameter type (1).
  327. result[0][3][i][1] = splitParamType(result[0][3][i][1], async=false)
  328. var multisyncAwait = quote:
  329. template await(value: typed): untyped =
  330. value
  331. result[0][^1] = nnkStmtList.newTree(multisyncAwait, result[0][^1])
  332. result[1] = prc.copyNimTree()
  333. if result[1][3][0].kind == nnkBracketExpr:
  334. result[1][3][0][1] = splitParamType(result[1][3][0][1], async = true)
  335. for i in 1 ..< result[1][3].len:
  336. # Async proc (1) -> FormalParams (3) -> IdentDefs, the parameter (i) ->
  337. # parameter type (1).
  338. result[1][3][i][1] = splitParamType(result[1][3][i][1], async = true)
  339. macro multisync*(prc: untyped): untyped =
  340. ## Macro which processes async procedures into both asynchronous and
  341. ## synchronous procedures.
  342. ##
  343. ## The generated async procedures use the `async` macro, whereas the
  344. ## generated synchronous procedures simply strip off the `await` calls.
  345. let (sync, asyncPrc) = splitProc(prc)
  346. result = newStmtList()
  347. result.add(asyncSingleProc(asyncPrc))
  348. result.add(sync)