├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── License.md ├── Readme.md ├── config.nims ├── nimble.lock ├── questionable.nim ├── questionable.nimble ├── questionable ├── binding.nim ├── chaining.nim ├── dotlike.nim ├── errorban.nim ├── indexing.nim ├── operators.nim ├── options.nim ├── private │ ├── bareexcept.nim │ ├── binderror.nim │ └── errorban.nim ├── results.nim ├── resultsbase.nim ├── without.nim └── withoutresult.nim └── testmodules ├── options ├── config.nims ├── nimbledeps │ └── .keep ├── test.nim └── test.nimble ├── results ├── config.nims ├── nimbledeps │ └── .keep ├── test.nim └── test.nimble └── stew ├── config.nims ├── nimbledeps └── .keep ├── test.nim └── test.nimble /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | insert_final_newline = true 4 | indent_size = 2 5 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | nim: [stable, 1.6.16, 1.4.8, 1.2.18] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: iffy/install-nim@v3 14 | with: 15 | version: ${{ matrix.nim }} 16 | - name: Build 17 | run: nimble install -y 18 | - name: Test 19 | run: nimble test -y 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*/ 3 | !*.* 4 | nimbledeps 5 | nimble.develop 6 | nimble.paths 7 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Licensed and distributed under either of 2 | [MIT license](http://opensource.org/licenses/MIT) or 3 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 4 | at your option. These files may not be copied, modified, or distributed except 5 | according to those terms. 6 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Questionable 🤔 2 | ============== 3 | 4 | [Option][1] and [Result][2] are two powerful abstractions that can be used 5 | instead of raising errors. They can be a bit unwieldy though. This library is an 6 | attempt at making their use a bit more elegant. 7 | 8 | Installation 9 | ------------ 10 | 11 | Use the [Nimble][3] package manager to add `questionable` to an existing 12 | project. Add the following to its .nimble file: 13 | 14 | ```nim 15 | requires "questionable >= 0.10.15 & < 0.11.0" 16 | ``` 17 | 18 | If you want to make use of Result types, then you also have to add either the 19 | [result][2] package, or the [stew][4] package: 20 | 21 | ```nim 22 | requires "results" # either this 23 | requires "stew" # or this 24 | ``` 25 | 26 | Options 27 | ------- 28 | 29 | You can use `?` to make a type optional. For example, the type `?int` is just 30 | short for [`Option[int]`][1]. 31 | 32 | ```nim 33 | import questionable 34 | 35 | var x: ?int 36 | ``` 37 | 38 | Assigning values is done using the `some` and `none` procs from the standard library: 39 | 40 | ```nim 41 | x = 42.some # Option x now holds the value 42 42 | x = int.none # Option x no longer holds a value 43 | ``` 44 | ### Option binding 45 | 46 | The `=?` operator lets you bind the value inside an Option to a new variable. It 47 | can be used inside of a conditional expression, for instance in an `if` 48 | statement: 49 | 50 | ```nim 51 | x = 42.some 52 | 53 | if y =? x: 54 | # y equals 42 here 55 | else: 56 | # this is never reached 57 | 58 | x = int.none 59 | 60 | if y =? x: 61 | # this is never reached 62 | else: 63 | # this is reached, and y is not defined 64 | ``` 65 | 66 | The `without` statement can be used to place guards that ensure that an Option 67 | contains a value: 68 | 69 | ```nim 70 | proc someProc(option: ?int) = 71 | without value =? option: 72 | # option did not contain a value 73 | return 74 | 75 | # use value 76 | ``` 77 | 78 | ### Option chaining 79 | 80 | To safely access fields and call procs, you can use the `.?` operator: 81 | 82 | > Note: in versions 0.3.x and 0.4.x, the operator was `?.` instead of `.?` 83 | 84 | ```nim 85 | var numbers: ?seq[int] 86 | var amount: ?int 87 | 88 | numbers = @[1, 2, 3].some 89 | amount = numbers.?len 90 | # amount now holds the integer 3 91 | 92 | numbers = seq[int].none 93 | amount = numbers.?len 94 | # amount now equals int.none 95 | ``` 96 | 97 | Invocations of the `.?` operator can be chained: 98 | ```nim 99 | import sequtils 100 | 101 | numbers = @[1, 1, 2, 2, 2].some 102 | amount = numbers.?deduplicate.?len 103 | # amount now holds the integer 2 104 | ``` 105 | 106 | ### Fallback values 107 | 108 | Use the `|?` operator to supply a fallback value when the Option does not hold 109 | a value: 110 | 111 | ```nim 112 | x = int.none 113 | 114 | let z = x |? 3 115 | # z equals 3 116 | ``` 117 | 118 | ### Obtaining value with ! 119 | 120 | The `!` operator returns the value of an Option when you're absolutely sure that 121 | it contains a value. 122 | 123 | ```nim 124 | x = 42.some 125 | let dare = !x # dare equals 42 126 | 127 | x = int.none 128 | let crash = !x # raises a Defect 129 | ``` 130 | 131 | ### Operators 132 | 133 | The operators `[]`, `-`, `+`, `@`, `*`, `/`, `div`, `mod`, `shl`, `shr`, `&`, 134 | `<=`, `<`, `>=`, `>` are all lifted, so they can be used directly on Options: 135 | 136 | ```nim 137 | numbers = @[1, 2, 3].some 138 | x = 39.some 139 | 140 | let indexed = numbers[0] # equals 1.some 141 | let sum = x + 3 # equals 42.some 142 | ``` 143 | 144 | Results 145 | ------- 146 | 147 | Support for `Result` is considered experimental. If you want to use them you 148 | have to explicitly import the `questionable/results` module: 149 | 150 | ```nim 151 | import questionable/results 152 | ``` 153 | 154 | You can use `?!` to make a Result type. These Result types either hold a value or 155 | an error. For example the type `?!int` is short for `Result[int, ref 156 | CatchableError]`. 157 | 158 | ```nim 159 | proc example: ?!int = 160 | # either return an integer or an error 161 | ``` 162 | 163 | Results can be made using the `success` and `failure` procs: 164 | 165 | ```nim 166 | proc works: ?!seq[int] = 167 | # always returns a Result holding a sequence 168 | success @[1, 1, 2, 2, 2] 169 | 170 | proc fails: ?!seq[int] = 171 | # always returns a Result holding an error 172 | failure "something went wrong" 173 | ``` 174 | 175 | ### Binding, chaining, fallbacks and operators 176 | 177 | Binding with the `=?` operator, chaining with the `.?` operator, fallbacks with 178 | the `|?` operator, and all the other operators that work with Options also work 179 | for Results: 180 | ```nim 181 | import sequtils 182 | 183 | # binding: 184 | if x =? works(): 185 | # use x 186 | 187 | # chaining: 188 | let amount = works().?deduplicate.?len 189 | 190 | # fallback values: 191 | let value = fails() |? @[] 192 | 193 | # lifted operators: 194 | let sum = works()[3] + 40 195 | ``` 196 | 197 | ### Without statement 198 | 199 | The `without` statement can also be used with Results. It provides access to any 200 | errors that may arise: 201 | 202 | ```nim 203 | proc someProc(r: ?!int) = 204 | without value =? r, error: 205 | # use `error` to get the error from r 206 | return 207 | 208 | # use value 209 | ``` 210 | 211 | ### Catching errors 212 | 213 | When you want to use Results, but need to call a proc that may raise an 214 | error, you can use `catch`: 215 | 216 | ```nim 217 | import strutils 218 | 219 | let x = parseInt("42").catch # equals 42.success 220 | let y = parseInt("XX").catch # equals int.failure(..) 221 | ``` 222 | 223 | ### Conversion to Option 224 | 225 | Any Result can be converted to an Option: 226 | 227 | ```nim 228 | let converted = works().option # equals @[1, 1, 2, 2, 2].some 229 | ``` 230 | 231 | [1]: https://nim-lang.org/docs/options.html 232 | [2]: https://github.com/arnetheduck/nim-result 233 | [3]: https://github.com/nim-lang/nimble 234 | [4]: https://github.com/status-im/nim-stew 235 | -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- 1 | # Style Check 2 | if (NimMajor, NimMinor, NimPatch) >= (1, 6, 6): 3 | --styleCheck:usages 4 | if (NimMajor, NimMinor) < (1, 6): 5 | --styleCheck:hint 6 | else: 7 | --styleCheck:error 8 | 9 | # Disable some warnings 10 | if (NimMajor, NimMinor) >= (1, 6): 11 | switch("warning", "DotLikeOps:off") 12 | 13 | # begin Nimble config (version 1) 14 | when fileExists("nimble.paths"): 15 | include "nimble.paths" 16 | # end Nimble config 17 | -------------------------------------------------------------------------------- /nimble.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "packages": {} 4 | } -------------------------------------------------------------------------------- /questionable.nim: -------------------------------------------------------------------------------- 1 | import ./questionable/dotlike 2 | import ./questionable/options 3 | 4 | export options 5 | -------------------------------------------------------------------------------- /questionable.nimble: -------------------------------------------------------------------------------- 1 | version = "0.10.15" 2 | author = "Questionable Authors" 3 | description = "Elegant optional types" 4 | license = "MIT" 5 | 6 | task test, "Runs the test suite": 7 | for module in ["options", "results", "stew"]: 8 | withDir "testmodules/" & module: 9 | delEnv "NIMBLE_DIR" # use nimbledeps dir 10 | exec "nimble install -d -y" 11 | exec "nimble test -y" 12 | -------------------------------------------------------------------------------- /questionable/binding.nim: -------------------------------------------------------------------------------- 1 | import std/options 2 | import std/macros 3 | import ./private/binderror 4 | 5 | when (NimMajor, NimMinor) < (1, 1): 6 | type SomePointer = ref | ptr | pointer 7 | elif (NimMajor, NimMinor) == (2, 0): # Broken in 2.0.0, fixed in 2.1.1. 8 | type SomePointer = ref | ptr | pointer | proc 9 | else: 10 | type SomePointer = ref | ptr | pointer | proc | iterator {.closure.} 11 | 12 | template toOption[T](option: Option[T]): Option[T] = 13 | option 14 | 15 | template toOption[T: SomePointer](value: T): Option[T] = 16 | value.option 17 | 18 | proc placeholder(T: type): T = 19 | discard 20 | 21 | template bindLet(name, expression): untyped = 22 | let evaluated = expression 23 | let option = evaluated.toOption 24 | type T = typeof(option.unsafeGet()) 25 | let name {.used.} = if option.isSome: 26 | option.unsafeGet() 27 | else: 28 | bindFailed(evaluated) 29 | placeholder(T) 30 | option.isSome 31 | 32 | template bindVar(name, expression): untyped = 33 | let evaluated = expression 34 | let option = evaluated.toOption 35 | type T = typeof(option.unsafeGet()) 36 | var name {.used.} = if option.isSome: 37 | option.unsafeGet() 38 | else: 39 | bindFailed(evaluated) 40 | placeholder(T) 41 | option.isSome 42 | 43 | proc newUnpackTupleNode(names: NimNode, value: NimNode): NimNode = 44 | # builds tuple unpacking statement, eg: let (a, b) = value 45 | let vartuple = nnkVarTuple.newTree() 46 | for i in 0..? option.unsafeGet.identifier 22 | 23 | macro chain(option: typed, infix: untyped{nkInfix}): untyped = 24 | # chain is of shape: option.?left `operator` right 25 | let infix = infix.copyNimTree() 26 | let left = infix[1] 27 | infix[1] = quote do: `option`.?`left` 28 | infix 29 | 30 | macro chain(option: typed, bracket: untyped{nkBracketExpr}): untyped = 31 | # chain is of shape: option.?left[right] 32 | let bracket = bracket.copyNimTree() 33 | let left = bracket[0] 34 | bracket[0] = quote do: `option`.?`left` 35 | bracket 36 | 37 | macro chain(option: typed, dot: untyped{nkDotExpr}): untyped = 38 | # chain is of shape: option.?left.right 39 | let dot = dot.copyNimTree() 40 | let left = dot[0] 41 | dot[0] = quote do: `option`.?`left` 42 | dot 43 | 44 | macro chain(option: typed, call: untyped{nkCall}): untyped = 45 | let call = call.copyNimTree() 46 | let procedure = call[0] 47 | if call.len == 1: 48 | # chain is of shape: option.?procedure() 49 | quote do: `option`.?`procedure` 50 | elif procedure.kind == nnkDotExpr: 51 | # chain is of shape: option.?left.right(arguments) 52 | let (left, right) = (procedure[0], procedure[1]) 53 | call[0] = right 54 | call.insert(1, quote do: `option`.?`left`) 55 | call 56 | elif procedure.isSym and $procedure == "[]": 57 | # chain is of shape: option.?left[right] after semantic analysis 58 | let left = call[1] 59 | call[1] = quote do: `option`.?`left` 60 | call 61 | else: 62 | # chain is of shape: option.?procedure(arguments) 63 | call.insert(1, quote do: `option`.unsafeGet) 64 | quote do: 65 | expectReturnType(`procedure`, `call`) 66 | `option` ->? `call` 67 | 68 | macro chain(option: typed, symbol: untyped): untyped = 69 | symbol.expectSym() 70 | let expression = ident($symbol) 71 | quote do: `option`.?`expression` 72 | 73 | template `.?`*(left: typed, right: untyped): untyped = 74 | ## The `.?` chaining operator is used to safely access fields and call procs 75 | ## on Options or Results. The expression is only evaluated when the preceding 76 | ## Option or Result has a value. 77 | block: 78 | let evaluated = left 79 | chain(evaluated, right) 80 | -------------------------------------------------------------------------------- /questionable/dotlike.nim: -------------------------------------------------------------------------------- 1 | when defined(nimPreviewDotLikeOps): 2 | {.error: "DotLikeOps are not supported by questionable".} 3 | -------------------------------------------------------------------------------- /questionable/errorban.nim: -------------------------------------------------------------------------------- 1 | {.warning: "errorban is deprecated; use nimble package `upraises` instead".} 2 | 3 | include ./private/errorban 4 | -------------------------------------------------------------------------------- /questionable/indexing.nim: -------------------------------------------------------------------------------- 1 | import std/macros 2 | import std/options 3 | 4 | proc safeGet[T](expression: seq[T] | openArray[T], index: int): Option[T] = 5 | if index >= expression.low and index <= expression.high: 6 | expression[index].some 7 | else: 8 | T.none 9 | 10 | proc safeGet(expression: string, index: int): Option[char] = 11 | if index >= expression.low and index <= expression.high: 12 | expression[index].some 13 | else: 14 | char.none 15 | 16 | macro `.?`*(expression: seq | string | openArray, brackets: untyped{nkBracket}): untyped = 17 | # chain is of shape: (seq or string or openArray).?[index] 18 | let index = brackets[0] 19 | quote do: 20 | block: 21 | safeGet(`expression`, `index`) 22 | 23 | macro `.?`*(expression: typed, brackets: untyped{nkBracket}): untyped = 24 | # chain is of shape: expression.?[index] 25 | let index = brackets[0] 26 | quote do: 27 | block: 28 | type T = typeof(`expression`[`index`]) 29 | try: 30 | `expression`[`index`].some 31 | except KeyError: 32 | T.none 33 | -------------------------------------------------------------------------------- /questionable/operators.nim: -------------------------------------------------------------------------------- 1 | template liftUnary*(T: type, operator: untyped) = 2 | 3 | template `operator`*(a: T): untyped = 4 | block: 5 | let evaluated = a 6 | evaluated ->? `operator`(evaluated.unsafeGet()) 7 | 8 | template liftBinary*(T: type, operator: untyped) = 9 | 10 | template `operator`*(a: T, b: T): untyped = 11 | block: 12 | let evalA = a 13 | let evalB = b 14 | (evalA, evalB) ->? `operator`(evalA.unsafeGet, evalB.unsafeGet) 15 | 16 | template `operator`*(a: T, b: typed): untyped = 17 | block: 18 | let evalA = a 19 | evalA ->? `operator`(evalA.unsafeGet(), b) 20 | -------------------------------------------------------------------------------- /questionable/options.nim: -------------------------------------------------------------------------------- 1 | import std/options 2 | import std/macros 3 | import ./binding 4 | import ./chaining 5 | import ./indexing 6 | import ./operators 7 | import ./without 8 | 9 | include ./private/errorban 10 | 11 | export options except get 12 | export binding 13 | export chaining 14 | export indexing 15 | export without 16 | 17 | template `?`*(T: typed): type Option[T] = 18 | ## Use `?` to make a type optional. For example the type `?int` is short for 19 | ## `Option[int]`. 20 | 21 | Option[T] 22 | 23 | template `!`*[T](option: ?T): T = 24 | ## Returns the value of an Option when you're absolutely sure that it 25 | ## contains value. Using `!` on an Option without a value raises a Defect. 26 | 27 | option.get 28 | 29 | template `->?`*[T,U](option: ?T, expression: ?U): ?U = 30 | if option.isSome: 31 | expression 32 | else: 33 | U.none 34 | 35 | template `->?`*[T,U](option: ?T, expression: U): ?U = 36 | option ->? expression.some 37 | 38 | template `->?`*[T,U,V](options: (?T, ?U), expression: ?V): ?V = 39 | if options[0].isSome and options[1].isSome: 40 | expression 41 | else: 42 | V.none 43 | 44 | template `->?`*[T,U,V](options: (?T, ?U), expression: V): ?V = 45 | options ->? expression.some 46 | 47 | proc `|?`*[T](option: ?T, fallback: T): T = 48 | ## Use the `|?` operator to supply a fallback value when an Option does not 49 | ## hold a value. 50 | 51 | if option.isSome: 52 | option.unsafeGet() 53 | else: 54 | fallback 55 | 56 | macro `.?`*[T](option: ?T, brackets: untyped{nkBracket}): untyped = 57 | let index = brackets[0] 58 | quote do: 59 | block: 60 | let evaluated = `option` 61 | type U = typeof(evaluated.unsafeGet().?[`index`].unsafeGet()) 62 | if evaluated.isSome: 63 | evaluated.unsafeGet().?[`index`] 64 | else: 65 | U.none 66 | 67 | Option.liftUnary(`-`) 68 | Option.liftUnary(`+`) 69 | Option.liftUnary(`@`) 70 | Option.liftBinary(`[]`) 71 | Option.liftBinary(`*`) 72 | Option.liftBinary(`/`) 73 | Option.liftBinary(`div`) 74 | Option.liftBinary(`mod`) 75 | Option.liftBinary(`shl`) 76 | Option.liftBinary(`shr`) 77 | Option.liftBinary(`+`) 78 | Option.liftBinary(`-`) 79 | Option.liftBinary(`&`) 80 | Option.liftBinary(`<=`) 81 | Option.liftBinary(`<`) 82 | Option.liftBinary(`>=`) 83 | Option.liftBinary(`>`) 84 | -------------------------------------------------------------------------------- /questionable/private/bareexcept.nim: -------------------------------------------------------------------------------- 1 | template ignoreBareExceptWarning*(body) = 2 | when defined(nimHasWarnBareExcept): 3 | {.push warning[BareExcept]:off warning[UnreachableCode]:off.} 4 | body 5 | when defined(nimHasWarnBareExcept): 6 | {.pop.} 7 | -------------------------------------------------------------------------------- /questionable/private/binderror.nim: -------------------------------------------------------------------------------- 1 | import std/options 2 | import std/macros 3 | 4 | # A stack of names of error variables. Keeps track of the error variables that 5 | # are given to captureBindError(). 6 | var errorVariableNames {.global, compileTime.}: seq[string] 7 | 8 | macro captureBindError*(error: var ref CatchableError, expression): auto = 9 | ## Ensures that an error is assigned to the error variable when a binding (=?) 10 | ## fails inside the expression. 11 | 12 | # name of the error variable as a string literal 13 | let errorVariableName = newLit($error) 14 | 15 | let evaluated = genSym(nskLet, "evaluated") 16 | quote do: 17 | # add error variable to the top of the stack 18 | static: errorVariableNames.add(`errorVariableName`) 19 | # evaluate the expression 20 | let `evaluated` = `expression` 21 | # pop error variable from the stack 22 | static: discard errorVariableNames.pop() 23 | # return the evaluated result 24 | `evaluated` 25 | 26 | func unsafeCatchableError[T](_: Option[T]): ref CatchableError = 27 | newException(ValueError, "Option is set to `none`") 28 | 29 | func unsafeCatchableError[T](_: ref T): ref CatchableError = 30 | newException(ValueError, "ref is nil") 31 | 32 | func unsafeCatchableError[T](_: ptr T): ref CatchableError = 33 | newException(ValueError, "ptr is nil") 34 | 35 | func unsafeCatchableError[Proc: proc | iterator](_: Proc): ref CatchableError = 36 | newException(ValueError, "proc or iterator is nil") 37 | 38 | macro bindFailed*(expression: typed) = 39 | ## Called when a binding (=?) fails. 40 | ## Assigns an error to the error variable (specified in captureBindError()) 41 | ## when appropriate. 42 | 43 | # The `expression` parameter is typed to ensure that the compiler does not 44 | # expand bindFailed() before it expands invocations of captureBindError(). 45 | 46 | # check that we have an error variable on the stack 47 | if errorVariableNames.len > 0: 48 | # create an identifier that references the current error variable 49 | let errorVariable = ident errorVariableNames[^1] 50 | return quote do: 51 | # check that the error variable is in scope 52 | when compiles(`errorVariable`): 53 | # assign bind error to error variable 54 | `errorVariable` = `expression`.unsafeCatchableError 55 | -------------------------------------------------------------------------------- /questionable/private/errorban.nim: -------------------------------------------------------------------------------- 1 | ## Include this file to indicate that your module does not raise Errors. 2 | ## Disables compiler hints about unused declarations in Nim < 1.4.0 3 | 4 | when (NimMajor, NimMinor, NimPatch) >= (1, 4, 0): 5 | {.push raises:[].} 6 | else: 7 | {.push raises: [Defect].} 8 | {.hint[XDeclaredButNotUsed]: off.} 9 | -------------------------------------------------------------------------------- /questionable/results.nim: -------------------------------------------------------------------------------- 1 | import std/macros 2 | import ./resultsbase 3 | import ./options 4 | import ./binding 5 | import ./chaining 6 | import ./indexing 7 | import ./operators 8 | import ./without 9 | import ./withoutresult 10 | import ./private/bareexcept 11 | 12 | include ./private/errorban 13 | 14 | export resultsbase except ok, err, isOk, isErr, get 15 | export binding 16 | export chaining 17 | export indexing 18 | export without 19 | export withoutresult 20 | 21 | type ResultFailure* = object of CatchableError 22 | 23 | template `?!`*(T: typed): type Result[T, ref CatchableError] = 24 | ## Use `?!` make a Result type. These Result types either hold a value or 25 | ## an error. For example the type `?!int` is short for 26 | ## `Result[int, ref CatchableError]`. 27 | 28 | Result[T, ref CatchableError] 29 | 30 | template `!`*[T](value: ?!T): T = 31 | ## Returns the value of a Result when you're absolutely sure that it 32 | ## contains value. Using `!` on a Result without a value raises a Defect. 33 | 34 | value.get 35 | 36 | proc success*[T](value: T): ?!T = 37 | ## Creates a successfull Result containing the value. 38 | ## 39 | ok(?!T, value) 40 | 41 | proc success*: ?!void = 42 | ## Creates a successfull Result without a value. 43 | 44 | ok(?!void) 45 | 46 | proc failure*(T: type, error: ref CatchableError): ?!T = 47 | ## Creates a failed Result containing the error. 48 | 49 | err(?!T, error) 50 | 51 | proc failure*(T: type, message: string): ?!T = 52 | ## Creates a failed Result containing a `ResultFailure` with the specified 53 | ## error message. 54 | 55 | T.failure newException(ResultFailure, message) 56 | 57 | template failure*(error: ref CatchableError): auto = 58 | ## Creates a failed Result containing the error. 59 | 60 | err error 61 | 62 | template failure*(message: string): auto = 63 | ## Creates a failed Result containing the error. 64 | 65 | failure newException(ResultFailure, message) 66 | 67 | proc isSuccess*[T](value: ?!T): bool = 68 | ## Returns true when the Result contains a value. 69 | 70 | value.isOk 71 | 72 | proc isFailure*[T](value: ?!T): bool = 73 | ## Returns true when the Result contains an error. 74 | 75 | value.isErr 76 | 77 | proc `$`*[T](value: ?!T): string = 78 | if value.isSuccess: 79 | "success(" & $(!value) & ")" 80 | else: 81 | "failure(\"" & $(value.error.msg) & "\")" 82 | 83 | template `->?`*[T,U](value: ?!T, expression: ?!U): ?!U = 84 | if value.isFailure: 85 | U.failure(value.error) 86 | else: 87 | expression 88 | 89 | template `->?`*[T,U](value: ?!T, expression: U): ?!U = 90 | value ->? expression.success 91 | 92 | template `->?`*[T,U,V](values: (?!T, ?!U), expression: ?!V): ?!V = 93 | if values[0].isFailure: 94 | V.failure(values[0].error) 95 | elif values[1].isFailure: 96 | V.failure(values[1].error) 97 | else: 98 | expression 99 | 100 | template `->?`*[T,U,V](values: (?!T, ?!U), expression: V): ?!V = 101 | values ->? expression.success 102 | 103 | proc `|?`*[T,E](value: Result[T,E], fallback: T): T = 104 | ## Use the `|?` operator to supply a fallback value when a Result does not 105 | ## hold a value. 106 | 107 | value.valueOr(fallback) 108 | 109 | proc option*[T,E](value: Result[T,E]): ?T = 110 | ## Converts a Result into an Option. 111 | 112 | if value.isOk: 113 | ignoreBareExceptWarning: 114 | try: # workaround for erroneous exception tracking when T is a closure 115 | return value.unsafeGet.some 116 | except Exception as exception: 117 | raise newException(Defect, exception.msg, exception) 118 | else: 119 | return T.none 120 | 121 | template toOption*[T, E](value: Result[T, E]): ?T = 122 | ## Converts a Result into an Option. 123 | 124 | value.option 125 | 126 | proc unsafeCatchableError*[T, E](value: Result[T, E]): ref CatchableError = 127 | ## Returns the error from the Result, converted to `ref CatchableError` if 128 | ## necessary. Behaviour is undefined when the result holds a value instead of 129 | ## an error. 130 | when E is ref CatchableError: 131 | value.unsafeError 132 | else: 133 | when compiles($value.unsafeError): 134 | newException(ResultFailure, $value.unsafeError) 135 | else: 136 | newException(ResultFailure, "Result is an error") 137 | 138 | proc errorOption*[T, E](value: Result[T, E]): ?E = 139 | ## Returns an Option that contains the error from the Result, if it has one. 140 | 141 | if value.isErr: 142 | value.error.some 143 | else: 144 | E.none 145 | 146 | Result.liftUnary(`-`) 147 | Result.liftUnary(`+`) 148 | Result.liftUnary(`@`) 149 | Result.liftBinary(`[]`) 150 | Result.liftBinary(`*`) 151 | Result.liftBinary(`/`) 152 | Result.liftBinary(`div`) 153 | Result.liftBinary(`mod`) 154 | Result.liftBinary(`shl`) 155 | Result.liftBinary(`shr`) 156 | Result.liftBinary(`+`) 157 | Result.liftBinary(`-`) 158 | Result.liftBinary(`&`) 159 | Result.liftBinary(`<=`) 160 | Result.liftBinary(`<`) 161 | Result.liftBinary(`>=`) 162 | Result.liftBinary(`>`) 163 | -------------------------------------------------------------------------------- /questionable/resultsbase.nim: -------------------------------------------------------------------------------- 1 | template tryImport(module) = import module 2 | 3 | when compiles tryImport pkg/results: 4 | import pkg/results 5 | else: 6 | import pkg/stew/results 7 | 8 | export results 9 | -------------------------------------------------------------------------------- /questionable/without.nim: -------------------------------------------------------------------------------- 1 | template without*(expression, body) = 2 | ## Used to place guards that ensure that an Option or Result contains a value. 3 | 4 | let ok = expression 5 | if not ok: 6 | body 7 | -------------------------------------------------------------------------------- /questionable/withoutresult.nim: -------------------------------------------------------------------------------- 1 | import std/macros 2 | import ./without 3 | import ./private/binderror 4 | 5 | const symbolKinds = {nnkSym, nnkOpenSymChoice, nnkClosedSymChoice} 6 | const identKinds = {nnkIdent} + symbolKinds 7 | 8 | proc undoSymbolResolution(expression, ident: NimNode): NimNode = 9 | ## Finds symbols in the expression that match the `ident` and replaces them 10 | ## with `ident`, effectively undoing any symbol resolution that happened 11 | ## before. 12 | 13 | if expression.kind in symbolKinds and eqIdent($expression, $ident): 14 | return ident 15 | 16 | let expression = expression.copyNimTree() 17 | for i in 0.. x) == @[41, 42].some 40 | 41 | test ".? chains work in generic code": 42 | proc test[T](a: ?T) = 43 | check a.?len == 2.some 44 | check a.?len.?uint8 == 2'u8.some 45 | check a.?len() == 2.some 46 | check a.?distribute(2).?len() == 2.some 47 | check a.?len.unsafeGet == 2 48 | check a.?len.unsafeGet.uint8.uint64 == 2'u64 49 | check a.?len.unsafeGet() == 2 50 | check a.?len.unsafeGet().uint8.uint64 == 2'u64 51 | check a.?deduplicate()[0].?uint8.?uint64 == 41'u64.some 52 | check a.?len + 1 == 3.some 53 | check a.?deduplicate()[0] + 1 == 42.some 54 | check a.?deduplicate.map(x => x) == @[41, 42].some 55 | 56 | test @[41, 42].some 57 | 58 | test "[] can be used for indexing optionals": 59 | let a: ?seq[int] = @[1, 2, 3].some 60 | let b: ?seq[int] = seq[int].none 61 | check a[1] == 2.some 62 | check a[^1] == 3.some 63 | check a[0..1] == @[1, 2].some 64 | check b[1] == int.none 65 | 66 | test "|? can be used to specify a fallback value": 67 | check 42.some |? 40 == 42 68 | check int.none |? 42 == 42 69 | 70 | test "=? can be used for optional binding": 71 | if a =? int.none: 72 | fail 73 | 74 | if b =? 42.some: 75 | check b == 42 76 | else: 77 | fail 78 | 79 | while a =? 42.some: 80 | check a == 42 81 | break 82 | 83 | while a =? int.none: 84 | fail 85 | break 86 | 87 | test "=? can appear multiple times in conditional expression": 88 | if a =? 42.some and b =? "foo".some: 89 | check a == 42 90 | check b == "foo" 91 | else: 92 | fail 93 | 94 | test "=? works with variable hiding": 95 | let a = 42.some 96 | if a =? a: 97 | check a == 42 98 | 99 | test "=? works with var": 100 | if var a =? 1.some and var b =? 2.some: 101 | check a == 1 102 | inc a 103 | check a == b 104 | inc b 105 | check b == 3 106 | else: 107 | fail 108 | 109 | if var a =? int.none: 110 | fail 111 | 112 | test "=? works with .?": 113 | if a =? 42.some.?uint8: 114 | check a == 42.uint8 115 | else: 116 | fail 117 | 118 | test "=? works in generic code": 119 | proc toString[T](option: ?T): string = 120 | if value =? option: 121 | $value 122 | else: 123 | "none" 124 | 125 | check 42.some.toString == "42" 126 | check int.none.toString == "none" 127 | 128 | test "=? works in generic code with variable hiding": 129 | let value {.used.} = "ignored" 130 | 131 | proc toString[T](option: ?T): string = 132 | if value =? option: 133 | $value 134 | else: 135 | "none" 136 | 137 | check 42.some.toString == "42" 138 | check int.none.toString == "none" 139 | 140 | test "=? works with closures": 141 | var called = false 142 | let closure = some(proc () = called = true) 143 | 144 | if a =? none(proc ()): 145 | a() 146 | 147 | check not called 148 | 149 | if a =? closure: 150 | a() 151 | 152 | check called 153 | 154 | test "=? works with types that do not have a default value": 155 | type NoDefault {.requiresInit.} = distinct int 156 | proc `==`(a,b: NoDefault): bool {.borrow.} 157 | 158 | if a =? some NoDefault(42): 159 | check a == NoDefault(42) 160 | else: 161 | fail() 162 | 163 | if var a =? some NoDefault(42): 164 | check a == NoDefault(42) 165 | else: 166 | fail() 167 | 168 | test "=? works with reference types": 169 | var x = new int 170 | x[] = 42 171 | if a =? x: 172 | check a[] == 42 173 | else: 174 | fail 175 | 176 | x = nil 177 | if a =? x: 178 | fail 179 | 180 | var p = proc = discard 181 | if a =? p: 182 | a() 183 | else: 184 | fail 185 | 186 | p = nil 187 | if a =? p: 188 | fail 189 | 190 | when (NimMajor, NimMinor) >= (1, 1) and (NimMajor, NimMinor) != (2, 0): 191 | var it = iterator: int = yield 2 192 | if a =? it: 193 | for x in a: 194 | check x == 2 195 | else: 196 | fail 197 | 198 | it = nil 199 | if a =? it: 200 | fail 201 | 202 | test "=? rejects non-reference types": 203 | check `not` compiles do: 204 | if a =? 0: 205 | discard 206 | check `not` compiles do: 207 | if var a =? 0: 208 | discard 209 | check `not` compiles do: 210 | if (a,) =? (0,): 211 | discard 212 | 213 | test "=? works with custom optional types": 214 | type MyOption = distinct int 215 | proc isSome(x: MyOption): bool = x.int >= 0 216 | proc unsafeGet(x: MyOption): int = x.int 217 | template toOption(x: MyOption): MyOption = x 218 | 219 | if a =? MyOption 42: 220 | check a == 42 221 | else: 222 | fail 223 | 224 | if a =? MyOption -1: 225 | fail 226 | 227 | test "=? binds and unpacks tuples": 228 | if (a, b) =? (some ("test", 1)): 229 | check a == "test" 230 | check b == 1 231 | else: 232 | fail() 233 | 234 | if (a, b) =? none (string, int): 235 | discard a 236 | discard b 237 | fail() 238 | 239 | test "=? binds and unpacks tuples with named fields": 240 | if (a, b) =? (some (desc: "test", id: 1)): 241 | check a == "test" 242 | check b == 1 243 | else: 244 | fail() 245 | 246 | test "=? binds and unpacks tuples returned from proc": 247 | proc returnsTuple(): ?tuple[name: string, id: int] = some ("test", 1) 248 | 249 | if (a, b) =? returnsTuple(): 250 | check a == "test" 251 | check b == 1 252 | else: 253 | fail() 254 | 255 | test "=? binds and unpacks tuples returned from proc with unnamed fields": 256 | proc returnsTuple(): ?(string, int,) = some ("test", 1,) 257 | 258 | if (a, b,) =? returnsTuple(): 259 | check a == "test" 260 | check b == 1 261 | else: 262 | fail() 263 | 264 | test "=? binds and unpacks tuples with _": 265 | if (_, b) =? some ("test", 1): 266 | check b == 1 267 | else: 268 | fail() 269 | 270 | test "=? binds and unpacks tuples with named fields": 271 | if (a, b) =? some (desc: "test", id: 1): 272 | check a == "test" 273 | check b == 1 274 | else: 275 | fail() 276 | 277 | test "=? binds variable to tuples with named fields": 278 | if t =? some (desc: "test", id: 1): 279 | check t.desc == "test" 280 | check t.id == 1 281 | else: 282 | fail() 283 | 284 | test "=? binds to tuple types": 285 | type MyTuple = tuple 286 | desc: string 287 | id: int 288 | 289 | let mt: MyTuple = ("test", 1) 290 | 291 | if t =? (some mt): 292 | check t.desc == "test" 293 | check t.id == 1 294 | else: 295 | fail() 296 | 297 | if (a, b) =? (some mt): 298 | check a == "test" 299 | check b == 1 300 | else: 301 | fail() 302 | 303 | test "=? for tuples does not leak symbols into caller's scope": 304 | const evaluated = "" 305 | type T = string 306 | if (a,) =? some (0,): 307 | check a == 0 308 | check option is proc 309 | check evaluated is string 310 | check T is string 311 | 312 | test "without statement can be used for early returns": 313 | proc test1 = 314 | without a =? 42.some: 315 | fail 316 | return 317 | check a == 42 318 | 319 | proc test2 = 320 | without a =? int.none: 321 | return 322 | fail 323 | 324 | test1() 325 | test2() 326 | 327 | test "without statement evaluates optional expression only once": 328 | var count = 0 329 | without a =? (inc count; 42.some): 330 | discard 331 | check count == 1 332 | 333 | test ".?[] can be used for indexing tables without raising KeyError": 334 | let table = @{"a": 1, "b": 2}.toTable 335 | check table.?["a"] == 1.some 336 | check table.?["c"] == int.none 337 | 338 | test ".?[] can be used for indexing strings without raising IndexDefect": 339 | let str = "a" 340 | check str.?[0] == 'a'.some 341 | check str.?[1] == char.none 342 | check str.?[-1] == char.none 343 | 344 | test ".?[] can be used for indexing sequences without raising IndexDefect": 345 | let sequence = @[1] 346 | check sequence.?[0] == 1.some 347 | check sequence.?[1] == int.none 348 | check sequence.?[-1] == int.none 349 | 350 | test ".?[] can be used for indexing openArrays without raising IndexDefect": 351 | proc checkOpenArray(oa: openArray[int]): void = 352 | check oa.?[0] == 1.some 353 | check oa.?[1] == int.none 354 | check oa.?[-1] == int.none 355 | 356 | checkOpenArray(@[1]) 357 | 358 | test ".?[] evaluates openArray expression only once": 359 | var count = 0 360 | discard (inc count; @[1].toOpenArray(0, 0)).?[0] 361 | check count == 1 362 | 363 | test ".?[] can be followed by calls, operators and indexing": 364 | let table = @{"a": @[41, 42]}.toTable 365 | check table.?["a"].isSome 366 | check table.?["a"].isSome() 367 | check table.?["a"][0] == 41.some 368 | check table.?["a"].?len.unsafeGet == 2 369 | check table.?["a"].?len.unsafeGet.uint8.uint64 == 2'u64 370 | check table.?["a"].?len.unsafeGet() == 2 371 | check table.?["a"].?len.unsafeGet().uint8.uint64 == 2'u64 372 | check table.?["a"].?deduplicate()[0].?uint8.?uint64 == 41'u64.some 373 | check table.?["a"].?len + 1 == 3.some 374 | check table.?["a"].?deduplicate()[0] + 1 == 42.some 375 | check table.?["a"].?deduplicate.map(x => x) == @[41, 42].some 376 | 377 | test "=? works with .?[]": 378 | let table = @{"a": 42}.toTable 379 | if a =? table.?["a"]: 380 | check a == 42 381 | else: 382 | fail 383 | 384 | test "unary operator `-` works for options": 385 | check -(-42.some) == 42.some 386 | check -(int.none) == int.none 387 | 388 | test "other unary operators work for options": 389 | check +(42.some) == 42.some 390 | check @([1, 2].some) == (@[1, 2]).some 391 | 392 | test "binary operator `+` works for options": 393 | check 40.some + 2.some == 42.some 394 | check 40.some + 2 == 42.some 395 | check int.none + 2 == int.none 396 | check 40.some + int.none == int.none 397 | check int.none + int.none == int.none 398 | 399 | test "other binary operators work for options": 400 | check 21.some * 2 == 42.some 401 | check 84'f.some / 2'f == 42'f.some 402 | check 84.some div 2 == 42.some 403 | check 85.some mod 43 == 42.some 404 | check 0b00110011.some shl 1 == 0b01100110.some 405 | check 0b00110011.some shr 1 == 0b00011001.some 406 | check 44.some - 2 == 42.some 407 | check "f".some & "oo" == "foo".some 408 | check 40.some <= 42 == true.some 409 | check 40.some < 42 == true.some 410 | check 40.some >= 42 == false.some 411 | check 40.some > 42 == false.some 412 | 413 | test ".? avoids wrapping option in option": 414 | let a = 41.some 415 | 416 | proc b(x: int): ?int = 417 | some x + 1 418 | 419 | check a.?b == 42.some 420 | 421 | test "lifted operators avoid wrapping option in option": 422 | let a = 40.some 423 | let b = 2.some 424 | 425 | func `&`(x, y: int): ?int = 426 | some x + y 427 | 428 | check (a & b) == 42.some 429 | 430 | test ".?[] avoids wrapping option in option": 431 | let a = @[41, 42].some 432 | 433 | check a.?[1] == 42.some 434 | 435 | test ".? chain evaluates optional expression only once": 436 | var count = 0 437 | discard (inc count; @[41, 42].some).?len 438 | check count == 1 439 | 440 | test "=? evaluates optional expression only once": 441 | var count = 0 442 | if a =? (inc count; 42.some): 443 | let b {.used.} = a 444 | check count == 1 445 | 446 | count = 0 447 | if var a =? (inc count; 42.some): 448 | let b {.used.} = a 449 | check count == 1 450 | 451 | test "|? evaluates optional expression only once": 452 | var count = 0 453 | discard (inc count; 42.some) |? 43 454 | check count == 1 455 | 456 | test ".?[] evaluates optional expression only once": 457 | # indexing on optional sequence: 458 | block: 459 | var count = 0 460 | discard (inc count; @[41, 42].some).?[0] 461 | check count == 1 462 | # indexing on normal sequence: 463 | block: 464 | var count = 0 465 | discard (inc count; @[41, 42]).?[0] 466 | check count == 1 467 | 468 | test "lifted unary operators evaluate optional expression only once": 469 | var count = 0 470 | discard -(inc count; 42.some) 471 | check count == 1 472 | 473 | test "lifted binary operators evaluate optional expressions only once": 474 | # lifted operator on two options: 475 | block: 476 | var count1, count2 = 0 477 | discard (inc count1; 40.some) + (inc count2; 2.some) 478 | check count1 == 1 479 | check count2 == 1 480 | # lifted operator on option and value: 481 | block: 482 | var count1, count2 = 0 483 | discard (inc count1; 40.some) + (inc count2; 2) 484 | check count1 == 1 485 | check count2 == 1 486 | 487 | test "examples from readme work": 488 | 489 | var x: ?int 490 | 491 | x = 42.some 492 | x = int.none 493 | 494 | # Option binding 495 | 496 | x = 42.some 497 | 498 | if y =? x: 499 | check y == 42 500 | else: 501 | fail 502 | 503 | x = int.none 504 | 505 | if y =? x: 506 | fail 507 | else: 508 | check not compiles(y) 509 | 510 | # without statement 511 | 512 | proc someProc(option: ?int) = 513 | without value =? option: 514 | check option.isNone 515 | return 516 | 517 | check value == 42 518 | 519 | someProc(int.none) 520 | someProc(42.some) 521 | 522 | # Option chaining 523 | 524 | var numbers: ?seq[int] 525 | var amount: ?int 526 | 527 | numbers = @[1, 2, 3].some 528 | amount = numbers.?len 529 | check amount == 3.some 530 | 531 | numbers = seq[int].none 532 | amount = numbers.?len 533 | check amount == int.none 534 | 535 | numbers = @[1, 1, 2, 2, 2].some 536 | amount = numbers.?deduplicate.?len 537 | check amount == 2.some 538 | 539 | # Fallback values 540 | 541 | x = int.none 542 | 543 | let z = x |? 3 544 | check z == 3 545 | 546 | # Obtaining value with ! 547 | 548 | x = 42.some 549 | let dare = !x 550 | check dare == 42 551 | 552 | x = int.none 553 | expect Defect: 554 | let crash {.used.} = !x 555 | 556 | # Operators 557 | 558 | numbers = @[1, 2, 3].some 559 | x = 39.some 560 | 561 | let indexed = numbers[0] 562 | check indexed == 1.some 563 | let sum = x + 3 564 | check sum == 42.some 565 | -------------------------------------------------------------------------------- /testmodules/options/test.nimble: -------------------------------------------------------------------------------- 1 | version = "0.1.0" 2 | author = "Questionable Authors" 3 | description = "Questionable tests for std/option" 4 | license = "MIT" 5 | 6 | task test, "Runs the test suite": 7 | var options = "-f -r --skipParentCfg" 8 | when (NimMajor, NimMinor) >= (1, 4): 9 | options &= " --warningAsError[UnsafeDefault]:on" 10 | options &= " --warningAsError[ProveInit]:on" 11 | exec "nim c " & options & " test.nim" 12 | -------------------------------------------------------------------------------- /testmodules/results/config.nims: -------------------------------------------------------------------------------- 1 | --path:"../.." 2 | --threads:on 3 | import "../../config.nims" 4 | -------------------------------------------------------------------------------- /testmodules/results/nimbledeps/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codex-storage/questionable/2e7f20392b6fbd8b1eeba84f326f06b8be0b5101/testmodules/results/nimbledeps/.keep -------------------------------------------------------------------------------- /testmodules/results/test.nim: -------------------------------------------------------------------------------- 1 | import std/unittest 2 | import std/options 3 | import std/sequtils 4 | import std/strutils 5 | import std/sugar 6 | import std/threadpool 7 | import pkg/questionable/results 8 | 9 | {.experimental: "parallel".} 10 | 11 | suite "result": 12 | 13 | let error = newException(CatchableError, "error") 14 | 15 | test "?!Type is shorthand for Result[Type, ref CatchableError]": 16 | check (?!int is Result[int, ref CatchableError]) 17 | check (?!string is Result[string, ref CatchableError]) 18 | check (?!seq[bool] is Result[seq[bool], ref CatchableError]) 19 | 20 | test "conversion to string $ works for ?!Types": 21 | check $42.success == "success(42)" 22 | check $(int.failure "some error") == "failure(\"some error\")" 23 | 24 | test "! gets value or raises Defect": 25 | check !42.success == 42 26 | expect Defect: discard !int.failure error 27 | 28 | test ".? can be used for chaining results": 29 | let a: ?!seq[int] = @[41, 42].success 30 | let b: ?!seq[int] = seq[int].failure error 31 | check a.?len == 2.success 32 | check b.?len == int.failure error 33 | check a.?len.?uint8 == 2'u8.success 34 | check b.?len.?uint8 == uint8.failure error 35 | check a.?len() == 2.success 36 | check b.?len() == int.failure error 37 | check a.?distribute(2).?len() == 2.success 38 | check b.?distribute(2).?len() == int.failure error 39 | 40 | test ".? chain can be followed by . calls and operators": 41 | let a = @[41, 42].success 42 | check (a.?len.unsafeGet == 2) 43 | check (a.?len.unsafeGet.uint8.uint64 == 2'u64) 44 | check (a.?len.unsafeGet() == 2) 45 | check (a.?len.unsafeGet().uint8.uint64 == 2'u64) 46 | check (a.?deduplicate()[0].?uint8.?uint64 == 41'u64.success) 47 | check (a.?len + 1 == 3.success) 48 | check (a.?deduplicate()[0] + 1 == 42.success) 49 | check (a.?deduplicate.map(x => x) == @[41, 42].success) 50 | 51 | test ".? chains work in generic code": 52 | proc test[T](a: ?!T) = 53 | check (a.?len == 2.success) 54 | check (a.?len.?uint8 == 2'u8.success) 55 | check (a.?len() == 2.success) 56 | check (a.?distribute(2).?len() == 2.success) 57 | check (a.?len.unsafeGet == 2) 58 | check (a.?len.unsafeGet.uint8.uint64 == 2'u64) 59 | check (a.?len.unsafeGet() == 2) 60 | check (a.?len.unsafeGet().uint8.uint64 == 2'u64) 61 | check (a.?deduplicate()[0].?uint8.?uint64 == 41'u64.success) 62 | check (a.?len + 1 == 3.success) 63 | check (a.?deduplicate()[0] + 1 == 42.success) 64 | check (a.?deduplicate.map(x => x) == @[41, 42].success) 65 | 66 | test @[41, 42].success 67 | 68 | test "[] can be used for indexing results": 69 | let a: ?!seq[int] = @[1, 2, 3].success 70 | let b: ?!seq[int] = seq[int].failure error 71 | check a[1] == 2.success 72 | check a[^1] == 3.success 73 | check a[0..1] == @[1, 2].success 74 | check b[1] == int.failure error 75 | 76 | test "|? can be used to specify a fallback value": 77 | check 42.success |? 40 == 42 78 | check int.failure(error) |? 42 == 42 79 | 80 | test "=? can be used for optional binding": 81 | if a =? int.failure(error): 82 | fail 83 | 84 | if b =? 42.success: 85 | check b == 42 86 | else: 87 | fail 88 | 89 | while a =? 42.success: 90 | check a == 42 91 | break 92 | 93 | while a =? int.failure(error): 94 | fail 95 | break 96 | 97 | test "=? can appear multiple times in conditional expression": 98 | if a =? 42.success and b =? "foo".success: 99 | check a == 42 100 | check b == "foo" 101 | else: 102 | fail 103 | 104 | test "=? works with variable hiding": 105 | let a = 42.success 106 | if a =? a: 107 | check a == 42 108 | 109 | test "=? works with var": 110 | if var a =? 1.success and var b =? 2.success: 111 | check a == 1 112 | inc a 113 | check a == b 114 | inc b 115 | check b == 3 116 | else: 117 | fail 118 | 119 | if var a =? int.failure(error): 120 | fail 121 | 122 | test "=? works with .?": 123 | if a =? 42.success.?uint8: 124 | check a == 42.uint8 125 | else: 126 | fail 127 | 128 | test "=? evaluates optional expression only once": 129 | var count = 0 130 | if a =? (inc count; 42.success): 131 | let b {.used.} = a 132 | check count == 1 133 | 134 | count = 0 135 | if var a =? (inc count; 42.success): 136 | let b {.used.} = a 137 | check count == 1 138 | 139 | test "=? works in generic code": 140 | proc toString[T](res: ?!T): string = 141 | if value =? res: 142 | $value 143 | else: 144 | "error" 145 | 146 | check 42.success.toString == "42" 147 | check int.failure(error).toString == "error" 148 | 149 | test "=? works in generic code with variable hiding": 150 | let value {.used.} = "ignored" 151 | 152 | proc toString[T](res: ?!T): string = 153 | if value =? res: 154 | $value 155 | else: 156 | "error" 157 | 158 | check 42.success.toString == "42" 159 | check int.failure(error).toString == "error" 160 | 161 | test "=? works with closures": 162 | var called = false 163 | let closure = success(proc () = called = true) 164 | 165 | if a =? failure(proc (), error): 166 | a() 167 | 168 | check not called 169 | 170 | if a =? closure: 171 | a() 172 | 173 | check called 174 | 175 | test "=? binds and unpacks tuples": 176 | if (a, b) =? (success ("test", 1)): 177 | check a == "test" 178 | check b == 1 179 | else: 180 | fail() 181 | 182 | if (a, b) =? (string, int).failure(error): 183 | discard a 184 | discard b 185 | fail() 186 | 187 | test "=? binds and unpacks tuples with named fields": 188 | if (a, b) =? (success (desc: "test", id: 1)): 189 | check a == "test" 190 | check b == 1 191 | else: 192 | fail() 193 | 194 | test "=? binds and unpacks tuples returned from proc": 195 | proc returnsTuple(): ?!tuple[name: string, id: int] = success ("test", 1) 196 | 197 | if (a, b) =? returnsTuple(): 198 | check a == "test" 199 | check b == 1 200 | else: 201 | fail() 202 | 203 | test "=? binds and unpacks tuples returned from proc with unnamed fields": 204 | proc returnsTuple(): ?!(string, int,) = success ("test", 1,) 205 | 206 | if (a, b,) =? returnsTuple(): 207 | check a == "test" 208 | check b == 1 209 | else: 210 | fail() 211 | 212 | test "=? binds and unpacks tuples with _": 213 | if (_, b) =? success ("test", 1): 214 | check b == 1 215 | else: 216 | fail() 217 | 218 | test "=? binds and unpacks tuples with named fields": 219 | if (a, b) =? success (desc: "test", id: 1): 220 | check a == "test" 221 | check b == 1 222 | else: 223 | fail() 224 | 225 | test "=? binds variable to tuples with named fields": 226 | if t =? success (desc: "test", id: 1): 227 | check t.desc == "test" 228 | check t.id == 1 229 | else: 230 | fail() 231 | 232 | test "=? binds to tuple types": 233 | type MyTuple = tuple 234 | desc: string 235 | id: int 236 | 237 | let mt: MyTuple = ("test", 1) 238 | 239 | if t =? (success mt): 240 | check t.desc == "test" 241 | check t.id == 1 242 | else: 243 | fail() 244 | 245 | if (a, b) =? (success mt): 246 | check a == "test" 247 | check b == 1 248 | else: 249 | fail() 250 | 251 | test "without statement works for results": 252 | proc test1 = 253 | without a =? 42.success: 254 | fail 255 | return 256 | check a == 42 257 | 258 | proc test2 = 259 | without a =? int.failure "error": 260 | return 261 | fail 262 | 263 | test1() 264 | test2() 265 | 266 | test "without statement can expose error": 267 | proc test = 268 | without a =? int.failure "some error", error: 269 | check error.msg == "some error" 270 | return 271 | fail 272 | 273 | test() 274 | 275 | test "without statement only exposes error variable inside block": 276 | proc test = 277 | without a =? 42.success, errorvar: 278 | fail 279 | discard errorvar # fixes warning about unused variable "errorvar" 280 | return 281 | check not compiles errorvar 282 | 283 | test() 284 | 285 | test "without statements with multiple bindings exposes first error": 286 | proc test1 = 287 | without (a =? int.failure "error 1") and 288 | (b =? int.failure "error 2"), 289 | error: 290 | check error.msg == "error 1" 291 | return 292 | fail 293 | 294 | proc test2 = 295 | without (a =? 42.success) and (b =? int.failure "error 2"), error: 296 | check error.msg == "error 2" 297 | return 298 | fail 299 | 300 | test1() 301 | test2() 302 | 303 | test "without statement with error evaluates result only once": 304 | proc test = 305 | var count = 0 306 | without a =? (inc count; int.failure "error"): 307 | check count == 1 308 | return 309 | fail 310 | 311 | test() 312 | 313 | test "without statement with error handles options as well": 314 | proc test1 = 315 | without a =? int.none and b =? int.failure "error", error: 316 | check error.msg == "Option is set to `none`" 317 | return 318 | fail 319 | 320 | proc test2 = 321 | without a =? 42.some and b =? int.failure "error", error: 322 | check error.msg == "error" 323 | return 324 | fail 325 | 326 | test1() 327 | test2() 328 | 329 | test "without statement with error handles references as well": 330 | proc test = 331 | var x: ref int = nil 332 | without a =? x, error: 333 | check error.msg == "ref is nil" 334 | return 335 | fail 336 | 337 | test() 338 | 339 | test "without statement with error handles pointers as well": 340 | proc test = 341 | var x: ptr int = nil 342 | without a =? x, error: 343 | check error.msg == "ptr is nil" 344 | return 345 | fail 346 | 347 | test() 348 | 349 | test "without statement with error handles closures as well": 350 | proc test = 351 | var x = proc = discard 352 | x = nil 353 | without a =? x, error: 354 | check error.msg == "proc or iterator is nil" 355 | return 356 | fail 357 | 358 | test() 359 | 360 | test "without statement with error handles iterators as well": 361 | when (NimMajor, NimMinor) != (2, 0): 362 | proc test = 363 | var x: iterator: int = nil 364 | without a =? x, error: 365 | check error.msg == "proc or iterator is nil" 366 | return 367 | fail 368 | 369 | test() 370 | 371 | test "without statement with error can be used more than once": 372 | proc test = 373 | without a =? 42.success, error: 374 | discard error 375 | fail 376 | without b =? 42.success, error: 377 | discard error 378 | fail 379 | 380 | test() 381 | 382 | test "without statement with error works with deeply nested =? operators": 383 | proc test = 384 | let fail1 = int.failure "error 1" 385 | let fail2 = int.failure "error 2" 386 | without (block: a =? (if b =? fail1: b.success else: fail2)), error: 387 | check error.msg == "error 2" 388 | return 389 | fail 390 | 391 | test() 392 | 393 | test "without statement with error works in generic code": 394 | proc test(_: type) = 395 | without a =? int.failure "error", e: 396 | check e.msg == "error" 397 | return 398 | fail 399 | 400 | test(int) 401 | 402 | test "without statements with error can be nested": 403 | without a =? int.failure "error1", e1: 404 | without b =? int.failure "error2", e2: 405 | check e1.msg == "error1" 406 | check e2.msg == "error2" 407 | check e1.msg == "error1" 408 | 409 | test "without statement works in generic code using existing error name": 410 | let existingName {.used.} = "some variable" 411 | 412 | proc shouldCompile(_: type int): ?!int = 413 | without _ =? int.failure "error", existingName: 414 | check existingName.msg == "error" 415 | return success 42 416 | 417 | discard int.shouldCompile() 418 | 419 | test "without statements with error work in nested calls": 420 | proc bar(): ?!int = 421 | without _ =? int.failure "error", err: 422 | return failure err.msg 423 | 424 | proc foo() = 425 | without _ =? bar(), err: 426 | check err.msg == "error" 427 | return 428 | fail() 429 | 430 | foo() 431 | 432 | test "without statement with error works in nested generic calls": 433 | proc works(_: type int): ?!int = 434 | without _ =? int.failure "error1", error: 435 | check error.msg == "error1" 436 | return success 42 437 | 438 | proc fails(_: type int): ?!int = 439 | return failure "error2" 440 | 441 | proc foo = 442 | without a =? int.works() and b =? int.fails(), error: 443 | check error.msg == "error2" 444 | return 445 | fail() 446 | 447 | foo() 448 | 449 | test "without statement with error works with multiple threads": 450 | proc fail(number: int) = 451 | without _ =? int.failure "error" & $number, error: 452 | check error.msg == "error" & $number 453 | parallel: 454 | for i in 0..<1000: 455 | spawn fail(i) 456 | 457 | test "without statement doesn't interfere with generic code called elsewhere": 458 | proc foo(_: type): ?!int = 459 | if error =? success(1).errorOption: 460 | discard 461 | 462 | proc bar {.used.} = # defined, but not used 463 | without x =? bool.foo(), error: 464 | discard error 465 | 466 | discard bool.foo() # same type parameter 'bool' as used in bar() 467 | 468 | test "catch can be used to convert exceptions to results": 469 | check parseInt("42").catch == 42.success 470 | check parseInt("foo").catch.error of ValueError 471 | 472 | test "success can be called without argument": 473 | check (success() is ?!void) 474 | 475 | test "failure can be called with string argument": 476 | let value = int.failure("some failure") 477 | check value.error of ResultFailure 478 | check value.error.msg == "some failure" 479 | 480 | test "unary operator `-` works for results": 481 | check -(-42.success) == 42.success 482 | check -(int.failure(error)) == int.failure(error) 483 | 484 | test "other unary operators work for results": 485 | check +(42.success) == 42.success 486 | check @([1, 2].success) == (@[1, 2]).success 487 | 488 | test "binary operator `+` works for results": 489 | check 40.success + 2.success == 42.success 490 | check 40.success + 2 == 42.success 491 | check int.failure(error) + 2 == int.failure(error) 492 | check 40.success + int.failure(error) == int.failure(error) 493 | check int.failure(error) + int.failure(error) == int.failure(error) 494 | 495 | test "other binary operators work for results": 496 | check (21.success * 2 == 42.success) 497 | check (84'f.success / 2'f == 42'f.success) 498 | check (84.success div 2 == 42.success) 499 | check (85.success mod 43 == 42.success) 500 | check (0b00110011.success shl 1 == 0b01100110.success) 501 | check (0b00110011.success shr 1 == 0b00011001.success) 502 | check (44.success - 2 == 42.success) 503 | check ("f".success & "oo" == "foo".success) 504 | check (40.success <= 42 == true.success) 505 | check (40.success < 42 == true.success) 506 | check (40.success >= 42 == false.success) 507 | check (40.success > 42 == false.success) 508 | 509 | test "Result can be converted to Option": 510 | check 42.success.option == 42.some 511 | check int.failure(error).option == int.none 512 | 513 | test "Result error can be converted to Option": 514 | check (int.failure(error).errorOption == error.some) 515 | check (42.success.errorOption == (ref CatchableError).none) 516 | check (void.failure(error).errorOption == error.some) 517 | check (success().errorOption == (ref CatchableError).none) 518 | 519 | test "failure can be used without type parameter in procs": 520 | proc fails: ?!int = 521 | failure "some error" 522 | 523 | check fails().isFailure 524 | check fails().error.msg == "some error" 525 | 526 | test ".? avoids wrapping result in result": 527 | let a = 41.success 528 | 529 | proc b(x: int): ?!int = 530 | success x + 1 531 | 532 | check a.?b == 42.success 533 | 534 | test "lifted operators avoid wrapping result in result": 535 | let a = 40.success 536 | let b = 2.success 537 | 538 | func `&`(x, y: int): ?!int = 539 | success x + y 540 | 541 | check (a & b) == 42.success 542 | 543 | test ".? chain evaluates result only once": 544 | var count = 0 545 | discard (inc count; @[41, 42].success).?len 546 | check count == 1 547 | 548 | test "=? evaluates result only once": 549 | var count = 0 550 | if a =? (inc count; 42.success): 551 | let b {.used.} = a 552 | check count == 1 553 | 554 | count = 0 555 | if var a =? (inc count; 42.success): 556 | let b {.used.} = a 557 | check count == 1 558 | 559 | test "|? evaluates result only once": 560 | var count = 0 561 | discard (inc count; 42.success) |? 43 562 | check count == 1 563 | 564 | test ".?[] evaluates result only once": 565 | var count = 0 566 | discard (inc count; @[41, 42].success).?[0] 567 | check count == 1 568 | 569 | test "lifted unary operators evaluate result only once": 570 | var count = 0 571 | discard -(inc count; 42.success) 572 | check count == 1 573 | 574 | test "lifted binary operators evaluate results only once": 575 | # lifted operator on two options: 576 | block: 577 | var count1, count2 = 0 578 | discard (inc count1; 40.success) + (inc count2; 2.success) 579 | check count1 == 1 580 | check count2 == 1 581 | # lifted operator on option and value: 582 | block: 583 | var count1, count2 = 0 584 | discard (inc count1; 40.success) + (inc count2; 2) 585 | check count1 == 1 586 | check count2 == 1 587 | 588 | test "conversion to option evaluates result only once": 589 | var count = 0 590 | discard (inc count; 42.success).option 591 | check count == 1 592 | 593 | test "conversion to error evaluates result only once": 594 | var count = 0 595 | discard (inc count; int.failure(error)).errorOption 596 | check count == 1 597 | 598 | test "examples from readme work": 599 | 600 | proc works: ?!seq[int] = 601 | success @[1, 1, 2, 2, 2] 602 | 603 | proc fails: ?!seq[int] = 604 | failure "something went wrong" 605 | 606 | # binding: 607 | if x =? works(): 608 | check x == @[1, 1, 2, 2, 2] 609 | else: 610 | fail 611 | 612 | # chaining: 613 | let amount = works().?deduplicate.?len 614 | check (amount == 2.success) 615 | 616 | # fallback values: 617 | let value = fails() |? @[] 618 | check (value == newSeq[int](0)) 619 | 620 | # lifted operators: 621 | let sum = works()[3] + 40 622 | check (sum == 42.success) 623 | 624 | # catch 625 | let x = parseInt("42").catch 626 | check (x == 42.success) 627 | let y = parseInt("XX").catch 628 | check y.isFailure 629 | 630 | # Conversion to Option 631 | 632 | let converted = works().option 633 | check (converted == @[1, 1, 2, 2, 2].some) 634 | 635 | # Without statement 636 | proc someProc(r: ?!int) = 637 | without value =? r, error: 638 | check error.msg == "some error" 639 | return 640 | 641 | check value == 42 642 | 643 | someProc(42.success) 644 | someProc(int.failure "some error") 645 | 646 | type TypeWithSideEffect = object 647 | proc `$`*(value: TypeWithSideEffect): string {.sideEffect.} = 648 | discard 649 | 650 | suite "result side effects": 651 | 652 | test "without statement with error works when `$` has side effects": 653 | proc foo = 654 | without x =? TypeWithSideEffect.failure("error"), error: 655 | discard error 656 | return 657 | fail() 658 | foo() 659 | 660 | import pkg/questionable/resultsbase 661 | 662 | suite "result compatibility": 663 | 664 | type R = Result[int, string] 665 | let good = R.ok 42 666 | let bad = R.err "some error" 667 | 668 | test "|?, =? and .option work on other types of Result": 669 | check bad |? 43 == 43 670 | 671 | if value =? good: 672 | check value == 42 673 | else: 674 | fail 675 | 676 | check good.option == 42.some 677 | 678 | test "=? works on other type of Result after without statement with error": 679 | without a =? 42.success, error: 680 | discard error # fixes warning about unused variable "error" 681 | fail 682 | without b =? good: 683 | fail 684 | 685 | test "without statement with error works on other type of Result": 686 | without value =? bad, error: 687 | check error of ResultFailure 688 | check error.msg == "some error" 689 | 690 | test "without statement with error works on Result[T, void]": 691 | without value =? Result[int, void].err, error: 692 | check error of ResultFailure 693 | check error.msg == "Result is an error" 694 | -------------------------------------------------------------------------------- /testmodules/results/test.nimble: -------------------------------------------------------------------------------- 1 | version = "0.1.0" 2 | author = "Questionable Authors" 3 | description = "Questionable tests for pkg/result" 4 | license = "MIT" 5 | 6 | requires "results" 7 | 8 | task test, "Runs the test suite": 9 | exec "nim c -f -r --skipParentCfg test.nim" 10 | -------------------------------------------------------------------------------- /testmodules/stew/config.nims: -------------------------------------------------------------------------------- 1 | --path:"../.." 2 | --threads:on 3 | import "../../config.nims" 4 | -------------------------------------------------------------------------------- /testmodules/stew/nimbledeps/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codex-storage/questionable/2e7f20392b6fbd8b1eeba84f326f06b8be0b5101/testmodules/stew/nimbledeps/.keep -------------------------------------------------------------------------------- /testmodules/stew/test.nim: -------------------------------------------------------------------------------- 1 | include ../results/test 2 | -------------------------------------------------------------------------------- /testmodules/stew/test.nimble: -------------------------------------------------------------------------------- 1 | version = "0.1.0" 2 | author = "Questionable Authors" 3 | description = "Questionable tests for pkg/stew" 4 | license = "MIT" 5 | 6 | when (NimMajor, NimMinor) >= (1, 6): 7 | requires "stew" 8 | 9 | task test, "Runs the test suite": 10 | exec "nim c -f -r --skipParentCfg test.nim" 11 | else: 12 | task test, "Runs the test suite": 13 | echo "Warning: Skipping test with stew on Nim < 1.6" 14 | --------------------------------------------------------------------------------