Compare commits

..

No commits in common. "main" and "0.10.5" have entirely different histories.
main ... 0.10.5

27 changed files with 106 additions and 679 deletions

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
nim: [stable, 1.6.16, 1.4.8, 1.2.18] nim: [stable, 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

View File

@ -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.10.15 & < 0.11.0" requires "questionable >= 0.10.5 & < 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 "results" # either this requires "result" # either this
requires "stew" # or this requires "stew" # or this
``` ```
@ -151,7 +151,7 @@ have to explicitly import the `questionable/results` module:
import questionable/results import questionable/results
``` ```
You can use `?!` to make a Result type. These Result types either hold a value or You can 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 an error. For example the type `?!int` is short for `Result[int, ref
CatchableError]`. CatchableError]`.
@ -226,7 +226,6 @@ 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
``` ```
[1]: https://nim-lang.org/docs/options.html [1]: https://nim-lang.org/docs/options.html

View File

@ -1,6 +1,5 @@
# Style Check # Style Check
if (NimMajor, NimMinor, NimPatch) >= (1, 6, 6): --styleCheck:usages
--styleCheck:usages
if (NimMajor, NimMinor) < (1, 6): if (NimMajor, NimMinor) < (1, 6):
--styleCheck:hint --styleCheck:hint
else: else:

View File

@ -1,10 +1,10 @@
version = "0.10.15" version = "0.10.5"
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", "results", "stew"]: for module in ["options", "result", "stew"]:
withDir "testmodules/" & module: withDir "testmodules/" & module:
delEnv "NIMBLE_DIR" # use nimbledeps dir delEnv "NIMBLE_DIR" # use nimbledeps dir
exec "nimble install -d -y" exec "nimble install -d -y"

View File

@ -2,25 +2,15 @@ import std/options
import std/macros import std/macros
import ./private/binderror import ./private/binderror
when (NimMajor, NimMinor) < (1, 1): proc option[T](option: Option[T]): Option[T] =
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 option
template toOption[T: SomePointer](value: T): Option[T] =
value.option
proc placeholder(T: type): T = proc placeholder(T: type): T =
discard discard
template bindLet(name, expression): untyped = template bindLet(name, expression): bool =
let evaluated = expression let evaluated = expression
let option = evaluated.toOption let option = evaluated.option
type T = typeof(option.unsafeGet()) type T = typeof(option.unsafeGet())
let name {.used.} = if option.isSome: let name {.used.} = if option.isSome:
option.unsafeGet() option.unsafeGet()
@ -29,9 +19,9 @@ template bindLet(name, expression): untyped =
placeholder(T) placeholder(T)
option.isSome option.isSome
template bindVar(name, expression): untyped = template bindVar(name, expression): bool =
let evaluated = expression let evaluated = expression
let option = evaluated.toOption let option = evaluated.option
type T = typeof(option.unsafeGet()) type T = typeof(option.unsafeGet())
var name {.used.} = if option.isSome: var name {.used.} = if option.isSome:
option.unsafeGet() option.unsafeGet()
@ -40,50 +30,14 @@ template bindVar(name, expression): untyped =
placeholder(T) placeholder(T)
option.isSome 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 = macro `=?`*(name, expression): bool =
## The `=?` operator lets you bind the value inside an Option or Result to a ## 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 ## new variable. It can be used inside of a conditional expression, for
## instance in an `if` statement. ## instance in an `if` statement.
when (NimMajor, NimMinor) < (1, 6): name.expectKind({nnkIdent, nnkVarTy})
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr, nnkPar})
else:
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr})
if name.kind == nnkIdent: if name.kind == nnkIdent:
quote do: bindLet(`name`, `expression`) quote do: bindLet(`name`, `expression`)
elif name.kind == nnkTupleConstr or name.kind == nnkPar:
quote do: bindTuple(`name`, `expression`)
else: else:
let name = name[0] let name = name[0]
quote do: bindVar(`name`, `expression`) quote do: bindVar(`name`, `expression`)

View File

@ -15,34 +15,50 @@ macro expectReturnType(identifier: untyped, expression: untyped): untyped =
when compiles(`expression`) and not compiles(typeof `expression`): when compiles(`expression`) and not compiles(typeof `expression`):
{.error: `message`.} {.error: `message`.}
template chain(option: typed, identifier: untyped{nkIdent}): untyped = template `.?`*(option: typed, identifier: untyped{nkIdent}): 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.
# chain is of shape: option.?identifier # chain is of shape: option.?identifier
expectReturnType(identifier, option.unsafeGet.identifier) expectReturnType(identifier, option.unsafeGet.identifier)
option ->? option.unsafeGet.identifier option ->? option.unsafeGet.identifier
macro chain(option: typed, infix: untyped{nkInfix}): untyped = macro `.?`*(option: typed, infix: untyped{nkInfix}): 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.
# 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 chain(option: typed, bracket: untyped{nkBracketExpr}): untyped = macro `.?`*(option: typed, bracket: untyped{nkBracketExpr}): 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.
# 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 chain(option: typed, dot: untyped{nkDotExpr}): untyped = macro `.?`*(option: typed, dot: untyped{nkDotExpr}): 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.
# 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 chain(option: typed, call: untyped{nkCall}): untyped = macro `.?`*(option: typed, call: untyped{nkCall}): untyped =
let call = call.copyNimTree() ## 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.
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()
@ -65,15 +81,11 @@ macro chain(option: typed, call: untyped{nkCall}): untyped =
expectReturnType(`procedure`, `call`) expectReturnType(`procedure`, `call`)
`option` ->? `call` `option` ->? `call`
macro chain(option: typed, symbol: untyped): untyped = macro `.?`*(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 ## The `.?` chaining operator is used to safely access fields and call procs
## on Options or Results. The expression is only evaluated when the preceding ## on Options or Results. The expression is only evaluated when the preceding
## Option or Result has a value. ## Option or Result has a value.
block:
let evaluated = left symbol.expectSym()
chain(evaluated, right) let expression = ident($symbol)
quote do: `option`.?`expression`

View File

@ -1,30 +1,9 @@
import std/macros import std/macros
import std/options
proc safeGet[T](expression: seq[T] | openArray[T], index: int): Option[T] =
if index >= expression.low and index <= expression.high:
expression[index].some
else:
T.none
proc safeGet(expression: string, index: int): Option[char] =
if index >= expression.low and index <= expression.high:
expression[index].some
else:
char.none
macro `.?`*(expression: seq | string | openArray, brackets: untyped{nkBracket}): untyped =
# chain is of shape: (seq or string or openArray).?[index]
let index = brackets[0]
quote do:
block:
safeGet(`expression`, `index`)
macro `.?`*(expression: typed, brackets: untyped{nkBracket}): untyped = macro `.?`*(expression: typed, brackets: untyped{nkBracket}): untyped =
# chain is of shape: expression.?[index] # chain is of shape: expression.?[index]
let index = brackets[0] let index = brackets[0]
quote do: quote do:
block:
type T = typeof(`expression`[`index`]) type T = typeof(`expression`[`index`])
try: try:
`expression`[`index`].some `expression`[`index`].some

View File

@ -1,19 +1,12 @@
template liftUnary*(T: type, operator: untyped) = template liftUnary*(T: type, operator: untyped) =
template `operator`*(a: T): untyped = template `operator`*(a: T): untyped =
block: a ->? `operator`(a.unsafeGet())
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 =
block: (a, b) ->? `operator`(a.unsafeGet, b.unsafeGet)
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 =
block: a ->? `operator`(a.unsafeGet(), b)
let evalA = a
evalA ->? `operator`(evalA.unsafeGet(), b)

View File

@ -44,7 +44,7 @@ template `->?`*[T,U,V](options: (?T, ?U), expression: ?V): ?V =
template `->?`*[T,U,V](options: (?T, ?U), expression: V): ?V = template `->?`*[T,U,V](options: (?T, ?U), expression: V): ?V =
options ->? expression.some options ->? expression.some
proc `|?`*[T](option: ?T, fallback: T): T = template `|?`*[T](option: ?T, fallback: T): T =
## Use the `|?` operator to supply a fallback value when an Option does not ## Use the `|?` operator to supply a fallback value when an Option does not
## hold a value. ## hold a value.
@ -56,11 +56,9 @@ proc `|?`*[T](option: ?T, fallback: T): T =
macro `.?`*[T](option: ?T, brackets: untyped{nkBracket}): untyped = macro `.?`*[T](option: ?T, brackets: untyped{nkBracket}): untyped =
let index = brackets[0] let index = brackets[0]
quote do: quote do:
block: type U = typeof(`option`.unsafeGet().?[`index`].unsafeGet())
let evaluated = `option` if `option`.isSome:
type U = typeof(evaluated.unsafeGet().?[`index`].unsafeGet()) `option`.unsafeGet().?[`index`]
if evaluated.isSome:
evaluated.unsafeGet().?[`index`]
else: else:
U.none U.none

View File

@ -1,6 +0,0 @@
template ignoreBareExceptWarning*(body) =
when defined(nimHasWarnBareExcept):
{.push warning[BareExcept]:off warning[UnreachableCode]:off.}
body
when defined(nimHasWarnBareExcept):
{.pop.}

View File

@ -1,54 +1,24 @@
import std/options import std/options
import std/macros
# A stack of names of error variables. Keeps track of the error variables that var captures {.global, compileTime.}: int
# are given to captureBindError(). var errorVariable: ptr ref CatchableError
var errorVariableNames {.global, compileTime.}: seq[string]
macro captureBindError*(error: var ref CatchableError, expression): auto = template captureBindError*(error: var ref CatchableError, expression): auto =
## Ensures that an error is assigned to the error variable when a binding (=?) let previousErrorVariable = errorVariable
## fails inside the expression. errorVariable = addr error
# name of the error variable as a string literal static: inc captures
let errorVariableName = newLit($error) let evaluated = expression
static: dec captures
let evaluated = genSym(nskLet, "evaluated") errorVariable = previousErrorVariable
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 = evaluated
func error[T](option: Option[T]): ref CatchableError =
newException(ValueError, "Option is set to `none`") newException(ValueError, "Option is set to `none`")
func unsafeCatchableError[T](_: ref T): ref CatchableError = template bindFailed*(expression) =
newException(ValueError, "ref is nil") when captures > 0:
mixin error
func unsafeCatchableError[T](_: ptr T): ref CatchableError = errorVariable[] = expression.error
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

View File

@ -7,7 +7,6 @@ import ./indexing
import ./operators import ./operators
import ./without import ./without
import ./withoutresult import ./withoutresult
import ./private/bareexcept
include ./private/errorban include ./private/errorban
@ -74,12 +73,6 @@ proc isFailure*[T](value: ?!T): bool =
value.isErr value.isErr
proc `$`*[T](value: ?!T): string =
if value.isSuccess:
"success(" & $(!value) & ")"
else:
"failure(\"" & $(value.error.msg) & "\")"
template `->?`*[T,U](value: ?!T, expression: ?!U): ?!U = template `->?`*[T,U](value: ?!T, expression: ?!U): ?!U =
if value.isFailure: if value.isFailure:
U.failure(value.error) U.failure(value.error)
@ -100,7 +93,7 @@ template `->?`*[T,U,V](values: (?!T, ?!U), expression: ?!V): ?!V =
template `->?`*[T,U,V](values: (?!T, ?!U), expression: V): ?!V = template `->?`*[T,U,V](values: (?!T, ?!U), expression: V): ?!V =
values ->? expression.success values ->? expression.success
proc `|?`*[T,E](value: Result[T,E], fallback: T): T = template `|?`*[T,E](value: Result[T,E], fallback: T): T =
## Use the `|?` operator to supply a fallback value when a Result does not ## Use the `|?` operator to supply a fallback value when a Result does not
## hold a value. ## hold a value.
@ -110,30 +103,12 @@ proc option*[T,E](value: Result[T,E]): ?T =
## Converts a Result into an Option. ## Converts a Result into an Option.
if value.isOk: if value.isOk:
ignoreBareExceptWarning:
try: # workaround for erroneous exception tracking when T is a closure try: # workaround for erroneous exception tracking when T is a closure
return value.unsafeGet.some value.unsafeGet.some
except Exception as exception: except Exception as exception:
raise newException(Defect, exception.msg, exception) raise newException(Defect, exception.msg, exception)
else: else:
return T.none 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 = proc errorOption*[T, E](value: Result[T, E]): ?E =
## Returns an Option that contains the error from the Result, if it has one. ## Returns an Option that contains the error from the Result, if it has one.

View File

@ -1,7 +1,7 @@
template tryImport(module) = import module template tryImport(module) = import module
when compiles tryImport pkg/results: when compiles tryImport pkg/result:
import pkg/results import pkg/result/../results
else: else:
import pkg/stew/results import pkg/stew/results

View File

@ -2,18 +2,16 @@ import std/macros
import ./without import ./without
import ./private/binderror import ./private/binderror
const symbolKinds = {nnkSym, nnkOpenSymChoice, nnkClosedSymChoice}
const identKinds = {nnkIdent} + symbolKinds
proc undoSymbolResolution(expression, ident: NimNode): NimNode = proc undoSymbolResolution(expression, ident: NimNode): NimNode =
## Finds symbols in the expression that match the `ident` and replaces them ## Finds symbols in the expression that match the `ident` and replaces them
## with `ident`, effectively undoing any symbol resolution that happened ## with `ident`, effectively undoing any symbol resolution that happened
## before. ## before.
const symbolKinds = {nnkSym, nnkOpenSymChoice, nnkClosedSymChoice}
if expression.kind in symbolKinds and eqIdent($expression, $ident): if expression.kind in symbolKinds and eqIdent($expression, $ident):
return ident return ident
let expression = expression.copyNimTree()
for i in 0..<expression.len: for i in 0..<expression.len:
expression[i] = undoSymbolResolution(expression[i], ident) expression[i] = undoSymbolResolution(expression[i], ident)
@ -23,9 +21,6 @@ macro without*(condition, errorname, body: untyped): untyped =
## Used to place guards that ensure that a Result contains a value. ## Used to place guards that ensure that a Result contains a value.
## Exposes error when Result does not contain 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 let errorIdent = ident $errorname
# Nim's early symbol resolution might have picked up a symbol with the # Nim's early symbol resolution might have picked up a symbol with the
@ -34,7 +29,7 @@ macro without*(condition, errorname, body: untyped): untyped =
let body = body.undoSymbolResolution(errorIdent) let body = body.undoSymbolResolution(errorIdent)
quote do: quote do:
var error {.gensym.}: ref CatchableError var error: ref CatchableError
without captureBindError(error, `condition`): without captureBindError(error, `condition`):
template `errorIdent`: ref CatchableError = error template `errorIdent`: ref CatchableError = error

View File

@ -1,2 +0,0 @@
--path:"../.."
import "../../config.nims"

View File

@ -0,0 +1 @@
--path:"../.."

View File

@ -115,6 +115,17 @@ suite "optionals":
else: else:
fail fail
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 "=? works in generic code": test "=? works in generic code":
proc toString[T](option: ?T): string = proc toString[T](option: ?T): string =
if value =? option: if value =? option:
@ -165,150 +176,6 @@ suite "optionals":
else: else:
fail() 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": test "without statement can be used for early returns":
proc test1 = proc test1 =
without a =? 42.some: without a =? 42.some:
@ -335,31 +202,6 @@ suite "optionals":
check table.?["a"] == 1.some check table.?["a"] == 1.some
check table.?["c"] == int.none 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
test ".?[] can be followed by calls, operators and indexing": test ".?[] can be followed by calls, operators and indexing":
let table = @{"a": @[41, 42]}.toTable let table = @{"a": @[41, 42]}.toTable
check table.?["a"].isSome check table.?["a"].isSome
@ -432,58 +274,6 @@ suite "optionals":
check a.?[1] == 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

View File

@ -4,7 +4,7 @@ description = "Questionable tests for std/option"
license = "MIT" license = "MIT"
task test, "Runs the test suite": task test, "Runs the test suite":
var options = "-f -r --skipParentCfg" var options = "-f -r"
when (NimMajor, NimMinor) >= (1, 4): when (NimMajor, NimMinor) >= (1, 4):
options &= " --warningAsError[UnsafeDefault]:on" options &= " --warningAsError[UnsafeDefault]:on"
options &= " --warningAsError[ProveInit]:on" options &= " --warningAsError[ProveInit]:on"

View File

@ -0,0 +1 @@
--path:"../.."

View File

@ -3,11 +3,8 @@ import std/options
import std/sequtils import std/sequtils
import std/strutils import std/strutils
import std/sugar import std/sugar
import std/threadpool
import pkg/questionable/results import pkg/questionable/results
{.experimental: "parallel".}
suite "result": suite "result":
let error = newException(CatchableError, "error") let error = newException(CatchableError, "error")
@ -17,10 +14,6 @@ suite "result":
check (?!string is Result[string, ref CatchableError]) check (?!string is Result[string, ref CatchableError])
check (?!seq[bool] is Result[seq[bool], 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": test "! gets value or raises Defect":
check !42.success == 42 check !42.success == 42
expect Defect: discard !int.failure error expect Defect: discard !int.failure error
@ -172,82 +165,6 @@ suite "result":
check called 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": test "without statement works for results":
proc test1 = proc test1 =
without a =? 42.success: without a =? 42.success:
@ -326,48 +243,6 @@ suite "result":
test1() test1()
test2() 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": test "without statement with error can be used more than once":
proc test = proc test =
without a =? 42.success, error: without a =? 42.success, error:
@ -446,25 +321,6 @@ suite "result":
foo() 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": test "catch can be used to convert exceptions to results":
check parseInt("42").catch == 42.success check parseInt("42").catch == 42.success
check parseInt("foo").catch.error of ValueError check parseInt("foo").catch.error of ValueError
@ -540,61 +396,6 @@ suite "result":
check (a & b) == 42.success 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": test "examples from readme work":
proc works: ?!seq[int] = proc works: ?!seq[int] =
@ -643,19 +444,6 @@ suite "result":
someProc(42.success) someProc(42.success)
someProc(int.failure "some error") 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 import pkg/questionable/resultsbase
@ -663,7 +451,7 @@ suite "result compatibility":
type R = Result[int, string] type R = Result[int, string]
let good = R.ok 42 let good = R.ok 42
let bad = R.err "some error" let bad = R.err "error"
test "|?, =? and .option work on other types of Result": test "|?, =? and .option work on other types of Result":
check bad |? 43 == 43 check bad |? 43 == 43
@ -681,13 +469,3 @@ suite "result compatibility":
fail fail
without b =? good: without b =? good:
fail 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"

View File

@ -3,7 +3,7 @@ author = "Questionable Authors"
description = "Questionable tests for pkg/result" description = "Questionable tests for pkg/result"
license = "MIT" license = "MIT"
requires "results" requires "result"
task test, "Runs the test suite": task test, "Runs the test suite":
exec "nim c -f -r --skipParentCfg test.nim" exec "nim c -f -r test.nim"

View File

@ -1,3 +0,0 @@
--path:"../.."
--threads:on
import "../../config.nims"

View File

@ -1,3 +0,0 @@
--path:"../.."
--threads:on
import "../../config.nims"

1
testmodules/stew/nim.cfg Normal file
View File

@ -0,0 +1 @@
--path:"../.."

View File

@ -1 +1 @@
include ../results/test include ../result/test

View File

@ -3,11 +3,7 @@ author = "Questionable Authors"
description = "Questionable tests for pkg/stew" description = "Questionable tests for pkg/stew"
license = "MIT" license = "MIT"
when (NimMajor, NimMinor) >= (1, 6): requires "stew"
requires "stew"
task test, "Runs the test suite": task test, "Runs the test suite":
exec "nim c -f -r --skipParentCfg test.nim" exec "nim c -f -r test.nim"
else:
task test, "Runs the test suite":
echo "Warning: Skipping test with stew on Nim < 1.6"