mirror of
https://github.com/logos-storage/questionable.git
synced 2026-01-02 13:53:11 +00:00
Handle bind (=?) errors in without statements differently
Keeps track of the current error variable at compile time, instead of using a pointer to the error variable at runtime. Employs a trick with an unused type parameter to ensure that invocations of the bindFailed() macro are expanded after captureBindError() is expanded.
This commit is contained in:
parent
1f0afff48b
commit
d463d491cc
@ -1,24 +1,43 @@
|
||||
import std/options
|
||||
import std/macros
|
||||
|
||||
var captures {.global, compileTime.}: int
|
||||
var errorVariable {.threadvar.}: ptr ref CatchableError
|
||||
# A stack of names of error variables. Keeps track of the error variables that
|
||||
# are given to captureBindError().
|
||||
var errorVariableNames {.global, compileTime.}: seq[string]
|
||||
|
||||
template captureBindError*(error: var ref CatchableError, expression): auto =
|
||||
let previousErrorVariable = errorVariable
|
||||
errorVariable = addr error
|
||||
macro captureBindError*(error: var ref CatchableError, expression): auto =
|
||||
## Ensures that an error is assigned to the error variable when a binding (=?)
|
||||
## fails inside the expression.
|
||||
|
||||
static: inc captures
|
||||
let evaluated = expression
|
||||
static: dec captures
|
||||
|
||||
errorVariable = previousErrorVariable
|
||||
|
||||
evaluated
|
||||
# name of the error variable as a string literal
|
||||
let errorVariableName = newLit($error)
|
||||
quote do:
|
||||
# add error variable to the top of the stack
|
||||
static: errorVariableNames.add(`errorVariableName`)
|
||||
# evaluate the expression
|
||||
let evaluated = `expression`
|
||||
# pop error variable from the stack
|
||||
static: discard errorVariableNames.pop()
|
||||
# return the evaluated result
|
||||
evaluated
|
||||
|
||||
func error[T](option: Option[T]): ref CatchableError =
|
||||
newException(ValueError, "Option is set to `none`")
|
||||
|
||||
template bindFailed*(expression) =
|
||||
when captures > 0:
|
||||
mixin error
|
||||
errorVariable[] = expression.error
|
||||
macro bindFailed*(expression; _: type = void) =
|
||||
## Called when a binding (=?) fails.
|
||||
## Assigns an error to the error variable (specified in captureBindError())
|
||||
## when appropriate.
|
||||
|
||||
# This macro has a type parameter to ensure that the compiler does not
|
||||
# expand it before it expands invocations of captureBindError().
|
||||
|
||||
# check that we have an error variable on the stack
|
||||
if errorVariableNames.len > 0:
|
||||
# create an identifier that references the current error variable
|
||||
let errorVariable = ident errorVariableNames[^1]
|
||||
return quote do:
|
||||
# check that the error variable is in scope
|
||||
when compiles(`errorVariable`):
|
||||
# assign bind error to error variable
|
||||
`errorVariable` = `expression`.error
|
||||
|
||||
@ -412,6 +412,17 @@ suite "result":
|
||||
for i in 0..<1000:
|
||||
spawn fail(i)
|
||||
|
||||
test "without statement doesn't interfere with generic code called elsewhere":
|
||||
proc foo(_: type): ?!int =
|
||||
if error =? success(1).errorOption:
|
||||
discard
|
||||
|
||||
proc bar {.used.} = # defined, but not used
|
||||
without x =? bool.foo(), error:
|
||||
discard error
|
||||
|
||||
discard bool.foo() # same type parameter 'bool' as used in bar()
|
||||
|
||||
test "catch can be used to convert exceptions to results":
|
||||
check parseInt("42").catch == 42.success
|
||||
check parseInt("foo").catch.error of ValueError
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user