mirror of
https://github.com/logos-storage/questionable.git
synced 2026-01-03 06:13:09 +00:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2906828765 | ||
|
|
2e7f20392b | ||
|
|
3dcf21491d | ||
|
|
82d90b67bc | ||
|
|
b098ae696a | ||
|
|
83ae4a6409 | ||
|
|
57e467b8b0 | ||
|
|
47692e0d92 | ||
|
|
6ef525cfe2 | ||
|
|
43e7deb827 | ||
|
|
4a74d65e17 | ||
|
|
672248f431 | ||
|
|
d463d491cc | ||
|
|
1f0afff48b | ||
|
|
2dd6b6b220 | ||
|
|
0f095d6b7c | ||
|
|
cdf639c4ea | ||
|
|
fe47a19825 | ||
|
|
440debc7c3 | ||
|
|
c2a08bd703 | ||
|
|
52e11f2011 | ||
|
|
af4f194597 | ||
|
|
5c8d422ac8 | ||
|
|
e56cf86c4a | ||
|
|
416b6dd566 | ||
|
|
08581f5efd | ||
|
|
9af4ce1ca1 | ||
|
|
1569ef4526 | ||
|
|
b3cf35ac45 | ||
|
|
3bce3088a7 | ||
|
|
ffe0faa3bb | ||
|
|
0d7ce8efde | ||
|
|
58c10fb333 | ||
|
|
8daae27089 | ||
|
|
b18444a6d0 | ||
|
|
f957dd59d6 | ||
|
|
6cbbda7e4d | ||
|
|
096ca864b0 | ||
|
|
1dcef4b302 | ||
|
|
f78bdd9d58 | ||
|
|
30e4184a99 | ||
|
|
4d631b1ba9 | ||
|
|
82408a5ca2 | ||
|
|
cbdd882b9f | ||
|
|
9e3a822877 | ||
|
|
cfe17ca899 | ||
|
|
90ea780ba9 | ||
|
|
4abeef5c36 | ||
|
|
22f2c9761a | ||
|
|
13c7ff7671 | ||
|
|
b0666ba4f1 | ||
|
|
955597a4fd | ||
|
|
0f9b12abc6 | ||
|
|
d9333a8f03 | ||
|
|
0895a9c065 | ||
|
|
d7e9f0bf7f | ||
|
|
361948499b | ||
|
|
6018fd43e0 | ||
|
|
a748d22350 | ||
|
|
ef29000f94 | ||
|
|
91a38040ea | ||
|
|
7b209af6fb | ||
|
|
92641b552d | ||
|
|
cfe4c6fc95 | ||
|
|
d7a757a8cb | ||
|
|
0fe40d2347 | ||
|
|
56a4bca641 | ||
|
|
4967084d22 | ||
|
|
b9a090d001 | ||
|
|
8feb684574 | ||
|
|
4a1783c472 | ||
|
|
a2ded4f01a | ||
|
|
986716511f | ||
|
|
3f18970931 | ||
|
|
93f5c919fb | ||
|
|
54516fd2d1 | ||
|
|
d18580bb68 | ||
|
|
dd9ac6dc20 | ||
|
|
2700038316 | ||
|
|
7303be50da | ||
|
|
8bf4f27f30 | ||
|
|
e8ab268758 | ||
|
|
ebc7a2b48e | ||
|
|
ee3d38d8d5 | ||
|
|
0569625f6a | ||
|
|
db9e1a343a | ||
|
|
fa56587bcd | ||
|
|
d82581244c | ||
|
|
fca4c14e0c | ||
|
|
a8834aea66 | ||
|
|
ef5f796463 | ||
|
|
827214f7a9 | ||
|
|
b85a8eb5c8 | ||
|
|
144c70580a | ||
|
|
a66e42d058 | ||
|
|
89850aab50 | ||
|
|
83101ce073 | ||
|
|
fd73ff713f | ||
|
|
86bfcc1a47 | ||
|
|
a2023ae18e | ||
|
|
a8fe7bf7b3 | ||
|
|
e66cd2439b | ||
|
|
3e92c35a85 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
nim: [stable, 1.2.6]
|
nim: [stable, 1.6.16, 1.4.8, 1.2.18]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: iffy/install-nim@v3
|
- uses: iffy/install-nim@v3
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@
|
|||||||
!*/
|
!*/
|
||||||
!*.*
|
!*.*
|
||||||
nimbledeps
|
nimbledeps
|
||||||
|
nimble.develop
|
||||||
|
nimble.paths
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
nim 1.4.4
|
|
||||||
5
License.md
Normal file
5
License.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Licensed and distributed under either of
|
||||||
|
[MIT license](http://opensource.org/licenses/MIT) or
|
||||||
|
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
at your option. These files may not be copied, modified, or distributed except
|
||||||
|
according to those terms.
|
||||||
84
Readme.md
84
Readme.md
@ -12,14 +12,14 @@ Use the [Nimble][3] package manager to add `questionable` to an existing
|
|||||||
project. Add the following to its .nimble file:
|
project. Add the following to its .nimble file:
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
requires "questionable >= 0.4.0 & < 0.5.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
|
If you want to make use of Result types, then you also have to add either the
|
||||||
[result][2] package, or the [stew][4] package:
|
[result][2] package, or the [stew][4] package:
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
requires "result" # either this
|
requires "results" # either this
|
||||||
requires "stew" # or this
|
requires "stew" # or this
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -63,31 +63,43 @@ else:
|
|||||||
# this is reached, and y is not defined
|
# this is reached, and y is not defined
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `without` statement can be used to place guards that ensure that an Option
|
||||||
|
contains a value:
|
||||||
|
|
||||||
|
```nim
|
||||||
|
proc someProc(option: ?int) =
|
||||||
|
without value =? option:
|
||||||
|
# option did not contain a value
|
||||||
|
return
|
||||||
|
|
||||||
|
# use value
|
||||||
|
```
|
||||||
|
|
||||||
### Option chaining
|
### Option chaining
|
||||||
|
|
||||||
To safely access fields and call procs, you can use the `?.` operator:
|
To safely access fields and call procs, you can use the `.?` operator:
|
||||||
|
|
||||||
> Note: before version 0.3.0, the operator was `.?` instead of `?.`
|
> Note: in versions 0.3.x and 0.4.x, the operator was `?.` instead of `.?`
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
var numbers: ?seq[int]
|
var numbers: ?seq[int]
|
||||||
var amount: ?int
|
var amount: ?int
|
||||||
|
|
||||||
numbers = @[1, 2, 3].some
|
numbers = @[1, 2, 3].some
|
||||||
amount = numbers?.len
|
amount = numbers.?len
|
||||||
# amount now holds the integer 3
|
# amount now holds the integer 3
|
||||||
|
|
||||||
numbers = seq[int].none
|
numbers = seq[int].none
|
||||||
amount = numbers?.len
|
amount = numbers.?len
|
||||||
# amount now equals int.none
|
# amount now equals int.none
|
||||||
```
|
```
|
||||||
|
|
||||||
Invocations of the `?.` operator can be chained:
|
Invocations of the `.?` operator can be chained:
|
||||||
```nim
|
```nim
|
||||||
import sequtils
|
import sequtils
|
||||||
|
|
||||||
numbers = @[1, 1, 2, 2, 2].some
|
numbers = @[1, 1, 2, 2, 2].some
|
||||||
amount = numbers?.deduplicate?.len
|
amount = numbers.?deduplicate.?len
|
||||||
# amount now holds the integer 2
|
# amount now holds the integer 2
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -103,6 +115,19 @@ let z = x |? 3
|
|||||||
# z equals 3
|
# z equals 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Obtaining value with !
|
||||||
|
|
||||||
|
The `!` operator returns the value of an Option when you're absolutely sure that
|
||||||
|
it contains a value.
|
||||||
|
|
||||||
|
```nim
|
||||||
|
x = 42.some
|
||||||
|
let dare = !x # dare equals 42
|
||||||
|
|
||||||
|
x = int.none
|
||||||
|
let crash = !x # raises a Defect
|
||||||
|
```
|
||||||
|
|
||||||
### Operators
|
### Operators
|
||||||
|
|
||||||
The operators `[]`, `-`, `+`, `@`, `*`, `/`, `div`, `mod`, `shl`, `shr`, `&`,
|
The operators `[]`, `-`, `+`, `@`, `*`, `/`, `div`, `mod`, `shl`, `shr`, `&`,
|
||||||
@ -126,7 +151,7 @@ have to explicitly import the `questionable/results` module:
|
|||||||
import questionable/results
|
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
|
an error. For example the type `?!int` is short for `Result[int, ref
|
||||||
CatchableError]`.
|
CatchableError]`.
|
||||||
|
|
||||||
@ -135,21 +160,21 @@ proc example: ?!int =
|
|||||||
# either return an integer or an error
|
# either return an integer or an error
|
||||||
```
|
```
|
||||||
|
|
||||||
Assigning values is done using the `success` and `failure` procs:
|
Results can be made using the `success` and `failure` procs:
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
proc works: ?!seq[int] =
|
proc works: ?!seq[int] =
|
||||||
# always returns a Result holding a sequence
|
# always returns a Result holding a sequence
|
||||||
@[1, 1, 2, 2, 2].success
|
success @[1, 1, 2, 2, 2]
|
||||||
|
|
||||||
proc fails: ?!seq[int] =
|
proc fails: ?!seq[int] =
|
||||||
# always returns a Result holding a ValueError
|
# always returns a Result holding an error
|
||||||
seq[int].failure newException(ValueError, "something went wrong")
|
failure "something went wrong"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Binding, chaining, fallbacks and operators
|
### Binding, chaining, fallbacks and operators
|
||||||
|
|
||||||
Binding with the `=?` operator, chaining with the `?.` operator, fallbacks with
|
Binding with the `=?` operator, chaining with the `.?` operator, fallbacks with
|
||||||
the `|?` operator, and all the other operators that work with Options also work
|
the `|?` operator, and all the other operators that work with Options also work
|
||||||
for Results:
|
for Results:
|
||||||
```nim
|
```nim
|
||||||
@ -160,7 +185,7 @@ if x =? works():
|
|||||||
# use x
|
# use x
|
||||||
|
|
||||||
# chaining:
|
# chaining:
|
||||||
let amount = works()?.deduplicate?.len
|
let amount = works().?deduplicate.?len
|
||||||
|
|
||||||
# fallback values:
|
# fallback values:
|
||||||
let value = fails() |? @[]
|
let value = fails() |? @[]
|
||||||
@ -169,6 +194,20 @@ let value = fails() |? @[]
|
|||||||
let sum = works()[3] + 40
|
let sum = works()[3] + 40
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Without statement
|
||||||
|
|
||||||
|
The `without` statement can also be used with Results. It provides access to any
|
||||||
|
errors that may arise:
|
||||||
|
|
||||||
|
```nim
|
||||||
|
proc someProc(r: ?!int) =
|
||||||
|
without value =? r, error:
|
||||||
|
# use `error` to get the error from r
|
||||||
|
return
|
||||||
|
|
||||||
|
# use value
|
||||||
|
```
|
||||||
|
|
||||||
### Catching errors
|
### Catching errors
|
||||||
|
|
||||||
When you want to use Results, but need to call a proc that may raise an
|
When you want to use Results, but need to call a proc that may raise an
|
||||||
@ -187,22 +226,9 @@ Any Result can be converted to an Option:
|
|||||||
|
|
||||||
```nim
|
```nim
|
||||||
let converted = works().option # equals @[1, 1, 2, 2, 2].some
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
Banning Errors
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Should you decide to use Options and Results instead of the standard exception
|
|
||||||
handling in Nim, you may want to check that your procs are not accidently
|
|
||||||
raising errors. You can use the following include for this:
|
|
||||||
|
|
||||||
```nim
|
|
||||||
include questionable/errorban
|
|
||||||
```
|
|
||||||
|
|
||||||
Proc definitions below the error ban are checked by the compiler to ensure that
|
|
||||||
they do not raise errors.
|
|
||||||
|
|
||||||
[1]: https://nim-lang.org/docs/options.html
|
[1]: https://nim-lang.org/docs/options.html
|
||||||
[2]: https://github.com/arnetheduck/nim-result
|
[2]: https://github.com/arnetheduck/nim-result
|
||||||
[3]: https://github.com/nim-lang/nimble
|
[3]: https://github.com/nim-lang/nimble
|
||||||
|
|||||||
16
config.nims
Normal file
16
config.nims
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Style Check
|
||||||
|
if (NimMajor, NimMinor, NimPatch) >= (1, 6, 6):
|
||||||
|
--styleCheck:usages
|
||||||
|
if (NimMajor, NimMinor) < (1, 6):
|
||||||
|
--styleCheck:hint
|
||||||
|
else:
|
||||||
|
--styleCheck:error
|
||||||
|
|
||||||
|
# Disable some warnings
|
||||||
|
if (NimMajor, NimMinor) >= (1, 6):
|
||||||
|
switch("warning", "DotLikeOps:off")
|
||||||
|
|
||||||
|
# begin Nimble config (version 1)
|
||||||
|
when fileExists("nimble.paths"):
|
||||||
|
include "nimble.paths"
|
||||||
|
# end Nimble config
|
||||||
4
nimble.lock
Normal file
4
nimble.lock
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import ./questionable/dotlike
|
||||||
import ./questionable/options
|
import ./questionable/options
|
||||||
|
|
||||||
export options
|
export options
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
version = "0.4.3"
|
version = "0.10.15"
|
||||||
author = "Questionable Authors"
|
author = "Questionable Authors"
|
||||||
description = "Elegant optional types"
|
description = "Elegant optional types"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
task test, "Runs the test suite":
|
task test, "Runs the test suite":
|
||||||
for module in ["options", "result", "stew"]:
|
for module in ["options", "results", "stew"]:
|
||||||
withDir "testmodules/" & module:
|
withDir "testmodules/" & module:
|
||||||
|
delEnv "NIMBLE_DIR" # use nimbledeps dir
|
||||||
exec "nimble install -d -y"
|
exec "nimble install -d -y"
|
||||||
exec "nimble test -y"
|
exec "nimble test -y"
|
||||||
|
|||||||
89
questionable/binding.nim
Normal file
89
questionable/binding.nim
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import std/options
|
||||||
|
import std/macros
|
||||||
|
import ./private/binderror
|
||||||
|
|
||||||
|
when (NimMajor, NimMinor) < (1, 1):
|
||||||
|
type SomePointer = ref | ptr | pointer
|
||||||
|
elif (NimMajor, NimMinor) == (2, 0): # Broken in 2.0.0, fixed in 2.1.1.
|
||||||
|
type SomePointer = ref | ptr | pointer | proc
|
||||||
|
else:
|
||||||
|
type SomePointer = ref | ptr | pointer | proc | iterator {.closure.}
|
||||||
|
|
||||||
|
template toOption[T](option: Option[T]): Option[T] =
|
||||||
|
option
|
||||||
|
|
||||||
|
template toOption[T: SomePointer](value: T): Option[T] =
|
||||||
|
value.option
|
||||||
|
|
||||||
|
proc placeholder(T: type): T =
|
||||||
|
discard
|
||||||
|
|
||||||
|
template bindLet(name, expression): untyped =
|
||||||
|
let evaluated = expression
|
||||||
|
let option = evaluated.toOption
|
||||||
|
type T = typeof(option.unsafeGet())
|
||||||
|
let name {.used.} = if option.isSome:
|
||||||
|
option.unsafeGet()
|
||||||
|
else:
|
||||||
|
bindFailed(evaluated)
|
||||||
|
placeholder(T)
|
||||||
|
option.isSome
|
||||||
|
|
||||||
|
template bindVar(name, expression): untyped =
|
||||||
|
let evaluated = expression
|
||||||
|
let option = evaluated.toOption
|
||||||
|
type T = typeof(option.unsafeGet())
|
||||||
|
var name {.used.} = if option.isSome:
|
||||||
|
option.unsafeGet()
|
||||||
|
else:
|
||||||
|
bindFailed(evaluated)
|
||||||
|
placeholder(T)
|
||||||
|
option.isSome
|
||||||
|
|
||||||
|
proc newUnpackTupleNode(names: NimNode, value: NimNode): NimNode =
|
||||||
|
# builds tuple unpacking statement, eg: let (a, b) = value
|
||||||
|
let vartuple = nnkVarTuple.newTree()
|
||||||
|
for i in 0..<names.len:
|
||||||
|
vartuple.add names[i]
|
||||||
|
vartuple.add newEmptyNode()
|
||||||
|
vartuple.add value
|
||||||
|
nnkLetSection.newTree(vartuple)
|
||||||
|
|
||||||
|
macro bindTuple(names, expression): bool =
|
||||||
|
let opt = genSym(nskLet, "option")
|
||||||
|
let evaluated = genSym(nskLet, "evaluated")
|
||||||
|
let T = genSym(nskType, "T")
|
||||||
|
|
||||||
|
let value = quote do:
|
||||||
|
if `opt`.isSome:
|
||||||
|
`opt`.unsafeGet()
|
||||||
|
else:
|
||||||
|
bindFailed(`evaluated`)
|
||||||
|
placeholder(`T`)
|
||||||
|
|
||||||
|
let letsection = newUnpackTupleNode(names, value)
|
||||||
|
|
||||||
|
quote do:
|
||||||
|
let `evaluated` = `expression`
|
||||||
|
let `opt` = `evaluated`.toOption
|
||||||
|
type `T` = typeof(`opt`.unsafeGet())
|
||||||
|
`letsection`
|
||||||
|
`opt`.isSome
|
||||||
|
|
||||||
|
macro `=?`*(name, expression): bool =
|
||||||
|
## The `=?` operator lets you bind the value inside an Option or Result to a
|
||||||
|
## new variable. It can be used inside of a conditional expression, for
|
||||||
|
## instance in an `if` statement.
|
||||||
|
|
||||||
|
when (NimMajor, NimMinor) < (1, 6):
|
||||||
|
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr, nnkPar})
|
||||||
|
else:
|
||||||
|
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr})
|
||||||
|
|
||||||
|
if name.kind == nnkIdent:
|
||||||
|
quote do: bindLet(`name`, `expression`)
|
||||||
|
elif name.kind == nnkTupleConstr or name.kind == nnkPar:
|
||||||
|
quote do: bindTuple(`name`, `expression`)
|
||||||
|
else:
|
||||||
|
let name = name[0]
|
||||||
|
quote do: bindVar(`name`, `expression`)
|
||||||
@ -1,39 +1,79 @@
|
|||||||
|
import std/options
|
||||||
import std/macros
|
import std/macros
|
||||||
|
import std/strformat
|
||||||
|
|
||||||
template `?.`*(option: typed, identifier: untyped{nkIdent}): untyped =
|
func isSym(node: NimNode): bool =
|
||||||
# chain is of shape: option?.identifier
|
node.kind in {nnkSym, nnkOpenSymChoice, nnkClosedSymChoice}
|
||||||
|
|
||||||
|
func expectSym(node: NimNode) =
|
||||||
|
node.expectKind({nnkSym, nnkOpenSymChoice, nnkClosedSymChoice})
|
||||||
|
|
||||||
|
macro expectReturnType(identifier: untyped, expression: untyped): untyped =
|
||||||
|
let message =
|
||||||
|
fmt"'{identifier}' doesn't have a return type, it can't be in a .? chain"
|
||||||
|
quote do:
|
||||||
|
when compiles(`expression`) and not compiles(typeof `expression`):
|
||||||
|
{.error: `message`.}
|
||||||
|
|
||||||
|
template chain(option: typed, identifier: untyped{nkIdent}): untyped =
|
||||||
|
# chain is of shape: option.?identifier
|
||||||
|
expectReturnType(identifier, option.unsafeGet.identifier)
|
||||||
option ->? option.unsafeGet.identifier
|
option ->? option.unsafeGet.identifier
|
||||||
|
|
||||||
macro `?.`*(option: typed, infix: untyped{nkInfix}): untyped =
|
macro chain(option: typed, infix: untyped{nkInfix}): untyped =
|
||||||
# chain is of shape: option?.left `operator` right
|
# chain is of shape: option.?left `operator` right
|
||||||
|
let infix = infix.copyNimTree()
|
||||||
let left = infix[1]
|
let left = infix[1]
|
||||||
infix[1] = quote do: `option`?.`left`
|
infix[1] = quote do: `option`.?`left`
|
||||||
infix
|
infix
|
||||||
|
|
||||||
macro `?.`*(option: typed, bracket: untyped{nkBracketExpr}): untyped =
|
macro chain(option: typed, bracket: untyped{nkBracketExpr}): untyped =
|
||||||
# chain is of shape: option?.left[right]
|
# chain is of shape: option.?left[right]
|
||||||
|
let bracket = bracket.copyNimTree()
|
||||||
let left = bracket[0]
|
let left = bracket[0]
|
||||||
bracket[0] = quote do: `option`?.`left`
|
bracket[0] = quote do: `option`.?`left`
|
||||||
bracket
|
bracket
|
||||||
|
|
||||||
macro `?.`*(option: typed, dot: untyped{nkDotExpr}): untyped =
|
macro chain(option: typed, dot: untyped{nkDotExpr}): untyped =
|
||||||
# chain is of shape: option?.left.right
|
# chain is of shape: option.?left.right
|
||||||
|
let dot = dot.copyNimTree()
|
||||||
let left = dot[0]
|
let left = dot[0]
|
||||||
dot[0] = quote do: `option`?.`left`
|
dot[0] = quote do: `option`.?`left`
|
||||||
dot
|
dot
|
||||||
|
|
||||||
macro `?.`*(option: typed, call: untyped{nkCall}): untyped =
|
macro chain(option: typed, call: untyped{nkCall}): untyped =
|
||||||
|
let call = call.copyNimTree()
|
||||||
let procedure = call[0]
|
let procedure = call[0]
|
||||||
if call.len == 1:
|
if call.len == 1:
|
||||||
# chain is of shape: option?.procedure()
|
# chain is of shape: option.?procedure()
|
||||||
quote do: `option`?.`procedure`
|
quote do: `option`.?`procedure`
|
||||||
elif procedure.kind == nnkDotExpr:
|
elif procedure.kind == nnkDotExpr:
|
||||||
# chain is of shape: option?.left.right(arguments)
|
# chain is of shape: option.?left.right(arguments)
|
||||||
let (left, right) = (procedure[0], procedure[1])
|
let (left, right) = (procedure[0], procedure[1])
|
||||||
call[0] = right
|
call[0] = right
|
||||||
call.insert(1, quote do: `option`?.`left`)
|
call.insert(1, quote do: `option`.?`left`)
|
||||||
|
call
|
||||||
|
elif procedure.isSym and $procedure == "[]":
|
||||||
|
# chain is of shape: option.?left[right] after semantic analysis
|
||||||
|
let left = call[1]
|
||||||
|
call[1] = quote do: `option`.?`left`
|
||||||
call
|
call
|
||||||
else:
|
else:
|
||||||
# chain is of shape: option?.procedure(arguments)
|
# chain is of shape: option.?procedure(arguments)
|
||||||
call.insert(1, quote do: `option`.unsafeGet)
|
call.insert(1, quote do: `option`.unsafeGet)
|
||||||
quote do: `option` ->? `call`
|
quote do:
|
||||||
|
expectReturnType(`procedure`, `call`)
|
||||||
|
`option` ->? `call`
|
||||||
|
|
||||||
|
macro chain(option: typed, symbol: untyped): untyped =
|
||||||
|
symbol.expectSym()
|
||||||
|
let expression = ident($symbol)
|
||||||
|
quote do: `option`.?`expression`
|
||||||
|
|
||||||
|
template `.?`*(left: typed, right: untyped): untyped =
|
||||||
|
## The `.?` chaining operator is used to safely access fields and call procs
|
||||||
|
## on Options or Results. The expression is only evaluated when the preceding
|
||||||
|
## Option or Result has a value.
|
||||||
|
block:
|
||||||
|
let evaluated = left
|
||||||
|
chain(evaluated, right)
|
||||||
|
|||||||
2
questionable/dotlike.nim
Normal file
2
questionable/dotlike.nim
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
when defined(nimPreviewDotLikeOps):
|
||||||
|
{.error: "DotLikeOps are not supported by questionable".}
|
||||||
@ -1,8 +1,3 @@
|
|||||||
## Include this file to indicate that your module does not raise Errors.
|
{.warning: "errorban is deprecated; use nimble package `upraises` instead".}
|
||||||
## Disables compiler hints about unused declarations in Nim < 1.4.0
|
|
||||||
|
|
||||||
when (NimMajor, NimMinor, NimPatch) >= (1, 4, 0):
|
include ./private/errorban
|
||||||
{.push raises:[].}
|
|
||||||
else:
|
|
||||||
{.push raises: [Defect].}
|
|
||||||
{.hint[XDeclaredButNotUsed]: off.}
|
|
||||||
|
|||||||
@ -1,41 +1,32 @@
|
|||||||
import std/macros
|
import std/macros
|
||||||
|
import std/options
|
||||||
|
|
||||||
macro `?`*(expression: typed, brackets: untyped{nkBracket}): untyped =
|
proc safeGet[T](expression: seq[T] | openArray[T], index: int): Option[T] =
|
||||||
# chain is of shape: expression?[index]
|
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]
|
||||||
let index = brackets[0]
|
let index = brackets[0]
|
||||||
quote do:
|
quote do:
|
||||||
type T = typeof(`expression`[`index`])
|
block:
|
||||||
try:
|
type T = typeof(`expression`[`index`])
|
||||||
`expression`[`index`].some
|
try:
|
||||||
except KeyError:
|
`expression`[`index`].some
|
||||||
T.none
|
except KeyError:
|
||||||
|
T.none
|
||||||
macro `?`*(expression: typed, infix: untyped{nkInfix}): untyped =
|
|
||||||
# chain is of shape: expression?left `operator` right
|
|
||||||
let left = infix[1]
|
|
||||||
infix[1] = quote do: `expression`?`left`
|
|
||||||
infix
|
|
||||||
|
|
||||||
macro `?`*(expression: typed, bracket: untyped{nkBracketExpr}): untyped =
|
|
||||||
# chain is of shape: expression?left[right]
|
|
||||||
let left = bracket[0]
|
|
||||||
bracket[0] = quote do: `expression`?`left`
|
|
||||||
bracket
|
|
||||||
|
|
||||||
macro `?`*(expression: typed, dot: untyped{nkDotExpr}): untyped =
|
|
||||||
# chain is of shape: expression?left.right
|
|
||||||
let left = dot[0]
|
|
||||||
dot[0] = quote do: `expression`?`left`
|
|
||||||
dot
|
|
||||||
|
|
||||||
macro `?`*(expression: typed, call: untyped{nkCall}): untyped =
|
|
||||||
let procedure = call[0]
|
|
||||||
if procedure.kind == nnkDotExpr:
|
|
||||||
# chain is of shape: expression?left.right(arguments)
|
|
||||||
let (left, right) = (procedure[0], procedure[1])
|
|
||||||
call[0] = right
|
|
||||||
call.insert(1, quote do: `expression`?`left`)
|
|
||||||
call
|
|
||||||
else:
|
|
||||||
call.expectKind(nnkBracketExpr)
|
|
||||||
nil
|
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
template liftUnary*(T: type, operator: untyped) =
|
template liftUnary*(T: type, operator: untyped) =
|
||||||
|
|
||||||
template `operator`*(a: T): untyped =
|
template `operator`*(a: T): untyped =
|
||||||
a ->? `operator`(a.unsafeGet())
|
block:
|
||||||
|
let evaluated = a
|
||||||
|
evaluated ->? `operator`(evaluated.unsafeGet())
|
||||||
|
|
||||||
template liftBinary*(T: type, operator: untyped) =
|
template liftBinary*(T: type, operator: untyped) =
|
||||||
|
|
||||||
template `operator`*(a: T, b: T): untyped =
|
template `operator`*(a: T, b: T): untyped =
|
||||||
(a, b) ->? `operator`(a.unsafeGet, b.unsafeGet)
|
block:
|
||||||
|
let evalA = a
|
||||||
|
let evalB = b
|
||||||
|
(evalA, evalB) ->? `operator`(evalA.unsafeGet, evalB.unsafeGet)
|
||||||
|
|
||||||
template `operator`*(a: T, b: typed): untyped =
|
template `operator`*(a: T, b: typed): untyped =
|
||||||
a ->? `operator`(a.unsafeGet(), b)
|
block:
|
||||||
|
let evalA = a
|
||||||
|
evalA ->? `operator`(evalA.unsafeGet(), b)
|
||||||
|
|||||||
@ -1,50 +1,69 @@
|
|||||||
import std/options
|
import std/options
|
||||||
import std/macros
|
import std/macros
|
||||||
|
import ./binding
|
||||||
import ./chaining
|
import ./chaining
|
||||||
import ./indexing
|
import ./indexing
|
||||||
import ./operators
|
import ./operators
|
||||||
|
import ./without
|
||||||
|
|
||||||
include ./errorban
|
include ./private/errorban
|
||||||
|
|
||||||
export options
|
export options except get
|
||||||
|
export binding
|
||||||
export chaining
|
export chaining
|
||||||
export indexing
|
export indexing
|
||||||
|
export without
|
||||||
|
|
||||||
template `?`*(T: typed): type Option[T] =
|
template `?`*(T: typed): type Option[T] =
|
||||||
|
## Use `?` to make a type optional. For example the type `?int` is short for
|
||||||
|
## `Option[int]`.
|
||||||
|
|
||||||
Option[T]
|
Option[T]
|
||||||
|
|
||||||
template `->?`*[T,U](option: ?T, expression: U): ?U =
|
template `!`*[T](option: ?T): T =
|
||||||
|
## Returns the value of an Option when you're absolutely sure that it
|
||||||
|
## contains value. Using `!` on an Option without a value raises a Defect.
|
||||||
|
|
||||||
|
option.get
|
||||||
|
|
||||||
|
template `->?`*[T,U](option: ?T, expression: ?U): ?U =
|
||||||
if option.isSome:
|
if option.isSome:
|
||||||
expression.some
|
expression
|
||||||
else:
|
else:
|
||||||
U.none
|
U.none
|
||||||
|
|
||||||
template `->?`*[T,U,V](options: (?T, ?U), expression: V): ?V =
|
template `->?`*[T,U](option: ?T, expression: U): ?U =
|
||||||
|
option ->? expression.some
|
||||||
|
|
||||||
|
template `->?`*[T,U,V](options: (?T, ?U), expression: ?V): ?V =
|
||||||
if options[0].isSome and options[1].isSome:
|
if options[0].isSome and options[1].isSome:
|
||||||
expression.some
|
expression
|
||||||
else:
|
else:
|
||||||
V.none
|
V.none
|
||||||
|
|
||||||
template `=?`*[T](name: untyped{nkIdent}, expression: ?T): bool =
|
template `->?`*[T,U,V](options: (?T, ?U), expression: V): ?V =
|
||||||
let option = expression
|
options ->? expression.some
|
||||||
template name: T {.used.} = option.unsafeGet()
|
|
||||||
option.isSome
|
|
||||||
|
|
||||||
macro `=?`*[T](variable: untyped{nkVarTy}, expression: ?T): bool =
|
proc `|?`*[T](option: ?T, fallback: T): T =
|
||||||
let name = variable[0]
|
## Use the `|?` operator to supply a fallback value when an Option does not
|
||||||
quote do:
|
## hold a value.
|
||||||
let option = `expression`
|
|
||||||
var `name` : typeof(option.unsafeGet())
|
|
||||||
if option.isSome:
|
|
||||||
`name` = option.unsafeGet()
|
|
||||||
option.isSome
|
|
||||||
|
|
||||||
template `|?`*[T](option: ?T, fallback: T): T =
|
|
||||||
if option.isSome:
|
if option.isSome:
|
||||||
option.unsafeGet()
|
option.unsafeGet()
|
||||||
else:
|
else:
|
||||||
fallback
|
fallback
|
||||||
|
|
||||||
|
macro `.?`*[T](option: ?T, brackets: untyped{nkBracket}): untyped =
|
||||||
|
let index = brackets[0]
|
||||||
|
quote do:
|
||||||
|
block:
|
||||||
|
let evaluated = `option`
|
||||||
|
type U = typeof(evaluated.unsafeGet().?[`index`].unsafeGet())
|
||||||
|
if evaluated.isSome:
|
||||||
|
evaluated.unsafeGet().?[`index`]
|
||||||
|
else:
|
||||||
|
U.none
|
||||||
|
|
||||||
Option.liftUnary(`-`)
|
Option.liftUnary(`-`)
|
||||||
Option.liftUnary(`+`)
|
Option.liftUnary(`+`)
|
||||||
Option.liftUnary(`@`)
|
Option.liftUnary(`@`)
|
||||||
|
|||||||
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.}
|
||||||
54
questionable/private/binderror.nim
Normal file
54
questionable/private/binderror.nim
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import std/options
|
||||||
|
import std/macros
|
||||||
|
|
||||||
|
# A stack of names of error variables. Keeps track of the error variables that
|
||||||
|
# are given to captureBindError().
|
||||||
|
var errorVariableNames {.global, compileTime.}: seq[string]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
# name of the error variable as a string literal
|
||||||
|
let errorVariableName = newLit($error)
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
|
func unsafeCatchableError[T](_: Option[T]): ref CatchableError =
|
||||||
|
newException(ValueError, "Option is set to `none`")
|
||||||
|
|
||||||
|
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
|
||||||
8
questionable/private/errorban.nim
Normal file
8
questionable/private/errorban.nim
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
## Include this file to indicate that your module does not raise Errors.
|
||||||
|
## Disables compiler hints about unused declarations in Nim < 1.4.0
|
||||||
|
|
||||||
|
when (NimMajor, NimMinor, NimPatch) >= (1, 4, 0):
|
||||||
|
{.push raises:[].}
|
||||||
|
else:
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
{.hint[XDeclaredButNotUsed]: off.}
|
||||||
@ -1,62 +1,147 @@
|
|||||||
import std/macros
|
import std/macros
|
||||||
import ./resultsbase
|
import ./resultsbase
|
||||||
import ./options
|
import ./options
|
||||||
|
import ./binding
|
||||||
|
import ./chaining
|
||||||
|
import ./indexing
|
||||||
import ./operators
|
import ./operators
|
||||||
|
import ./without
|
||||||
|
import ./withoutresult
|
||||||
|
import ./private/bareexcept
|
||||||
|
|
||||||
include ./errorban
|
include ./private/errorban
|
||||||
|
|
||||||
export resultsbase
|
export resultsbase except ok, err, isOk, isErr, get
|
||||||
|
export binding
|
||||||
|
export chaining
|
||||||
|
export indexing
|
||||||
|
export without
|
||||||
|
export withoutresult
|
||||||
|
|
||||||
type ResultFailure* = object of CatchableError
|
type ResultFailure* = object of CatchableError
|
||||||
|
|
||||||
template `?!`*(T: typed): type Result[T, ref CatchableError] =
|
template `?!`*(T: typed): type Result[T, ref CatchableError] =
|
||||||
|
## Use `?!` 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]`.
|
||||||
|
|
||||||
Result[T, ref CatchableError]
|
Result[T, ref CatchableError]
|
||||||
|
|
||||||
|
template `!`*[T](value: ?!T): T =
|
||||||
|
## Returns the value of a Result when you're absolutely sure that it
|
||||||
|
## contains value. Using `!` on a Result without a value raises a Defect.
|
||||||
|
|
||||||
|
value.get
|
||||||
|
|
||||||
proc success*[T](value: T): ?!T =
|
proc success*[T](value: T): ?!T =
|
||||||
|
## Creates a successfull Result containing the value.
|
||||||
|
##
|
||||||
ok(?!T, value)
|
ok(?!T, value)
|
||||||
|
|
||||||
|
proc success*: ?!void =
|
||||||
|
## Creates a successfull Result without a value.
|
||||||
|
|
||||||
|
ok(?!void)
|
||||||
|
|
||||||
proc failure*(T: type, error: ref CatchableError): ?!T =
|
proc failure*(T: type, error: ref CatchableError): ?!T =
|
||||||
|
## Creates a failed Result containing the error.
|
||||||
|
|
||||||
err(?!T, error)
|
err(?!T, error)
|
||||||
|
|
||||||
proc failure*(T: type, message: string): ?!T =
|
proc failure*(T: type, message: string): ?!T =
|
||||||
|
## Creates a failed Result containing a `ResultFailure` with the specified
|
||||||
|
## error message.
|
||||||
|
|
||||||
T.failure newException(ResultFailure, message)
|
T.failure newException(ResultFailure, message)
|
||||||
|
|
||||||
template `->?`*[T,U](value: ?!T, expression: U): ?!U =
|
template failure*(error: ref CatchableError): auto =
|
||||||
if value.isErr:
|
## Creates a failed Result containing the error.
|
||||||
U.failure(value.error)
|
|
||||||
else:
|
|
||||||
expression.success
|
|
||||||
|
|
||||||
template `->?`*[T,U,V](values: (?!T, ?!U), expression: V): ?!V =
|
err error
|
||||||
if values[0].isErr:
|
|
||||||
V.failure(values[0].error)
|
|
||||||
elif values[1].isErr:
|
|
||||||
V.failure(values[1].error)
|
|
||||||
else:
|
|
||||||
expression.success
|
|
||||||
|
|
||||||
template `|?`*[T](value: ?!T, fallback: T): T =
|
template failure*(message: string): auto =
|
||||||
value.valueOr(fallback)
|
## Creates a failed Result containing the error.
|
||||||
|
|
||||||
|
failure newException(ResultFailure, message)
|
||||||
|
|
||||||
|
proc isSuccess*[T](value: ?!T): bool =
|
||||||
|
## Returns true when the Result contains a value.
|
||||||
|
|
||||||
template `=?`*[T](name: untyped{nkIdent}, expression: ?!T): bool =
|
|
||||||
let value = expression
|
|
||||||
template name: T {.used.} = value.unsafeGet()
|
|
||||||
value.isOk
|
value.isOk
|
||||||
|
|
||||||
macro `=?`*[T](variable: untyped{nkVarTy}, expression: ?!T): bool =
|
proc isFailure*[T](value: ?!T): bool =
|
||||||
let name = variable[0]
|
## Returns true when the Result contains an error.
|
||||||
quote do:
|
|
||||||
let value = `expression`
|
value.isErr
|
||||||
var `name` : typeof(value.unsafeGet())
|
|
||||||
if value.isOk:
|
proc `$`*[T](value: ?!T): string =
|
||||||
`name` = value.unsafeGet()
|
if value.isSuccess:
|
||||||
value.isOk
|
"success(" & $(!value) & ")"
|
||||||
|
else:
|
||||||
|
"failure(\"" & $(value.error.msg) & "\")"
|
||||||
|
|
||||||
|
template `->?`*[T,U](value: ?!T, expression: ?!U): ?!U =
|
||||||
|
if value.isFailure:
|
||||||
|
U.failure(value.error)
|
||||||
|
else:
|
||||||
|
expression
|
||||||
|
|
||||||
|
template `->?`*[T,U](value: ?!T, expression: U): ?!U =
|
||||||
|
value ->? expression.success
|
||||||
|
|
||||||
|
template `->?`*[T,U,V](values: (?!T, ?!U), expression: ?!V): ?!V =
|
||||||
|
if values[0].isFailure:
|
||||||
|
V.failure(values[0].error)
|
||||||
|
elif values[1].isFailure:
|
||||||
|
V.failure(values[1].error)
|
||||||
|
else:
|
||||||
|
expression
|
||||||
|
|
||||||
|
template `->?`*[T,U,V](values: (?!T, ?!U), expression: V): ?!V =
|
||||||
|
values ->? expression.success
|
||||||
|
|
||||||
|
proc `|?`*[T,E](value: Result[T,E], fallback: T): T =
|
||||||
|
## Use the `|?` operator to supply a fallback value when a Result does not
|
||||||
|
## hold a value.
|
||||||
|
|
||||||
|
value.valueOr(fallback)
|
||||||
|
|
||||||
proc option*[T,E](value: Result[T,E]): ?T =
|
proc option*[T,E](value: Result[T,E]): ?T =
|
||||||
|
## Converts a Result into an Option.
|
||||||
|
|
||||||
if value.isOk:
|
if value.isOk:
|
||||||
value.unsafeGet.some
|
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:
|
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.
|
||||||
|
|
||||||
|
if value.isErr:
|
||||||
|
value.error.some
|
||||||
|
else:
|
||||||
|
E.none
|
||||||
|
|
||||||
Result.liftUnary(`-`)
|
Result.liftUnary(`-`)
|
||||||
Result.liftUnary(`+`)
|
Result.liftUnary(`+`)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
template tryImport(module) = import module
|
template tryImport(module) = import module
|
||||||
|
|
||||||
when compiles tryimport pkg/result:
|
when compiles tryImport pkg/results:
|
||||||
import pkg/result/../results
|
import pkg/results
|
||||||
else:
|
else:
|
||||||
import pkg/stew/results
|
import pkg/stew/results
|
||||||
|
|
||||||
|
|||||||
6
questionable/without.nim
Normal file
6
questionable/without.nim
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
template without*(expression, body) =
|
||||||
|
## Used to place guards that ensure that an Option or Result contains a value.
|
||||||
|
|
||||||
|
let ok = expression
|
||||||
|
if not ok:
|
||||||
|
body
|
||||||
41
questionable/withoutresult.nim
Normal file
41
questionable/withoutresult.nim
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import std/macros
|
||||||
|
import ./without
|
||||||
|
import ./private/binderror
|
||||||
|
|
||||||
|
const symbolKinds = {nnkSym, nnkOpenSymChoice, nnkClosedSymChoice}
|
||||||
|
const identKinds = {nnkIdent} + symbolKinds
|
||||||
|
|
||||||
|
proc undoSymbolResolution(expression, ident: NimNode): NimNode =
|
||||||
|
## Finds symbols in the expression that match the `ident` and replaces them
|
||||||
|
## with `ident`, effectively undoing any symbol resolution that happened
|
||||||
|
## before.
|
||||||
|
|
||||||
|
if expression.kind in symbolKinds and eqIdent($expression, $ident):
|
||||||
|
return ident
|
||||||
|
|
||||||
|
let expression = expression.copyNimTree()
|
||||||
|
for i in 0..<expression.len:
|
||||||
|
expression[i] = undoSymbolResolution(expression[i], ident)
|
||||||
|
|
||||||
|
expression
|
||||||
|
|
||||||
|
macro without*(condition, errorname, body: untyped): untyped =
|
||||||
|
## Used to place guards that ensure that a Result contains a value.
|
||||||
|
## Exposes error when Result does not contain a value.
|
||||||
|
|
||||||
|
if errorname.kind notin identKinds:
|
||||||
|
error("expected an identifier, got " & errorname.repr, errorname)
|
||||||
|
|
||||||
|
let errorIdent = ident $errorname
|
||||||
|
|
||||||
|
# Nim's early symbol resolution might have picked up a symbol with the
|
||||||
|
# same name as our error variable. We need to undo this to make sure that our
|
||||||
|
# error variable is seen.
|
||||||
|
let body = body.undoSymbolResolution(errorIdent)
|
||||||
|
|
||||||
|
quote do:
|
||||||
|
var error {.gensym.}: ref CatchableError
|
||||||
|
|
||||||
|
without captureBindError(error, `condition`):
|
||||||
|
template `errorIdent`: ref CatchableError = error
|
||||||
|
`body`
|
||||||
2
testmodules/options/config.nims
Normal file
2
testmodules/options/config.nims
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
--path:"../.."
|
||||||
|
import "../../config.nims"
|
||||||
@ -1 +0,0 @@
|
|||||||
--path:"../.."
|
|
||||||
@ -11,28 +11,49 @@ suite "optionals":
|
|||||||
check (?string is Option[string])
|
check (?string is Option[string])
|
||||||
check (?seq[bool] is Option[seq[bool]])
|
check (?seq[bool] is Option[seq[bool]])
|
||||||
|
|
||||||
test "?. can be used for chaining optionals":
|
test "! gets value or raises Defect":
|
||||||
|
check !42.some == 42
|
||||||
|
expect Defect: discard !int.none
|
||||||
|
|
||||||
|
test ".? can be used for chaining optionals":
|
||||||
let a: ?seq[int] = @[41, 42].some
|
let a: ?seq[int] = @[41, 42].some
|
||||||
let b: ?seq[int] = seq[int].none
|
let b: ?seq[int] = seq[int].none
|
||||||
check a?.len == 2.some
|
check a.?len == 2.some
|
||||||
check b?.len == int.none
|
check b.?len == int.none
|
||||||
check a?.len?.uint8 == 2'u8.some
|
check a.?len.?uint8 == 2'u8.some
|
||||||
check b?.len?.uint8 == uint8.none
|
check b.?len.?uint8 == uint8.none
|
||||||
check a?.len() == 2.some
|
check a.?len() == 2.some
|
||||||
check b?.len() == int.none
|
check b.?len() == int.none
|
||||||
check a?.distribute(2)?.len() == 2.some
|
check a.?distribute(2).?len() == 2.some
|
||||||
check b?.distribute(2)?.len() == int.none
|
check b.?distribute(2).?len() == int.none
|
||||||
|
|
||||||
test "?. chain can be followed by . calls and operators":
|
test ".? chain can be followed by . calls and operators":
|
||||||
let a = @[41, 42].some
|
let a = @[41, 42].some
|
||||||
check a?.len.get == 2
|
check a.?len.unsafeGet == 2
|
||||||
check a?.len.get.uint8.uint64 == 2'u64
|
check a.?len.unsafeGet.uint8.uint64 == 2'u64
|
||||||
check a?.len.get() == 2
|
check a.?len.unsafeGet() == 2
|
||||||
check a?.len.get().uint8.uint64 == 2'u64
|
check a.?len.unsafeGet().uint8.uint64 == 2'u64
|
||||||
check a?.deduplicate()[0]?.uint8?.uint64 == 41'u64.some
|
check a.?deduplicate()[0].?uint8.?uint64 == 41'u64.some
|
||||||
check a?.len + 1 == 3.some
|
check a.?len + 1 == 3.some
|
||||||
check a?.deduplicate()[0] + 1 == 42.some
|
check a.?deduplicate()[0] + 1 == 42.some
|
||||||
check a?.deduplicate.map(x => x) == @[41, 42].some
|
check a.?deduplicate.map(x => x) == @[41, 42].some
|
||||||
|
|
||||||
|
test ".? chains work in generic code":
|
||||||
|
proc test[T](a: ?T) =
|
||||||
|
check a.?len == 2.some
|
||||||
|
check a.?len.?uint8 == 2'u8.some
|
||||||
|
check a.?len() == 2.some
|
||||||
|
check a.?distribute(2).?len() == 2.some
|
||||||
|
check a.?len.unsafeGet == 2
|
||||||
|
check a.?len.unsafeGet.uint8.uint64 == 2'u64
|
||||||
|
check a.?len.unsafeGet() == 2
|
||||||
|
check a.?len.unsafeGet().uint8.uint64 == 2'u64
|
||||||
|
check a.?deduplicate()[0].?uint8.?uint64 == 41'u64.some
|
||||||
|
check a.?len + 1 == 3.some
|
||||||
|
check a.?deduplicate()[0] + 1 == 42.some
|
||||||
|
check a.?deduplicate.map(x => x) == @[41, 42].some
|
||||||
|
|
||||||
|
test @[41, 42].some
|
||||||
|
|
||||||
test "[] can be used for indexing optionals":
|
test "[] can be used for indexing optionals":
|
||||||
let a: ?seq[int] = @[1, 2, 3].some
|
let a: ?seq[int] = @[1, 2, 3].some
|
||||||
@ -88,36 +109,278 @@ suite "optionals":
|
|||||||
if var a =? int.none:
|
if var a =? int.none:
|
||||||
fail
|
fail
|
||||||
|
|
||||||
test "?[] can be used for indexing tables without raising KeyError":
|
test "=? works with .?":
|
||||||
let table = @{"a": 1, "b": 2}.toTable
|
if a =? 42.some.?uint8:
|
||||||
check table?["a"] == 1.some
|
check a == 42.uint8
|
||||||
check table?["c"] == int.none
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
test "?[] can be followed by calls, operators and indexing":
|
test "=? works in generic code":
|
||||||
let table = @{"a": @[41, 42]}.toTable
|
proc toString[T](option: ?T): string =
|
||||||
check table?["a"].isSome
|
if value =? option:
|
||||||
check table?["a"].isSome()
|
$value
|
||||||
check table?["a"][0] == 41.some
|
else:
|
||||||
check table?["a"]?.len.get == 2
|
"none"
|
||||||
check table?["a"]?.len.get.uint8.uint64 == 2'u64
|
|
||||||
check table?["a"]?.len.get() == 2
|
|
||||||
check table?["a"]?.len.get().uint8.uint64 == 2'u64
|
|
||||||
check table?["a"]?.deduplicate()[0]?.uint8?.uint64 == 41'u64.some
|
|
||||||
check table?["a"]?.len + 1 == 3.some
|
|
||||||
check table?["a"]?.deduplicate()[0] + 1 == 42.some
|
|
||||||
check table?["a"]?.deduplicate.map(x => x) == @[41, 42].some
|
|
||||||
|
|
||||||
test "=? evaluates optional expression only once":
|
check 42.some.toString == "42"
|
||||||
|
check int.none.toString == "none"
|
||||||
|
|
||||||
|
test "=? works in generic code with variable hiding":
|
||||||
|
let value {.used.} = "ignored"
|
||||||
|
|
||||||
|
proc toString[T](option: ?T): string =
|
||||||
|
if value =? option:
|
||||||
|
$value
|
||||||
|
else:
|
||||||
|
"none"
|
||||||
|
|
||||||
|
check 42.some.toString == "42"
|
||||||
|
check int.none.toString == "none"
|
||||||
|
|
||||||
|
test "=? works with closures":
|
||||||
|
var called = false
|
||||||
|
let closure = some(proc () = called = true)
|
||||||
|
|
||||||
|
if a =? none(proc ()):
|
||||||
|
a()
|
||||||
|
|
||||||
|
check not called
|
||||||
|
|
||||||
|
if a =? closure:
|
||||||
|
a()
|
||||||
|
|
||||||
|
check called
|
||||||
|
|
||||||
|
test "=? works with types that do not have a default value":
|
||||||
|
type NoDefault {.requiresInit.} = distinct int
|
||||||
|
proc `==`(a,b: NoDefault): bool {.borrow.}
|
||||||
|
|
||||||
|
if a =? some NoDefault(42):
|
||||||
|
check a == NoDefault(42)
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
if var a =? some NoDefault(42):
|
||||||
|
check a == NoDefault(42)
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? works with reference types":
|
||||||
|
var x = new int
|
||||||
|
x[] = 42
|
||||||
|
if a =? x:
|
||||||
|
check a[] == 42
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
x = nil
|
||||||
|
if a =? x:
|
||||||
|
fail
|
||||||
|
|
||||||
|
var p = proc = discard
|
||||||
|
if a =? p:
|
||||||
|
a()
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
p = nil
|
||||||
|
if a =? p:
|
||||||
|
fail
|
||||||
|
|
||||||
|
when (NimMajor, NimMinor) >= (1, 1) and (NimMajor, NimMinor) != (2, 0):
|
||||||
|
var it = iterator: int = yield 2
|
||||||
|
if a =? it:
|
||||||
|
for x in a:
|
||||||
|
check x == 2
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
it = nil
|
||||||
|
if a =? it:
|
||||||
|
fail
|
||||||
|
|
||||||
|
test "=? rejects non-reference types":
|
||||||
|
check `not` compiles do:
|
||||||
|
if a =? 0:
|
||||||
|
discard
|
||||||
|
check `not` compiles do:
|
||||||
|
if var a =? 0:
|
||||||
|
discard
|
||||||
|
check `not` compiles do:
|
||||||
|
if (a,) =? (0,):
|
||||||
|
discard
|
||||||
|
|
||||||
|
test "=? works with custom optional types":
|
||||||
|
type MyOption = distinct int
|
||||||
|
proc isSome(x: MyOption): bool = x.int >= 0
|
||||||
|
proc unsafeGet(x: MyOption): int = x.int
|
||||||
|
template toOption(x: MyOption): MyOption = x
|
||||||
|
|
||||||
|
if a =? MyOption 42:
|
||||||
|
check a == 42
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
if a =? MyOption -1:
|
||||||
|
fail
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples":
|
||||||
|
if (a, b) =? (some ("test", 1)):
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
if (a, b) =? none (string, int):
|
||||||
|
discard a
|
||||||
|
discard b
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples with named fields":
|
||||||
|
if (a, b) =? (some (desc: "test", id: 1)):
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples returned from proc":
|
||||||
|
proc returnsTuple(): ?tuple[name: string, id: int] = some ("test", 1)
|
||||||
|
|
||||||
|
if (a, b) =? returnsTuple():
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples returned from proc with unnamed fields":
|
||||||
|
proc returnsTuple(): ?(string, int,) = some ("test", 1,)
|
||||||
|
|
||||||
|
if (a, b,) =? returnsTuple():
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples with _":
|
||||||
|
if (_, b) =? some ("test", 1):
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples with named fields":
|
||||||
|
if (a, b) =? some (desc: "test", id: 1):
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds variable to tuples with named fields":
|
||||||
|
if t =? some (desc: "test", id: 1):
|
||||||
|
check t.desc == "test"
|
||||||
|
check t.id == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds to tuple types":
|
||||||
|
type MyTuple = tuple
|
||||||
|
desc: string
|
||||||
|
id: int
|
||||||
|
|
||||||
|
let mt: MyTuple = ("test", 1)
|
||||||
|
|
||||||
|
if t =? (some mt):
|
||||||
|
check t.desc == "test"
|
||||||
|
check t.id == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
if (a, b) =? (some mt):
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? for tuples does not leak symbols into caller's scope":
|
||||||
|
const evaluated = ""
|
||||||
|
type T = string
|
||||||
|
if (a,) =? some (0,):
|
||||||
|
check a == 0
|
||||||
|
check option is proc
|
||||||
|
check evaluated is string
|
||||||
|
check T is string
|
||||||
|
|
||||||
|
test "without statement can be used for early returns":
|
||||||
|
proc test1 =
|
||||||
|
without a =? 42.some:
|
||||||
|
fail
|
||||||
|
return
|
||||||
|
check a == 42
|
||||||
|
|
||||||
|
proc test2 =
|
||||||
|
without a =? int.none:
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
test1()
|
||||||
|
test2()
|
||||||
|
|
||||||
|
test "without statement evaluates optional expression only once":
|
||||||
var count = 0
|
var count = 0
|
||||||
if a =? (inc count; 42.some):
|
without a =? (inc count; 42.some):
|
||||||
let b {.used.} = a
|
discard
|
||||||
check count == 1
|
check count == 1
|
||||||
|
|
||||||
count = 0
|
test ".?[] can be used for indexing tables without raising KeyError":
|
||||||
if var a =? (inc count; 42.some):
|
let table = @{"a": 1, "b": 2}.toTable
|
||||||
let b {.used.} = a
|
check table.?["a"] == 1.some
|
||||||
|
check table.?["c"] == int.none
|
||||||
|
|
||||||
|
test ".?[] can be used for indexing strings without raising IndexDefect":
|
||||||
|
let str = "a"
|
||||||
|
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
|
check count == 1
|
||||||
|
|
||||||
|
test ".?[] can be followed by calls, operators and indexing":
|
||||||
|
let table = @{"a": @[41, 42]}.toTable
|
||||||
|
check table.?["a"].isSome
|
||||||
|
check table.?["a"].isSome()
|
||||||
|
check table.?["a"][0] == 41.some
|
||||||
|
check table.?["a"].?len.unsafeGet == 2
|
||||||
|
check table.?["a"].?len.unsafeGet.uint8.uint64 == 2'u64
|
||||||
|
check table.?["a"].?len.unsafeGet() == 2
|
||||||
|
check table.?["a"].?len.unsafeGet().uint8.uint64 == 2'u64
|
||||||
|
check table.?["a"].?deduplicate()[0].?uint8.?uint64 == 41'u64.some
|
||||||
|
check table.?["a"].?len + 1 == 3.some
|
||||||
|
check table.?["a"].?deduplicate()[0] + 1 == 42.some
|
||||||
|
check table.?["a"].?deduplicate.map(x => x) == @[41, 42].some
|
||||||
|
|
||||||
|
test "=? works with .?[]":
|
||||||
|
let table = @{"a": 42}.toTable
|
||||||
|
if a =? table.?["a"]:
|
||||||
|
check a == 42
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
test "unary operator `-` works for options":
|
test "unary operator `-` works for options":
|
||||||
check -(-42.some) == 42.some
|
check -(-42.some) == 42.some
|
||||||
check -(int.none) == int.none
|
check -(int.none) == int.none
|
||||||
@ -147,6 +410,80 @@ suite "optionals":
|
|||||||
check 40.some >= 42 == false.some
|
check 40.some >= 42 == false.some
|
||||||
check 40.some > 42 == false.some
|
check 40.some > 42 == false.some
|
||||||
|
|
||||||
|
test ".? avoids wrapping option in option":
|
||||||
|
let a = 41.some
|
||||||
|
|
||||||
|
proc b(x: int): ?int =
|
||||||
|
some x + 1
|
||||||
|
|
||||||
|
check a.?b == 42.some
|
||||||
|
|
||||||
|
test "lifted operators avoid wrapping option in option":
|
||||||
|
let a = 40.some
|
||||||
|
let b = 2.some
|
||||||
|
|
||||||
|
func `&`(x, y: int): ?int =
|
||||||
|
some x + y
|
||||||
|
|
||||||
|
check (a & b) == 42.some
|
||||||
|
|
||||||
|
test ".?[] avoids wrapping option in option":
|
||||||
|
let a = @[41, 42].some
|
||||||
|
|
||||||
|
check a.?[1] == 42.some
|
||||||
|
|
||||||
|
test ".? chain evaluates optional expression only once":
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; @[41, 42].some).?len
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "=? evaluates optional expression only once":
|
||||||
|
var count = 0
|
||||||
|
if a =? (inc count; 42.some):
|
||||||
|
let b {.used.} = a
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
if var a =? (inc count; 42.some):
|
||||||
|
let b {.used.} = a
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "|? evaluates optional expression only once":
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; 42.some) |? 43
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test ".?[] evaluates optional expression only once":
|
||||||
|
# indexing on optional sequence:
|
||||||
|
block:
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; @[41, 42].some).?[0]
|
||||||
|
check count == 1
|
||||||
|
# indexing on normal sequence:
|
||||||
|
block:
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; @[41, 42]).?[0]
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "lifted unary operators evaluate optional expression only once":
|
||||||
|
var count = 0
|
||||||
|
discard -(inc count; 42.some)
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "lifted binary operators evaluate optional expressions only once":
|
||||||
|
# lifted operator on two options:
|
||||||
|
block:
|
||||||
|
var count1, count2 = 0
|
||||||
|
discard (inc count1; 40.some) + (inc count2; 2.some)
|
||||||
|
check count1 == 1
|
||||||
|
check count2 == 1
|
||||||
|
# lifted operator on option and value:
|
||||||
|
block:
|
||||||
|
var count1, count2 = 0
|
||||||
|
discard (inc count1; 40.some) + (inc count2; 2)
|
||||||
|
check count1 == 1
|
||||||
|
check count2 == 1
|
||||||
|
|
||||||
test "examples from readme work":
|
test "examples from readme work":
|
||||||
|
|
||||||
var x: ?int
|
var x: ?int
|
||||||
@ -170,21 +507,33 @@ suite "optionals":
|
|||||||
else:
|
else:
|
||||||
check not compiles(y)
|
check not compiles(y)
|
||||||
|
|
||||||
|
# without statement
|
||||||
|
|
||||||
|
proc someProc(option: ?int) =
|
||||||
|
without value =? option:
|
||||||
|
check option.isNone
|
||||||
|
return
|
||||||
|
|
||||||
|
check value == 42
|
||||||
|
|
||||||
|
someProc(int.none)
|
||||||
|
someProc(42.some)
|
||||||
|
|
||||||
# Option chaining
|
# Option chaining
|
||||||
|
|
||||||
var numbers: ?seq[int]
|
var numbers: ?seq[int]
|
||||||
var amount: ?int
|
var amount: ?int
|
||||||
|
|
||||||
numbers = @[1, 2, 3].some
|
numbers = @[1, 2, 3].some
|
||||||
amount = numbers?.len
|
amount = numbers.?len
|
||||||
check amount == 3.some
|
check amount == 3.some
|
||||||
|
|
||||||
numbers = seq[int].none
|
numbers = seq[int].none
|
||||||
amount = numbers?.len
|
amount = numbers.?len
|
||||||
check amount == int.none
|
check amount == int.none
|
||||||
|
|
||||||
numbers = @[1, 1, 2, 2, 2].some
|
numbers = @[1, 1, 2, 2, 2].some
|
||||||
amount = numbers?.deduplicate?.len
|
amount = numbers.?deduplicate.?len
|
||||||
check amount == 2.some
|
check amount == 2.some
|
||||||
|
|
||||||
# Fallback values
|
# Fallback values
|
||||||
@ -194,6 +543,16 @@ suite "optionals":
|
|||||||
let z = x |? 3
|
let z = x |? 3
|
||||||
check z == 3
|
check z == 3
|
||||||
|
|
||||||
|
# Obtaining value with !
|
||||||
|
|
||||||
|
x = 42.some
|
||||||
|
let dare = !x
|
||||||
|
check dare == 42
|
||||||
|
|
||||||
|
x = int.none
|
||||||
|
expect Defect:
|
||||||
|
let crash {.used.} = !x
|
||||||
|
|
||||||
# Operators
|
# Operators
|
||||||
|
|
||||||
numbers = @[1, 2, 3].some
|
numbers = @[1, 2, 3].some
|
||||||
|
|||||||
@ -4,4 +4,8 @@ description = "Questionable tests for std/option"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
task test, "Runs the test suite":
|
task test, "Runs the test suite":
|
||||||
exec "nim c -f -r test.nim"
|
var options = "-f -r --skipParentCfg"
|
||||||
|
when (NimMajor, NimMinor) >= (1, 4):
|
||||||
|
options &= " --warningAsError[UnsafeDefault]:on"
|
||||||
|
options &= " --warningAsError[ProveInit]:on"
|
||||||
|
exec "nim c " & options & " test.nim"
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
--path:"../.."
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
import std/unittest
|
|
||||||
import std/sequtils
|
|
||||||
import std/strutils
|
|
||||||
import std/sugar
|
|
||||||
import pkg/questionable
|
|
||||||
import pkg/questionable/results
|
|
||||||
|
|
||||||
suite "result":
|
|
||||||
|
|
||||||
let error = newException(CatchableError, "error")
|
|
||||||
|
|
||||||
test "?!Type is shorthand for Result[Type, ref CatchableError]":
|
|
||||||
check (?!int is Result[int, ref CatchableError])
|
|
||||||
check (?!string is Result[string, ref CatchableError])
|
|
||||||
check (?!seq[bool] is Result[seq[bool], ref CatchableError])
|
|
||||||
|
|
||||||
test "?. can be used for chaining results":
|
|
||||||
let a: ?!seq[int] = @[41, 42].success
|
|
||||||
let b: ?!seq[int] = seq[int].failure error
|
|
||||||
check a?.len == 2.success
|
|
||||||
check b?.len == int.failure error
|
|
||||||
check a?.len?.uint8 == 2'u8.success
|
|
||||||
check b?.len?.uint8 == uint8.failure error
|
|
||||||
check a?.len() == 2.success
|
|
||||||
check b?.len() == int.failure error
|
|
||||||
check a?.distribute(2)?.len() == 2.success
|
|
||||||
check b?.distribute(2)?.len() == int.failure error
|
|
||||||
|
|
||||||
test "?. chain can be followed by . calls and operators":
|
|
||||||
let a = @[41, 42].success
|
|
||||||
check (a?.len.get == 2)
|
|
||||||
check (a?.len.get.uint8.uint64 == 2'u64)
|
|
||||||
check (a?.len.get() == 2)
|
|
||||||
check (a?.len.get().uint8.uint64 == 2'u64)
|
|
||||||
check (a?.deduplicate()[0]?.uint8?.uint64 == 41'u64.success)
|
|
||||||
check (a?.len + 1 == 3.success)
|
|
||||||
check (a?.deduplicate()[0] + 1 == 42.success)
|
|
||||||
check (a?.deduplicate.map(x => x) == @[41, 42].success)
|
|
||||||
|
|
||||||
test "[] can be used for indexing optionals":
|
|
||||||
let a: ?!seq[int] = @[1, 2, 3].success
|
|
||||||
let b: ?!seq[int] = seq[int].failure error
|
|
||||||
check a[1] == 2.success
|
|
||||||
check a[^1] == 3.success
|
|
||||||
check a[0..1] == @[1, 2].success
|
|
||||||
check b[1] == int.failure error
|
|
||||||
|
|
||||||
test "|? can be used to specify a fallback value":
|
|
||||||
check 42.success |? 40 == 42
|
|
||||||
check int.failure(error) |? 42 == 42
|
|
||||||
|
|
||||||
test "=? can be used for optional binding":
|
|
||||||
if a =? int.failure(error):
|
|
||||||
fail
|
|
||||||
|
|
||||||
if b =? 42.success:
|
|
||||||
check b == 42
|
|
||||||
else:
|
|
||||||
fail
|
|
||||||
|
|
||||||
while a =? 42.success:
|
|
||||||
check a == 42
|
|
||||||
break
|
|
||||||
|
|
||||||
while a =? int.failure(error):
|
|
||||||
fail
|
|
||||||
break
|
|
||||||
|
|
||||||
test "=? can appear multiple times in conditional expression":
|
|
||||||
if a =? 42.success and b =? "foo".success:
|
|
||||||
check a == 42
|
|
||||||
check b == "foo"
|
|
||||||
else:
|
|
||||||
fail
|
|
||||||
|
|
||||||
test "=? works with variable hiding":
|
|
||||||
let a = 42.success
|
|
||||||
if a =? a:
|
|
||||||
check a == 42
|
|
||||||
|
|
||||||
test "=? works with var":
|
|
||||||
if var a =? 1.success and var b =? 2.success:
|
|
||||||
check a == 1
|
|
||||||
inc a
|
|
||||||
check a == b
|
|
||||||
inc b
|
|
||||||
check b == 3
|
|
||||||
else:
|
|
||||||
fail
|
|
||||||
|
|
||||||
if var a =? int.failure(error):
|
|
||||||
fail
|
|
||||||
|
|
||||||
test "=? evaluates optional expression only once":
|
|
||||||
var count = 0
|
|
||||||
if a =? (inc count; 42.success):
|
|
||||||
let b {.used.} = a
|
|
||||||
check count == 1
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
if var a =? (inc count; 42.success):
|
|
||||||
let b {.used.} = a
|
|
||||||
check count == 1
|
|
||||||
|
|
||||||
test "catch can be used to convert exceptions to results":
|
|
||||||
check parseInt("42").catch == 42.success
|
|
||||||
check parseInt("foo").catch.error of ValueError
|
|
||||||
|
|
||||||
test "failure can be called with string argument":
|
|
||||||
let value = int.failure("some failure")
|
|
||||||
check value.error of ResultFailure
|
|
||||||
check value.error.msg == "some failure"
|
|
||||||
|
|
||||||
test "unary operator `-` works for results":
|
|
||||||
check -(-42.success) == 42.success
|
|
||||||
check -(int.failure(error)) == int.failure(error)
|
|
||||||
|
|
||||||
test "other unary operators work for results":
|
|
||||||
check +(42.success) == 42.success
|
|
||||||
check @([1, 2].success) == (@[1, 2]).success
|
|
||||||
|
|
||||||
test "binary operator `+` works for results":
|
|
||||||
check 40.success + 2.success == 42.success
|
|
||||||
check 40.success + 2 == 42.success
|
|
||||||
check int.failure(error) + 2 == int.failure(error)
|
|
||||||
check 40.success + int.failure(error) == int.failure(error)
|
|
||||||
check int.failure(error) + int.failure(error) == int.failure(error)
|
|
||||||
|
|
||||||
test "other binary operators work for results":
|
|
||||||
check (21.success * 2 == 42.success)
|
|
||||||
check (84'f.success / 2'f == 42'f.success)
|
|
||||||
check (84.success div 2 == 42.success)
|
|
||||||
check (85.success mod 43 == 42.success)
|
|
||||||
check (0b00110011.success shl 1 == 0b01100110.success)
|
|
||||||
check (0b00110011.success shr 1 == 0b00011001.success)
|
|
||||||
check (44.success - 2 == 42.success)
|
|
||||||
check ("f".success & "oo" == "foo".success)
|
|
||||||
check (40.success <= 42 == true.success)
|
|
||||||
check (40.success < 42 == true.success)
|
|
||||||
check (40.success >= 42 == false.success)
|
|
||||||
check (40.success > 42 == false.success)
|
|
||||||
|
|
||||||
test "Result can be converted to Option":
|
|
||||||
check 42.success.option == 42.some
|
|
||||||
check int.failure(error).option == int.none
|
|
||||||
|
|
||||||
test "examples from readme work":
|
|
||||||
|
|
||||||
proc works: ?!seq[int] =
|
|
||||||
@[1, 1, 2, 2, 2].success
|
|
||||||
|
|
||||||
proc fails: ?!seq[int] =
|
|
||||||
seq[int].failure newException(ValueError, "something went wrong")
|
|
||||||
|
|
||||||
# binding:
|
|
||||||
if x =? works():
|
|
||||||
check x == @[1, 1, 2, 2, 2]
|
|
||||||
else:
|
|
||||||
fail
|
|
||||||
|
|
||||||
# chaining:
|
|
||||||
let amount = works()?.deduplicate?.len
|
|
||||||
check (amount == 2.success)
|
|
||||||
|
|
||||||
# fallback values:
|
|
||||||
let value = fails() |? @[]
|
|
||||||
check (value == newSeq[int](0))
|
|
||||||
|
|
||||||
# lifted operators:
|
|
||||||
let sum = works()[3] + 40
|
|
||||||
check (sum == 42.success)
|
|
||||||
|
|
||||||
# catch
|
|
||||||
let x = parseInt("42").catch
|
|
||||||
check (x == 42.success)
|
|
||||||
let y = parseInt("XX").catch
|
|
||||||
check y.isErr
|
|
||||||
|
|
||||||
# Conversion to Option
|
|
||||||
|
|
||||||
let converted = works().option
|
|
||||||
check (converted == @[1, 1, 2, 2, 2].some)
|
|
||||||
3
testmodules/results/config.nims
Normal file
3
testmodules/results/config.nims
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
--path:"../.."
|
||||||
|
--threads:on
|
||||||
|
import "../../config.nims"
|
||||||
693
testmodules/results/test.nim
Normal file
693
testmodules/results/test.nim
Normal file
@ -0,0 +1,693 @@
|
|||||||
|
import std/unittest
|
||||||
|
import std/options
|
||||||
|
import std/sequtils
|
||||||
|
import std/strutils
|
||||||
|
import std/sugar
|
||||||
|
import std/threadpool
|
||||||
|
import pkg/questionable/results
|
||||||
|
|
||||||
|
{.experimental: "parallel".}
|
||||||
|
|
||||||
|
suite "result":
|
||||||
|
|
||||||
|
let error = newException(CatchableError, "error")
|
||||||
|
|
||||||
|
test "?!Type is shorthand for Result[Type, ref CatchableError]":
|
||||||
|
check (?!int is Result[int, ref CatchableError])
|
||||||
|
check (?!string is Result[string, ref CatchableError])
|
||||||
|
check (?!seq[bool] is Result[seq[bool], ref CatchableError])
|
||||||
|
|
||||||
|
test "conversion to string $ works for ?!Types":
|
||||||
|
check $42.success == "success(42)"
|
||||||
|
check $(int.failure "some error") == "failure(\"some error\")"
|
||||||
|
|
||||||
|
test "! gets value or raises Defect":
|
||||||
|
check !42.success == 42
|
||||||
|
expect Defect: discard !int.failure error
|
||||||
|
|
||||||
|
test ".? can be used for chaining results":
|
||||||
|
let a: ?!seq[int] = @[41, 42].success
|
||||||
|
let b: ?!seq[int] = seq[int].failure error
|
||||||
|
check a.?len == 2.success
|
||||||
|
check b.?len == int.failure error
|
||||||
|
check a.?len.?uint8 == 2'u8.success
|
||||||
|
check b.?len.?uint8 == uint8.failure error
|
||||||
|
check a.?len() == 2.success
|
||||||
|
check b.?len() == int.failure error
|
||||||
|
check a.?distribute(2).?len() == 2.success
|
||||||
|
check b.?distribute(2).?len() == int.failure error
|
||||||
|
|
||||||
|
test ".? chain can be followed by . calls and operators":
|
||||||
|
let a = @[41, 42].success
|
||||||
|
check (a.?len.unsafeGet == 2)
|
||||||
|
check (a.?len.unsafeGet.uint8.uint64 == 2'u64)
|
||||||
|
check (a.?len.unsafeGet() == 2)
|
||||||
|
check (a.?len.unsafeGet().uint8.uint64 == 2'u64)
|
||||||
|
check (a.?deduplicate()[0].?uint8.?uint64 == 41'u64.success)
|
||||||
|
check (a.?len + 1 == 3.success)
|
||||||
|
check (a.?deduplicate()[0] + 1 == 42.success)
|
||||||
|
check (a.?deduplicate.map(x => x) == @[41, 42].success)
|
||||||
|
|
||||||
|
test ".? chains work in generic code":
|
||||||
|
proc test[T](a: ?!T) =
|
||||||
|
check (a.?len == 2.success)
|
||||||
|
check (a.?len.?uint8 == 2'u8.success)
|
||||||
|
check (a.?len() == 2.success)
|
||||||
|
check (a.?distribute(2).?len() == 2.success)
|
||||||
|
check (a.?len.unsafeGet == 2)
|
||||||
|
check (a.?len.unsafeGet.uint8.uint64 == 2'u64)
|
||||||
|
check (a.?len.unsafeGet() == 2)
|
||||||
|
check (a.?len.unsafeGet().uint8.uint64 == 2'u64)
|
||||||
|
check (a.?deduplicate()[0].?uint8.?uint64 == 41'u64.success)
|
||||||
|
check (a.?len + 1 == 3.success)
|
||||||
|
check (a.?deduplicate()[0] + 1 == 42.success)
|
||||||
|
check (a.?deduplicate.map(x => x) == @[41, 42].success)
|
||||||
|
|
||||||
|
test @[41, 42].success
|
||||||
|
|
||||||
|
test "[] can be used for indexing results":
|
||||||
|
let a: ?!seq[int] = @[1, 2, 3].success
|
||||||
|
let b: ?!seq[int] = seq[int].failure error
|
||||||
|
check a[1] == 2.success
|
||||||
|
check a[^1] == 3.success
|
||||||
|
check a[0..1] == @[1, 2].success
|
||||||
|
check b[1] == int.failure error
|
||||||
|
|
||||||
|
test "|? can be used to specify a fallback value":
|
||||||
|
check 42.success |? 40 == 42
|
||||||
|
check int.failure(error) |? 42 == 42
|
||||||
|
|
||||||
|
test "=? can be used for optional binding":
|
||||||
|
if a =? int.failure(error):
|
||||||
|
fail
|
||||||
|
|
||||||
|
if b =? 42.success:
|
||||||
|
check b == 42
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
while a =? 42.success:
|
||||||
|
check a == 42
|
||||||
|
break
|
||||||
|
|
||||||
|
while a =? int.failure(error):
|
||||||
|
fail
|
||||||
|
break
|
||||||
|
|
||||||
|
test "=? can appear multiple times in conditional expression":
|
||||||
|
if a =? 42.success and b =? "foo".success:
|
||||||
|
check a == 42
|
||||||
|
check b == "foo"
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
test "=? works with variable hiding":
|
||||||
|
let a = 42.success
|
||||||
|
if a =? a:
|
||||||
|
check a == 42
|
||||||
|
|
||||||
|
test "=? works with var":
|
||||||
|
if var a =? 1.success and var b =? 2.success:
|
||||||
|
check a == 1
|
||||||
|
inc a
|
||||||
|
check a == b
|
||||||
|
inc b
|
||||||
|
check b == 3
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
if var a =? int.failure(error):
|
||||||
|
fail
|
||||||
|
|
||||||
|
test "=? works with .?":
|
||||||
|
if a =? 42.success.?uint8:
|
||||||
|
check a == 42.uint8
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
test "=? evaluates optional expression only once":
|
||||||
|
var count = 0
|
||||||
|
if a =? (inc count; 42.success):
|
||||||
|
let b {.used.} = a
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
if var a =? (inc count; 42.success):
|
||||||
|
let b {.used.} = a
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "=? works in generic code":
|
||||||
|
proc toString[T](res: ?!T): string =
|
||||||
|
if value =? res:
|
||||||
|
$value
|
||||||
|
else:
|
||||||
|
"error"
|
||||||
|
|
||||||
|
check 42.success.toString == "42"
|
||||||
|
check int.failure(error).toString == "error"
|
||||||
|
|
||||||
|
test "=? works in generic code with variable hiding":
|
||||||
|
let value {.used.} = "ignored"
|
||||||
|
|
||||||
|
proc toString[T](res: ?!T): string =
|
||||||
|
if value =? res:
|
||||||
|
$value
|
||||||
|
else:
|
||||||
|
"error"
|
||||||
|
|
||||||
|
check 42.success.toString == "42"
|
||||||
|
check int.failure(error).toString == "error"
|
||||||
|
|
||||||
|
test "=? works with closures":
|
||||||
|
var called = false
|
||||||
|
let closure = success(proc () = called = true)
|
||||||
|
|
||||||
|
if a =? failure(proc (), error):
|
||||||
|
a()
|
||||||
|
|
||||||
|
check not called
|
||||||
|
|
||||||
|
if a =? closure:
|
||||||
|
a()
|
||||||
|
|
||||||
|
check called
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples":
|
||||||
|
if (a, b) =? (success ("test", 1)):
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
if (a, b) =? (string, int).failure(error):
|
||||||
|
discard a
|
||||||
|
discard b
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples with named fields":
|
||||||
|
if (a, b) =? (success (desc: "test", id: 1)):
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples returned from proc":
|
||||||
|
proc returnsTuple(): ?!tuple[name: string, id: int] = success ("test", 1)
|
||||||
|
|
||||||
|
if (a, b) =? returnsTuple():
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples returned from proc with unnamed fields":
|
||||||
|
proc returnsTuple(): ?!(string, int,) = success ("test", 1,)
|
||||||
|
|
||||||
|
if (a, b,) =? returnsTuple():
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples with _":
|
||||||
|
if (_, b) =? success ("test", 1):
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds and unpacks tuples with named fields":
|
||||||
|
if (a, b) =? success (desc: "test", id: 1):
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds variable to tuples with named fields":
|
||||||
|
if t =? success (desc: "test", id: 1):
|
||||||
|
check t.desc == "test"
|
||||||
|
check t.id == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "=? binds to tuple types":
|
||||||
|
type MyTuple = tuple
|
||||||
|
desc: string
|
||||||
|
id: int
|
||||||
|
|
||||||
|
let mt: MyTuple = ("test", 1)
|
||||||
|
|
||||||
|
if t =? (success mt):
|
||||||
|
check t.desc == "test"
|
||||||
|
check t.id == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
if (a, b) =? (success mt):
|
||||||
|
check a == "test"
|
||||||
|
check b == 1
|
||||||
|
else:
|
||||||
|
fail()
|
||||||
|
|
||||||
|
test "without statement works for results":
|
||||||
|
proc test1 =
|
||||||
|
without a =? 42.success:
|
||||||
|
fail
|
||||||
|
return
|
||||||
|
check a == 42
|
||||||
|
|
||||||
|
proc test2 =
|
||||||
|
without a =? int.failure "error":
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
test1()
|
||||||
|
test2()
|
||||||
|
|
||||||
|
test "without statement can expose error":
|
||||||
|
proc test =
|
||||||
|
without a =? int.failure "some error", error:
|
||||||
|
check error.msg == "some error"
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
test()
|
||||||
|
|
||||||
|
test "without statement only exposes error variable inside block":
|
||||||
|
proc test =
|
||||||
|
without a =? 42.success, errorvar:
|
||||||
|
fail
|
||||||
|
discard errorvar # fixes warning about unused variable "errorvar"
|
||||||
|
return
|
||||||
|
check not compiles errorvar
|
||||||
|
|
||||||
|
test()
|
||||||
|
|
||||||
|
test "without statements with multiple bindings exposes first error":
|
||||||
|
proc test1 =
|
||||||
|
without (a =? int.failure "error 1") and
|
||||||
|
(b =? int.failure "error 2"),
|
||||||
|
error:
|
||||||
|
check error.msg == "error 1"
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
proc test2 =
|
||||||
|
without (a =? 42.success) and (b =? int.failure "error 2"), error:
|
||||||
|
check error.msg == "error 2"
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
test1()
|
||||||
|
test2()
|
||||||
|
|
||||||
|
test "without statement with error evaluates result only once":
|
||||||
|
proc test =
|
||||||
|
var count = 0
|
||||||
|
without a =? (inc count; int.failure "error"):
|
||||||
|
check count == 1
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
test()
|
||||||
|
|
||||||
|
test "without statement with error handles options as well":
|
||||||
|
proc test1 =
|
||||||
|
without a =? int.none and b =? int.failure "error", error:
|
||||||
|
check error.msg == "Option is set to `none`"
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
proc test2 =
|
||||||
|
without a =? 42.some and b =? int.failure "error", error:
|
||||||
|
check error.msg == "error"
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
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:
|
||||||
|
discard error
|
||||||
|
fail
|
||||||
|
without b =? 42.success, error:
|
||||||
|
discard error
|
||||||
|
fail
|
||||||
|
|
||||||
|
test()
|
||||||
|
|
||||||
|
test "without statement with error works with deeply nested =? operators":
|
||||||
|
proc test =
|
||||||
|
let fail1 = int.failure "error 1"
|
||||||
|
let fail2 = int.failure "error 2"
|
||||||
|
without (block: a =? (if b =? fail1: b.success else: fail2)), error:
|
||||||
|
check error.msg == "error 2"
|
||||||
|
return
|
||||||
|
fail
|
||||||
|
|
||||||
|
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 "without statement works in generic code using existing error name":
|
||||||
|
let existingName {.used.} = "some variable"
|
||||||
|
|
||||||
|
proc shouldCompile(_: type int): ?!int =
|
||||||
|
without _ =? int.failure "error", existingName:
|
||||||
|
check existingName.msg == "error"
|
||||||
|
return success 42
|
||||||
|
|
||||||
|
discard int.shouldCompile()
|
||||||
|
|
||||||
|
test "without statements with error work in nested calls":
|
||||||
|
proc bar(): ?!int =
|
||||||
|
without _ =? int.failure "error", err:
|
||||||
|
return failure err.msg
|
||||||
|
|
||||||
|
proc foo() =
|
||||||
|
without _ =? bar(), err:
|
||||||
|
check err.msg == "error"
|
||||||
|
return
|
||||||
|
fail()
|
||||||
|
|
||||||
|
foo()
|
||||||
|
|
||||||
|
test "without statement with error works in nested generic calls":
|
||||||
|
proc works(_: type int): ?!int =
|
||||||
|
without _ =? int.failure "error1", error:
|
||||||
|
check error.msg == "error1"
|
||||||
|
return success 42
|
||||||
|
|
||||||
|
proc fails(_: type int): ?!int =
|
||||||
|
return failure "error2"
|
||||||
|
|
||||||
|
proc foo =
|
||||||
|
without a =? int.works() and b =? int.fails(), error:
|
||||||
|
check error.msg == "error2"
|
||||||
|
return
|
||||||
|
fail()
|
||||||
|
|
||||||
|
foo()
|
||||||
|
|
||||||
|
test "without statement with error works with multiple threads":
|
||||||
|
proc fail(number: int) =
|
||||||
|
without _ =? int.failure "error" & $number, error:
|
||||||
|
check error.msg == "error" & $number
|
||||||
|
parallel:
|
||||||
|
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
|
||||||
|
|
||||||
|
test "success can be called without argument":
|
||||||
|
check (success() is ?!void)
|
||||||
|
|
||||||
|
test "failure can be called with string argument":
|
||||||
|
let value = int.failure("some failure")
|
||||||
|
check value.error of ResultFailure
|
||||||
|
check value.error.msg == "some failure"
|
||||||
|
|
||||||
|
test "unary operator `-` works for results":
|
||||||
|
check -(-42.success) == 42.success
|
||||||
|
check -(int.failure(error)) == int.failure(error)
|
||||||
|
|
||||||
|
test "other unary operators work for results":
|
||||||
|
check +(42.success) == 42.success
|
||||||
|
check @([1, 2].success) == (@[1, 2]).success
|
||||||
|
|
||||||
|
test "binary operator `+` works for results":
|
||||||
|
check 40.success + 2.success == 42.success
|
||||||
|
check 40.success + 2 == 42.success
|
||||||
|
check int.failure(error) + 2 == int.failure(error)
|
||||||
|
check 40.success + int.failure(error) == int.failure(error)
|
||||||
|
check int.failure(error) + int.failure(error) == int.failure(error)
|
||||||
|
|
||||||
|
test "other binary operators work for results":
|
||||||
|
check (21.success * 2 == 42.success)
|
||||||
|
check (84'f.success / 2'f == 42'f.success)
|
||||||
|
check (84.success div 2 == 42.success)
|
||||||
|
check (85.success mod 43 == 42.success)
|
||||||
|
check (0b00110011.success shl 1 == 0b01100110.success)
|
||||||
|
check (0b00110011.success shr 1 == 0b00011001.success)
|
||||||
|
check (44.success - 2 == 42.success)
|
||||||
|
check ("f".success & "oo" == "foo".success)
|
||||||
|
check (40.success <= 42 == true.success)
|
||||||
|
check (40.success < 42 == true.success)
|
||||||
|
check (40.success >= 42 == false.success)
|
||||||
|
check (40.success > 42 == false.success)
|
||||||
|
|
||||||
|
test "Result can be converted to Option":
|
||||||
|
check 42.success.option == 42.some
|
||||||
|
check int.failure(error).option == int.none
|
||||||
|
|
||||||
|
test "Result error can be converted to Option":
|
||||||
|
check (int.failure(error).errorOption == error.some)
|
||||||
|
check (42.success.errorOption == (ref CatchableError).none)
|
||||||
|
check (void.failure(error).errorOption == error.some)
|
||||||
|
check (success().errorOption == (ref CatchableError).none)
|
||||||
|
|
||||||
|
test "failure can be used without type parameter in procs":
|
||||||
|
proc fails: ?!int =
|
||||||
|
failure "some error"
|
||||||
|
|
||||||
|
check fails().isFailure
|
||||||
|
check fails().error.msg == "some error"
|
||||||
|
|
||||||
|
test ".? avoids wrapping result in result":
|
||||||
|
let a = 41.success
|
||||||
|
|
||||||
|
proc b(x: int): ?!int =
|
||||||
|
success x + 1
|
||||||
|
|
||||||
|
check a.?b == 42.success
|
||||||
|
|
||||||
|
test "lifted operators avoid wrapping result in result":
|
||||||
|
let a = 40.success
|
||||||
|
let b = 2.success
|
||||||
|
|
||||||
|
func `&`(x, y: int): ?!int =
|
||||||
|
success x + y
|
||||||
|
|
||||||
|
check (a & b) == 42.success
|
||||||
|
|
||||||
|
test ".? chain evaluates result only once":
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; @[41, 42].success).?len
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "=? evaluates result only once":
|
||||||
|
var count = 0
|
||||||
|
if a =? (inc count; 42.success):
|
||||||
|
let b {.used.} = a
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
if var a =? (inc count; 42.success):
|
||||||
|
let b {.used.} = a
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "|? evaluates result only once":
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; 42.success) |? 43
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test ".?[] evaluates result only once":
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; @[41, 42].success).?[0]
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "lifted unary operators evaluate result only once":
|
||||||
|
var count = 0
|
||||||
|
discard -(inc count; 42.success)
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "lifted binary operators evaluate results only once":
|
||||||
|
# lifted operator on two options:
|
||||||
|
block:
|
||||||
|
var count1, count2 = 0
|
||||||
|
discard (inc count1; 40.success) + (inc count2; 2.success)
|
||||||
|
check count1 == 1
|
||||||
|
check count2 == 1
|
||||||
|
# lifted operator on option and value:
|
||||||
|
block:
|
||||||
|
var count1, count2 = 0
|
||||||
|
discard (inc count1; 40.success) + (inc count2; 2)
|
||||||
|
check count1 == 1
|
||||||
|
check count2 == 1
|
||||||
|
|
||||||
|
test "conversion to option evaluates result only once":
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; 42.success).option
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "conversion to error evaluates result only once":
|
||||||
|
var count = 0
|
||||||
|
discard (inc count; int.failure(error)).errorOption
|
||||||
|
check count == 1
|
||||||
|
|
||||||
|
test "examples from readme work":
|
||||||
|
|
||||||
|
proc works: ?!seq[int] =
|
||||||
|
success @[1, 1, 2, 2, 2]
|
||||||
|
|
||||||
|
proc fails: ?!seq[int] =
|
||||||
|
failure "something went wrong"
|
||||||
|
|
||||||
|
# binding:
|
||||||
|
if x =? works():
|
||||||
|
check x == @[1, 1, 2, 2, 2]
|
||||||
|
else:
|
||||||
|
fail
|
||||||
|
|
||||||
|
# chaining:
|
||||||
|
let amount = works().?deduplicate.?len
|
||||||
|
check (amount == 2.success)
|
||||||
|
|
||||||
|
# fallback values:
|
||||||
|
let value = fails() |? @[]
|
||||||
|
check (value == newSeq[int](0))
|
||||||
|
|
||||||
|
# lifted operators:
|
||||||
|
let sum = works()[3] + 40
|
||||||
|
check (sum == 42.success)
|
||||||
|
|
||||||
|
# catch
|
||||||
|
let x = parseInt("42").catch
|
||||||
|
check (x == 42.success)
|
||||||
|
let y = parseInt("XX").catch
|
||||||
|
check y.isFailure
|
||||||
|
|
||||||
|
# Conversion to Option
|
||||||
|
|
||||||
|
let converted = works().option
|
||||||
|
check (converted == @[1, 1, 2, 2, 2].some)
|
||||||
|
|
||||||
|
# Without statement
|
||||||
|
proc someProc(r: ?!int) =
|
||||||
|
without value =? r, error:
|
||||||
|
check error.msg == "some error"
|
||||||
|
return
|
||||||
|
|
||||||
|
check value == 42
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
suite "result compatibility":
|
||||||
|
|
||||||
|
type R = Result[int, string]
|
||||||
|
let good = R.ok 42
|
||||||
|
let bad = R.err "some error"
|
||||||
|
|
||||||
|
test "|?, =? and .option work on other types of Result":
|
||||||
|
check bad |? 43 == 43
|
||||||
|
|
||||||
|
if value =? good:
|
||||||
|
check value == 42
|
||||||
|
else:
|
||||||
|
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
|
||||||
|
|
||||||
|
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"
|
||||||
@ -3,7 +3,7 @@ author = "Questionable Authors"
|
|||||||
description = "Questionable tests for pkg/result"
|
description = "Questionable tests for pkg/result"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
requires "result"
|
requires "results"
|
||||||
|
|
||||||
task test, "Runs the test suite":
|
task test, "Runs the test suite":
|
||||||
exec "nim c -f -r test.nim"
|
exec "nim c -f -r --skipParentCfg test.nim"
|
||||||
3
testmodules/stew/config.nims
Normal file
3
testmodules/stew/config.nims
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
--path:"../.."
|
||||||
|
--threads:on
|
||||||
|
import "../../config.nims"
|
||||||
@ -1 +0,0 @@
|
|||||||
--path:"../.."
|
|
||||||
@ -1 +1 @@
|
|||||||
include ../result/test
|
include ../results/test
|
||||||
|
|||||||
@ -3,7 +3,11 @@ author = "Questionable Authors"
|
|||||||
description = "Questionable tests for pkg/stew"
|
description = "Questionable tests for pkg/stew"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
requires "stew"
|
when (NimMajor, NimMinor) >= (1, 6):
|
||||||
|
requires "stew"
|
||||||
|
|
||||||
task test, "Runs the test suite":
|
task test, "Runs the test suite":
|
||||||
exec "nim c -f -r test.nim"
|
exec "nim c -f -r --skipParentCfg test.nim"
|
||||||
|
else:
|
||||||
|
task test, "Runs the test suite":
|
||||||
|
echo "Warning: Skipping test with stew on Nim < 1.6"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user