asyncmacro.nim 12 KB

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