Skip to content

Commit

Permalink
detect async procs
Browse files Browse the repository at this point in the history
  • Loading branch information
nitely committed Jan 7, 2025
1 parent 9fbf0c0 commit 1ed9ea6
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 30 deletions.
38 changes: 29 additions & 9 deletions lib/pure/asyncmacro.nim
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,29 @@ proc verifyReturnType(typeName: string, node: NimNode = nil) =
error("Expected return type of 'Future' got '$1'" %
typeName, node)

proc isAsyncPrc0(n: NimNode): bool =
if n.kind == nnkBlockStmt and n[0].strVal == "asynctrack":
return true
if n.kind in RoutineNodes:
return false
for i in 0 .. n.len-1:
if isAsyncPrc0(n[i]):
return true
return false

proc isAsyncPrc(n: NimNode): bool =
for i in 0 .. n.len-1:
if isAsyncPrc0(n[i]):
return true
return false

macro withRaises[T](f: Future[T], body: untyped): untyped =
#echo repr f.kind
# XXX support more cases
let prcSym = case f.kind
of nnkSym:
if f.getImpl.kind == nnkIdentDefs and f.getImpl[^1].kind == nnkCall:
#echo repr f.getImpl[^1][0]
f.getImpl[^1][0]
else:
nil
Expand All @@ -173,7 +190,8 @@ macro withRaises[T](f: Future[T], body: untyped): untyped =
else:
nil
#echo repr prcSym
if prcSym != nil:
#echo repr prcSym.getImpl
if prcSym != nil and isAsyncPrc(prcSym.getImpl):
let raisesList = getRaisesList(prcSym)
var raisesListTyp = newNimNode(nnkBracket)
if raisesList.len > 0:
Expand Down Expand Up @@ -361,8 +379,10 @@ proc asyncSingleProc(prc: NimNode): NimNode =

# based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
if procBody.kind != nnkEmpty:
let asynctrack = ident"asynctrack"
body2.add quote do:
`outerProcBody`
block `asynctrack`:
`outerProcBody`
result.body = body2

macro async*(prc: untyped): untyped =
Expand Down Expand Up @@ -436,15 +456,15 @@ macro multisync*(prc: untyped): untyped =
result.add(sync)

macro toFutureEx*(prc: typed): untyped =
# XXX error instead of asserts
#echo repr getRaisesList(prc[0])
#assert prc.kind == nnkCall
template check(cond: untyped): untyped =
if not cond:
error("async proc call expected", prc)
check prc.kind == nnkCall
check prc[0].kind == nnkSym
check isAsyncPrc(prc[0].getImpl)
let procImpl = getTypeImpl(prc[0])
#assert procImpl.kind == nnkProcTy
check procImpl.kind == nnkProcTy
let retTyp = procImpl.params[0]
#assert retTyp.kind == nnkBracketExpr
#let fut = repr(retTyp[0])
#assert fut == "FutureUntracked", fut
let baseTyp = retTyp[1]
let raisesList = getRaisesList(prc[0])
let exTyp = if raisesList.len == 0:
Expand Down
68 changes: 47 additions & 21 deletions tests/async/tasync_error_tracking.nim
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ block:
discard

block:
# XXX raises: []
# we cannot tell if fcb is an async proc
# or a closure that returns a user created newFuture()
# that can raise anything
type FooBar = object
fcb: proc(): Future[void] {.closure, gcsafe.}

Expand Down Expand Up @@ -76,18 +78,16 @@ block:
doAssert not compiles(bad())

block:
proc bar {.async.} =
err(false)

proc foo {.async.} =
await bar()

template good =
proc bar {.async.} =
err(false)
proc foo {.async.} =
await bar()
proc main {.async, raises: [MyError].} =
await foo()
template bad =
proc bar {.async.} =
err(false)
proc foo {.async.} =
await bar()
proc main {.async, raises: [].} =
await foo()
doAssert compiles(good())
Expand All @@ -103,17 +103,43 @@ block:
doAssert compiles(good())
doAssert not compiles(bad())

# XXX this should not compile;
# add Future.isInternal field?
when false:
proc foo {.async, raises: [].} =
let f = newFuture[void]()
f.complete()
await f
when false:
block:
template good =
proc foo {.async, raises: [Exception].} =
await newFuture[void]()
template bad =
proc foo {.async, raises: [].} =
await newFuture[void]()
doAssert compiles(good())
doAssert not compiles(bad())

block:
proc fut: Future[void] =
newFuture[void]()
proc main {.async, raises: [].} =
let f = fut()
f.complete()
await f

template good =
proc main {.async, raises: [Exception].} =
await fut()
template bad =
proc main {.async, raises: [].} =
await fut()
doAssert compiles(good())
doAssert not compiles(bad())

block:
proc bar() {.async.} =
err(false)

# XXX We could check all returns are from async procs
# and if so use the inferred proc raises
proc foo(): Future[void] =
bar()

template good =
proc main {.async, raises: [Exception].} =
await foo()
template bad =
proc main {.async, raises: [MyError].} =
await foo()
doAssert compiles(good())
doAssert not compiles(bad())

0 comments on commit 1ed9ea6

Please sign in to comment.