Skip to content

Commit

Permalink
result: add result assignment extenstion point for ?
Browse files Browse the repository at this point in the history
When `?` exits early, it does so using variations `return v` - however,
in frameworks such as `chronos`, we need to change `return v` to
`future.complete(v)` - this feature allows chronos to inject a template
that performs this assignment instead of using the "ordinary" flow.

Risk:

`assignResult` might already be taken as a name, and multiple frameworks
might compete for the functionality.

Potential alternative:

Instead of `assignResult`, this construct could be called
`returnResult`, and it would be responsible for breaking the control
flow (performing the `return` in addition to assigning the result).

Other interactions:

#34 proposes a general-purpose
conversion framework - this could be used to automatically convert the
error based on existing conversions - the use case is that oftentimes,
one error needs to be converted to another in a call chain and injecting
a converter simplifies this flow - this might however create an
problematic interaction with an `assignResult` template in the future.
  • Loading branch information
arnetheduck committed Jun 8, 2023
1 parent 13e55ed commit e68ca83
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 6 deletions.
38 changes: 32 additions & 6 deletions stew/results.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1061,18 +1061,44 @@ template `?`*[T, E](self: Result[T, E]): auto =
## let v = ? funcWithResult()
## echo v # prints value, not Result!
## ```
##
## ``assignResult?`` extension point
##
## If a template / function named ``assignResult?`` exists in the scope, it will
## be called instead of assigning the result - this can be used to intercept
## the assignment to `result` that implicitly happens on early return in case
## of error.
##
## To prevent conflicts, it is recommended that ``assignResult?`` is declared
## close to the scope of `?` (as opposed to a globally visible symbol)
##
## ```nim
## proc f(): Result[...] =
## template `assignResult?`(v: Result) = ...
## let a = ? f2()
## ```
##
## Experimental
# TODO the v copy is here to prevent multiple evaluations of self - could
# probably avoid it with some fancy macro magic..
let v = (self)
if not v.oResultPrivate:
when typeof(result) is typeof(v):
return v
else:
when E is void:
return err(typeof(result))
when compiles(`assignResult?`(default(typeof(result)))):
when typeof(result) is typeof(v):
`assignResult?`(v)
elif E is void:
`assignResult?`(err(typeof(result)))
else:
return err(typeof(result), v.eResultPrivate)
`assignResult?`(err(typeof(result), v.eResultPrivate))
return
else:
return
when typeof(result) is typeof(v):
v
elif E is void:
err(typeof(result))
else:
err(typeof(result), v.eResultPrivate)

when not(T is void):
v.vResultPrivate
Expand Down
63 changes: 63 additions & 0 deletions tests/test_results.nim
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,66 @@ block: # Constants
proc checkIt(v: WithOpt) =
doAssert v.opt.isNone()
checkIt(noneWithOpt)

proc testAssignResult() =
var assigned: bool
template `assignResult?`(v: Result) =
assigned = true
result = v

proc failed(): Result[int, string] =
err("fail")

proc calling(): Result[int, string] =
let _ = ? failed()
doAssert false

let r = calling()
doAssert assigned
doAssert r == Result[int, string].err("fail")

assigned = false

proc emptyErr: Result[int, void] =
return err()

proc emptyErr2: Result[int, void] =
let _ = ? emptyErr()
doAssert false

doAssert emptyErr2().isErr()
doAssert assigned

proc testAssignResultGeneric[T]() =
var assigned: bool
template `assignResult?`(v: Result) =
mixin result
assigned = true
result = v

proc failed(): Result[T, string] =
err("fail")

proc calling(): Result[T, string] =
let _ = ? failed()
doAssert false

let r = calling()
doAssert assigned
doAssert r == Result[T, string].err("fail")

assigned = false

proc emptyErr: Result[T, void] =
return err()

proc emptyErr2: Result[T, void] =
let _ = ? emptyErr()
doAssert false

doAssert emptyErr2().isErr()
doAssert assigned

testAssignResult()

testAssignResultGeneric[int]()

0 comments on commit e68ca83

Please sign in to comment.