Fix: without statement with error works in generic code

This commit is contained in:
Mark Spanbroek 2022-06-20 17:36:24 +02:00 committed by markspanbroek
parent d7e9f0bf7f
commit 0895a9c065
4 changed files with 64 additions and 40 deletions

View File

@ -1,5 +1,6 @@
import std/options
import std/macros
import ./private/binderror
proc option[T](option: Option[T]): Option[T] =
option
@ -8,17 +9,25 @@ proc placeholder(T: type): T =
discard
template bindLet(name, expression): bool =
let option = expression.option
let evaluated = expression
let option = evaluated.option
type T = typeof(option.unsafeGet())
let name {.used.} = if option.isSome: option.unsafeGet() else: placeholder(T)
let name {.used.} = if option.isSome:
option.unsafeGet()
else:
bindFailed(evaluated)
placeholder(T)
option.isSome
template bindVar(name, expression): bool =
let option = expression.option
let evaluated = expression
let option = evaluated.option
type T = typeof(option.unsafeGet())
var name {.used.} : T = placeholder(T)
if option.isSome:
name = option.unsafeGet()
var name {.used.} = if option.isSome:
option.unsafeGet()
else:
bindFailed(evaluated)
placeholder(T)
option.isSome
macro `=?`*(name, expression): bool =

View File

@ -0,0 +1,19 @@
import std/options
var captureEnabled {.global, compiletime.}: bool
var errorVariable: ptr ref CatchableError
template captureBindError*(error: var ref CatchableError, expression): auto =
static: captureEnabled = true
errorVariable = addr error
let evaluated = expression
static: captureEnabled = false
evaluated
func error[T](option: Option[T]): ref CatchableError =
newException(ValueError, "Option is set to `none`")
template bindFailed*(expression) =
when captureEnabled:
mixin error
errorVariable[] = expression.error

View File

@ -1,39 +1,12 @@
import std/macros
import ./binding
import ./without
import ./private/binderror
macro replaceInfix(expression, operator, replacement): untyped =
## Replaces an infix operator in an expression. The AST of the expression is
## traversed to find and replace all instances of the operator.
proc replace(expression, operator, replacement: NimNode): NimNode =
if expression.kind == nnkInfix and eqIdent(expression[0], operator):
expression[0] = replacement
expression[2] = replace(expression[2], operator, replacement)
else:
for i in 0..<expression.len:
expression[i] = replace(expression[i], operator, replacement)
expression
replace(expression, operator, replacement)
template without*(condition, errorname, body) =
template without*(condition, errorname, body): untyped =
## Used to place guards that ensure that a Result contains a value.
## Exposes error when Result does not contain a value.
var error: ref CatchableError
# override =? operator such that it stores the error if there is one
template `override=?`(name, expression): bool {.gensym, used.} =
let optional = expression
when optional is Result:
if optional.isFailure:
error = optional.error
when optional is Option:
if optional.isNone:
error = newException(ValueError, "Option is set to `none`")
name =? optional
without replaceInfix(condition, `=?`, `override=?`):
without captureBindError(error, condition):
template errorname: ref CatchableError = error
body

View File

@ -265,6 +265,22 @@ suite "result":
test()
test "without statement with error works in generic code":
proc test(_: type) =
without a =? int.failure "error", e:
check e.msg == "error"
return
fail
test(int)
test "without statements with error can be nested":
without a =? int.failure "error1", e1:
without b =? int.failure "error2", e2:
check e1.msg == "error1"
check e2.msg == "error2"
check e1.msg == "error1"
test "catch can be used to convert exceptions to results":
check parseInt("42").catch == 42.success
check parseInt("foo").catch.error of ValueError
@ -387,11 +403,11 @@ import pkg/questionable/resultsbase
suite "result compatibility":
test "|?, =? and .option work on other types of Result":
type R = Result[int, string]
let good = R.ok 42
let bad = R.err "error"
type R = Result[int, string]
let good = R.ok 42
let bad = R.err "error"
test "|?, =? and .option work on other types of Result":
check bad |? 43 == 43
if value =? good:
@ -400,3 +416,10 @@ suite "result compatibility":
fail
check good.option == 42.some
test "=? works on other type of Result after without statement with error":
without a =? 42.success, error:
discard error # fixes warning about unused variable "error"
fail
without b =? good:
fail