asyncmacro.nim 13 KB

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