mirror of
https://github.com/logos-storage/questionable.git
synced 2026-01-02 22:03:10 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2906828765 | ||
|
|
2e7f20392b | ||
|
|
3dcf21491d | ||
|
|
82d90b67bc | ||
|
|
b098ae696a | ||
|
|
83ae4a6409 | ||
|
|
57e467b8b0 | ||
|
|
47692e0d92 | ||
|
|
6ef525cfe2 | ||
|
|
43e7deb827 | ||
|
|
4a74d65e17 | ||
|
|
672248f431 | ||
|
|
d463d491cc | ||
|
|
1f0afff48b |
@ -12,7 +12,7 @@ Use the [Nimble][3] package manager to add `questionable` to an existing
|
||||
project. Add the following to its .nimble file:
|
||||
|
||||
```nim
|
||||
requires "questionable >= 0.10.12 & < 0.11.0"
|
||||
requires "questionable >= 0.10.15 & < 0.11.0"
|
||||
```
|
||||
|
||||
If you want to make use of Result types, then you also have to add either the
|
||||
@ -151,7 +151,7 @@ have to explicitly import the `questionable/results` module:
|
||||
import questionable/results
|
||||
```
|
||||
|
||||
You can use `?!` make a Result type. These Result types either hold a value or
|
||||
You can use `?!` to make a Result type. These Result types either hold a value or
|
||||
an error. For example the type `?!int` is short for `Result[int, ref
|
||||
CatchableError]`.
|
||||
|
||||
@ -226,6 +226,7 @@ Any Result can be converted to an Option:
|
||||
|
||||
```nim
|
||||
let converted = works().option # equals @[1, 1, 2, 2, 2].some
|
||||
let errOption = fails().errorOption # option that is set when the Result holds an error
|
||||
```
|
||||
|
||||
[1]: https://nim-lang.org/docs/options.html
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
version = "0.10.12"
|
||||
version = "0.10.15"
|
||||
author = "Questionable Authors"
|
||||
description = "Elegant optional types"
|
||||
license = "MIT"
|
||||
|
||||
@ -1,16 +1,24 @@
|
||||
import std/macros
|
||||
import std/options
|
||||
|
||||
macro `.?`*(expression: seq | string, brackets: untyped{nkBracket}): untyped =
|
||||
# chain is of shape: (seq or string).?[index]
|
||||
let index = brackets[0]
|
||||
quote do:
|
||||
block:
|
||||
type T = typeof(`expression`[`index`])
|
||||
let evaluated = `expression`
|
||||
if `index` < evaluated.len:
|
||||
evaluated[`index`].some
|
||||
else:
|
||||
T.none
|
||||
proc safeGet[T](expression: seq[T] | openArray[T], index: int): Option[T] =
|
||||
if index >= expression.low and index <= expression.high:
|
||||
expression[index].some
|
||||
else:
|
||||
T.none
|
||||
|
||||
proc safeGet(expression: string, index: int): Option[char] =
|
||||
if index >= expression.low and index <= expression.high:
|
||||
expression[index].some
|
||||
else:
|
||||
char.none
|
||||
|
||||
macro `.?`*(expression: seq | string | openArray, brackets: untyped{nkBracket}): untyped =
|
||||
# chain is of shape: (seq or string or openArray).?[index]
|
||||
let index = brackets[0]
|
||||
quote do:
|
||||
block:
|
||||
safeGet(`expression`, `index`)
|
||||
|
||||
macro `.?`*(expression: typed, brackets: untyped{nkBracket}): untyped =
|
||||
# chain is of shape: expression.?[index]
|
||||
|
||||
6
questionable/private/bareexcept.nim
Normal file
6
questionable/private/bareexcept.nim
Normal file
@ -0,0 +1,6 @@
|
||||
template ignoreBareExceptWarning*(body) =
|
||||
when defined(nimHasWarnBareExcept):
|
||||
{.push warning[BareExcept]:off warning[UnreachableCode]:off.}
|
||||
body
|
||||
when defined(nimHasWarnBareExcept):
|
||||
{.pop.}
|
||||
@ -1,24 +1,54 @@
|
||||
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
|
||||
# name of the error variable as a string literal
|
||||
let errorVariableName = newLit($error)
|
||||
|
||||
errorVariable = previousErrorVariable
|
||||
let evaluated = genSym(nskLet, "evaluated")
|
||||
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`
|
||||
|
||||
evaluated
|
||||
|
||||
func error[T](option: Option[T]): ref CatchableError =
|
||||
func unsafeCatchableError[T](_: Option[T]): ref CatchableError =
|
||||
newException(ValueError, "Option is set to `none`")
|
||||
|
||||
template bindFailed*(expression) =
|
||||
when captures > 0:
|
||||
mixin error
|
||||
errorVariable[] = expression.error
|
||||
func unsafeCatchableError[T](_: ref T): ref CatchableError =
|
||||
newException(ValueError, "ref is nil")
|
||||
|
||||
func unsafeCatchableError[T](_: ptr T): ref CatchableError =
|
||||
newException(ValueError, "ptr is nil")
|
||||
|
||||
func unsafeCatchableError[Proc: proc | iterator](_: Proc): ref CatchableError =
|
||||
newException(ValueError, "proc or iterator is nil")
|
||||
|
||||
macro bindFailed*(expression: typed) =
|
||||
## Called when a binding (=?) fails.
|
||||
## Assigns an error to the error variable (specified in captureBindError())
|
||||
## when appropriate.
|
||||
|
||||
# The `expression` parameter is typed to ensure that the compiler does not
|
||||
# expand bindFailed() 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`.unsafeCatchableError
|
||||
|
||||
@ -7,6 +7,7 @@ import ./indexing
|
||||
import ./operators
|
||||
import ./without
|
||||
import ./withoutresult
|
||||
import ./private/bareexcept
|
||||
|
||||
include ./private/errorban
|
||||
|
||||
@ -109,18 +110,31 @@ proc option*[T,E](value: Result[T,E]): ?T =
|
||||
## Converts a Result into an Option.
|
||||
|
||||
if value.isOk:
|
||||
try: # workaround for erroneous exception tracking when T is a closure
|
||||
value.unsafeGet.some
|
||||
except Exception as exception:
|
||||
raise newException(Defect, exception.msg, exception)
|
||||
ignoreBareExceptWarning:
|
||||
try: # workaround for erroneous exception tracking when T is a closure
|
||||
return value.unsafeGet.some
|
||||
except Exception as exception:
|
||||
raise newException(Defect, exception.msg, exception)
|
||||
else:
|
||||
T.none
|
||||
return T.none
|
||||
|
||||
template toOption*[T, E](value: Result[T, E]): ?T =
|
||||
## Converts a Result into an Option.
|
||||
|
||||
value.option
|
||||
|
||||
proc unsafeCatchableError*[T, E](value: Result[T, E]): ref CatchableError =
|
||||
## Returns the error from the Result, converted to `ref CatchableError` if
|
||||
## necessary. Behaviour is undefined when the result holds a value instead of
|
||||
## an error.
|
||||
when E is ref CatchableError:
|
||||
value.unsafeError
|
||||
else:
|
||||
when compiles($value.unsafeError):
|
||||
newException(ResultFailure, $value.unsafeError)
|
||||
else:
|
||||
newException(ResultFailure, "Result is an error")
|
||||
|
||||
proc errorOption*[T, E](value: Result[T, E]): ?E =
|
||||
## Returns an Option that contains the error from the Result, if it has one.
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ macro without*(condition, errorname, body: untyped): untyped =
|
||||
let body = body.undoSymbolResolution(errorIdent)
|
||||
|
||||
quote do:
|
||||
var error: ref CatchableError
|
||||
var error {.gensym.}: ref CatchableError
|
||||
|
||||
without captureBindError(error, `condition`):
|
||||
template `errorIdent`: ref CatchableError = error
|
||||
|
||||
@ -337,13 +337,28 @@ suite "optionals":
|
||||
|
||||
test ".?[] can be used for indexing strings without raising IndexDefect":
|
||||
let str = "a"
|
||||
check str.?[0] == 'a'.some
|
||||
check str.?[0] == 'a'.some
|
||||
check str.?[1] == char.none
|
||||
check str.?[-1] == char.none
|
||||
|
||||
test ".?[] can be used for indexing sequences without raising IndexDefect":
|
||||
let sequence = @[1]
|
||||
check sequence.?[0] == 1.some
|
||||
check sequence.?[1] == int.none
|
||||
check sequence.?[-1] == int.none
|
||||
|
||||
test ".?[] can be used for indexing openArrays without raising IndexDefect":
|
||||
proc checkOpenArray(oa: openArray[int]): void =
|
||||
check oa.?[0] == 1.some
|
||||
check oa.?[1] == int.none
|
||||
check oa.?[-1] == int.none
|
||||
|
||||
checkOpenArray(@[1])
|
||||
|
||||
test ".?[] evaluates openArray expression only once":
|
||||
var count = 0
|
||||
discard (inc count; @[1].toOpenArray(0, 0)).?[0]
|
||||
check count == 1
|
||||
|
||||
test ".?[] can be followed by calls, operators and indexing":
|
||||
let table = @{"a": @[41, 42]}.toTable
|
||||
|
||||
@ -326,6 +326,48 @@ suite "result":
|
||||
test1()
|
||||
test2()
|
||||
|
||||
test "without statement with error handles references as well":
|
||||
proc test =
|
||||
var x: ref int = nil
|
||||
without a =? x, error:
|
||||
check error.msg == "ref is nil"
|
||||
return
|
||||
fail
|
||||
|
||||
test()
|
||||
|
||||
test "without statement with error handles pointers as well":
|
||||
proc test =
|
||||
var x: ptr int = nil
|
||||
without a =? x, error:
|
||||
check error.msg == "ptr is nil"
|
||||
return
|
||||
fail
|
||||
|
||||
test()
|
||||
|
||||
test "without statement with error handles closures as well":
|
||||
proc test =
|
||||
var x = proc = discard
|
||||
x = nil
|
||||
without a =? x, error:
|
||||
check error.msg == "proc or iterator is nil"
|
||||
return
|
||||
fail
|
||||
|
||||
test()
|
||||
|
||||
test "without statement with error handles iterators as well":
|
||||
when (NimMajor, NimMinor) != (2, 0):
|
||||
proc test =
|
||||
var x: iterator: int = nil
|
||||
without a =? x, error:
|
||||
check error.msg == "proc or iterator is nil"
|
||||
return
|
||||
fail
|
||||
|
||||
test()
|
||||
|
||||
test "without statement with error can be used more than once":
|
||||
proc test =
|
||||
without a =? 42.success, error:
|
||||
@ -412,6 +454,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
|
||||
@ -590,6 +643,19 @@ suite "result":
|
||||
someProc(42.success)
|
||||
someProc(int.failure "some error")
|
||||
|
||||
type TypeWithSideEffect = object
|
||||
proc `$`*(value: TypeWithSideEffect): string {.sideEffect.} =
|
||||
discard
|
||||
|
||||
suite "result side effects":
|
||||
|
||||
test "without statement with error works when `$` has side effects":
|
||||
proc foo =
|
||||
without x =? TypeWithSideEffect.failure("error"), error:
|
||||
discard error
|
||||
return
|
||||
fail()
|
||||
foo()
|
||||
|
||||
import pkg/questionable/resultsbase
|
||||
|
||||
@ -597,7 +663,7 @@ suite "result compatibility":
|
||||
|
||||
type R = Result[int, string]
|
||||
let good = R.ok 42
|
||||
let bad = R.err "error"
|
||||
let bad = R.err "some error"
|
||||
|
||||
test "|?, =? and .option work on other types of Result":
|
||||
check bad |? 43 == 43
|
||||
@ -615,3 +681,13 @@ suite "result compatibility":
|
||||
fail
|
||||
without b =? good:
|
||||
fail
|
||||
|
||||
test "without statement with error works on other type of Result":
|
||||
without value =? bad, error:
|
||||
check error of ResultFailure
|
||||
check error.msg == "some error"
|
||||
|
||||
test "without statement with error works on Result[T, void]":
|
||||
without value =? Result[int, void].err, error:
|
||||
check error of ResultFailure
|
||||
check error.msg == "Result is an error"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user