-
Notifications
You must be signed in to change notification settings - Fork 53
Raise tracking #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Raise tracking #251
Conversation
|
This is indeed one of the options we've discussed in the past, to annotate future with an error type akin to However, it would indeed be nice to reuse the raises instead, and this should be doable eventually - the way forward here should be that we move any given raises annotations to the generated iterator such that the compiler enforces them - what remains is to introspect code snippets with the There are smaller incremental steps we can take however: one is an Finally, there's already a few places experimenting with |
|
This is one of the two possible approaches to solve this problem. Besides changing the
I've written about the design and implemented the key parts of it here: https://github.com/status-im/nim-stew/blob/b3a6b52c7764da0b351e748b1080127677c734eb/docs/error_handling.md Having that said, changing the |
this is good for experiments experiments and proof-of-concepts, but I think ultimately we will want to land in a "native" solution for
does it even work to do |
Couldn't make it work. However, I think we could do type
EFuture[T, E] =...
Future[T] = EFuture[T, (CatchableError)]And then, the async pragma could automatically transform the return Future into a EFuture using the raises as E
Issue is, iirc, the push pragma is applied after async transformation, so we don't know the raises coming from push yet.
To my current knowledge, the only way to do this is how I did it here |
that's what |
a starting point might be to get explicit |
This is a false dilemma in general. You can implement exactly the same specializations of the |
I'm saying that we must, compared to the proposal in nim-stew, if we're going to actually land it in something like nim-chronos that's used for real.. it's not so much a dilemma as a requirement so as to turn the error handling proposal into a pragmatic and real solution |
|
I don't understand any bit of this sentence. What I said is that you can define the error-information-carrying wrapper type at a layer that is above Chronos (or within Chronos) and you can still implement features such as "A Future for an async op that can't raise an exception doesn't have an |
b6dec2a to
473a33c
Compare
|
I did a V2: Future*[T] = ref object of FutureBase ## Typed future.
[..]
FuturEx*[T, E] = Future[T]This allows it to be backward compatible, you can use a Future directly but loose raises info, or use FuturEx to have it The async macro will read the raises pragma and put info in the return type: proc test1(): Future[void] {.async, raises: [ValueError].} =
raise newException(ValueError, "Value")Will become proc test1(): FuturEx[void, (CancelledError, ValueError)] {.stackTrace: off,
raises: [], gcsafe.} =
iterator test1_436207619(chronosInternalRetFuture: Future[void]): FutureBase {.
closure, gcsafe, raises: [CancelledError, ValueError].} =
block:
raise newException(ValueError, "Value")
complete(chronosInternalRetFuture)
var resultFuture = newFuture[void]("test1")
resultFuture.closure = test1_436207619
futureContinue(resultFuture)
return resultFutureIf no Works: proc test1(): Future[void] {.async, raises: [ValueError].} =
raise newException(ValueError, "Value")
proc test2() {.async, raises: [].} =
await sleepAsync(100.milliseconds)
let toto = test1()
try:
await toto
echo "Mhh?"
except ValueError as exc:
echo "Works"(and also shows why the errors must be stored in the future type, and effectsOf may not help us if we keep our current syntax) Doesn't compile: proc test1(): Future[void] {.async, raises: [ValueError].} =
raise newException(ValueError, "Value")
proc test2() {.async, raises: [].} =
await test1()Doesn't compile: proc test1(): Future[void] {.async, raises: [ValueError].} =
raise newException(ValueError, "Value")
proc test2() {.async, raises: [ValueError].} =
let fut: Future[void] = test1() # We lost raise info here
await fut # can raise an unlisted exception: CatchableError |
You would still need to inform the caller that you can't raise, otherwise eg proc test1() {.atask.} =
discard
proc test2() {.atask.} =
await test1() # Can't compile, can raise unlisted exceptionsSo the usefulness would be very limited (only useful for asyncSpawn) |
118da94 to
3f53d01
Compare
|
The branches I had to create to make this work with nimbus: With theses branches, everything compiles correctly As you can see, the major issue is people (including me) putting wrong raises in async There is also a weird issue with libp2p on nim devel, need to investigate wdyt? |
|
Overall this looks really promising! I'd love to see this moving forward and it would be invaluable given the current status-quo! |
|
Thinking about the Futurex type, one of the goals I had for the future type family was that it should carry no exceptions for code that doesn't raise - There are two ways to go forward here, I think - either a base type or allowing E to be void - this is also one of the reasons Result is more fundamentally more powerful as a vehicle for transporting error information: it allows both static and dynamic typing to be used, with the former allowing more powerful static analysis down the line - in a base layer library / type structure like Future, it's an important flexibility to have. We have to anticipate that whatever type we add here will end up being used in user code as well - although the majority of code uses the macros, there are special cases to consider where the transformation ends up generating poor code and manually written Future code simply is better. |
putting raises on |
|
Bumped this to be on top of #449 Also, chronos doesn't compile anymore with nim 1.2 (even before this PR), so updated the nimble file |
|
Upstream regression: nim-lang/Nim#22765 test "Can create callbacks":
proc test1 {.async, asyncraises: [ValueError].} = raise newException(ValueError, "hey")
let callback: proc {.async, asyncraises: [ValueError].} = test1on nim 2.0 Workaround is: test "Can create callbacks":
proc test1 {.async, asyncraises: [ValueError].} = raise newException(ValueError, "hey")
let callback: proc: Future[void] {.async, asyncraises: [ValueError].} = test1 |
Use internal naming scheme for RTF (this type should only be accessed via asyncraises)
|
This is now looking ok:ish - since it was easy, we also no longer generate try/finally at all for |
This reverts commit 86bfeb5. `finally` is needed if code returns early :/
* avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc
|
This version of the PR is sufficient for enabling experimentation with the feature, as the changes are additive (should not significantly change code that doesn't use it) - merging so as to simplify further work on the feature while testing it in real-life code |
Per discussion in #251 (comment), `async: (parameters..)` is introduced as a way to customize the async transformation instead of relying on separate keywords (like asyncraises). Two parameters are available as of now: `raises`: controls the exception effect tracking `raw`: disables body transformation Parameters are added to `async` as a tuple allowing more params to be added easily in the future: ```nim: proc f() {.async: (name: value, ...).}` ```
Per discussion in #251 (comment), `async: (parameters..)` is introduced as a way to customize the async transformation instead of relying on separate keywords (like asyncraises). Two parameters are available as of now: `raises`: controls the exception effect tracking `raw`: disables body transformation Parameters are added to `async` as a tuple allowing more params to be added easily in the future: ```nim: proc f() {.async: (name: value, ...).}` ```
Per discussion in #251 (comment), `async: (parameters..)` is introduced as a way to customize the async transformation instead of relying on separate keywords (like asyncraises). Two parameters are available as of now: `raises`: controls the exception effect tracking `raw`: disables body transformation Parameters are added to `async` as a tuple allowing more params to be added easily in the future: ```nim: proc f() {.async: (name: value, ...).}` ```
EDIT:
This PR adds the possibility to do check exceptions in async procedures. More info from the updated README:
By specifying a
asyncraiseslist to an async procedure, you can check whichexceptions can be thrown by it.
Under the hood, the return type of
p1will be rewritten to another type,which will convey raises informations to await.