Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a Lent[T] shim for the lent type and use it in the results module #88

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 29 additions & 19 deletions stew/results.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import
viewtypes

type
ResultError*[E] = object of ValueError
## Error raised when using `tryGet` value of result when error is set
Expand Down Expand Up @@ -458,21 +461,21 @@ func `==`*[E0, E1](lhs: Result[void, E0], rhs: Result[void, E1]): bool {.inline.
else:
lhs.e == rhs.e

func get*[T: not void, E](self: Result[T, E]): T {.inline.} =
func get*[T: not void, E](self: Result[T, E]): Lent[T] {.inline.} =
## Fetch value of result if set, or raise Defect
## Exception bridge mode: raise given Exception instead
## See also: Option.get
assertOk(self)
self.v
return self.v
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like something to fix in the compiler rather than here, or we're in for a massive refactoring


func tryGet*[T: not void, E](self: Result[T, E]): T {.inline.} =
func tryGet*[T: not void, E](self: Result[T, E]): Lent[T] {.inline.} =
## Fetch value of result if set, or raise
## When E is an Exception, raise that exception - otherwise, raise a ResultError[E]
mixin raiseResultError
if not self.o: self.raiseResultError()
self.v
return self.v

func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} =
func get*[T, E](self: Result[T, E], otherwise: T): Lent[T] {.inline.} =
## Fetch value of result if set, or return the value `otherwise`
## See `valueOr` for a template version that avoids evaluating `otherwise`
## unless necessary
Expand All @@ -486,7 +489,7 @@ func get*[T, E](self: var Result[T, E]): var T {.inline.} =
assertOk(self)
self.v

template `[]`*[T: not void, E](self: Result[T, E]): T =
template `[]`*[T: not void, E](self: Result[T, E]): Lent[T] =
## Fetch value of result if set, or raise Defect
## Exception bridge mode: raise given Exception instead
mixin get
Expand All @@ -498,14 +501,14 @@ template `[]`*[T, E](self: var Result[T, E]): var T =
mixin get
self.get()

template unsafeGet*[T, E](self: Result[T, E]): T =
template unsafeGet*[T, E](self: Result[T, E]): Lent[T] =
## Fetch value of result if set, undefined behavior if unset
## See also: Option.unsafeGet
assert self.o

self.v

func expect*[T: not void, E](self: Result[T, E], m: string): T =
func expect*[T: not void, E](self: Result[T, E], m: string): Lent[T] =
## Return value of Result, or raise a `Defect` with the given message - use
## this helper to extract the value when an error is not expected, for example
## because the program logic dictates that the operation should never fail
Expand All @@ -520,7 +523,7 @@ func expect*[T: not void, E](self: Result[T, E], m: string): T =
raiseResultDefect(m, self.e)
else:
raiseResultDefect(m)
self.v
return self.v

func expect*[T: not void, E](self: var Result[T, E], m: string): var T =
if not self.o:
Expand All @@ -535,24 +538,24 @@ func `$`*(self: Result): string =
if self.o: "Ok(" & $self.v & ")"
else: "Err(" & $self.e & ")"

func error*[T, E](self: Result[T, E]): E =
func error*[T, E](self: Result[T, E]): Lent[E] =
## Fetch error of result if set, or raise Defect
if self.o:
when T isnot void:
raiseResultDefect("Trying to access error when value is set", self.v)
else:
raiseResultDefect("Trying to access error when value is set")
self.e
return self.e

template value*[T, E](self: Result[T, E]): T =
template value*[T, E](self: Result[T, E]): Lent[T] =
mixin get
self.get()

template value*[T, E](self: var Result[T, E]): T =
template value*[T, E](self: var Result[T, E]): Lent[T] =
mixin get
self.get()

template valueOr*[T, E](self: Result[T, E], def: T): T =
template valueOr*[T, E](self: Result[T, E], def: T): Lent[T] =
## Fetch value of result if set, or supplied default
## default will not be evaluated iff value is set
if self.o: self.v
Expand Down Expand Up @@ -648,6 +651,11 @@ template value*[E](self: var Result[void, E]) =
mixin get
self.get()

template isBorrowable*(x: typed): bool =
type T = typeof(x)
compiles:
let v: Lent[T] = x

template `?`*[T, E](self: Result[T, E]): auto =
## Early return - if self is an error, we will return from the current
## function, else we'll move on..
Expand All @@ -656,12 +664,14 @@ template `?`*[T, E](self: Result[T, E]): auto =
## let v = ? funcWithResult()
## echo v # prints value, not Result!
## ```
## Experimental
# TODO the v copy is here to prevent multiple evaluations of self - could
# probably avoid it with some fancy macro magic..
let v = (self)
type S = typeof(self)
when isBorrowable(self):
let v: Lent[S] = self
else:
let v = self

if not v.o:
when typeof(result) is typeof(v):
when typeof(result) is typeof(self):
return v
else:
return err(typeof(result), v.e)
Expand Down
4 changes: 4 additions & 0 deletions stew/viewtypes.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
when (NimMajor, NimMinor) < (1, 5):
type Lent*[T] = T
else:
type Lent*[T] = lent T
4 changes: 4 additions & 0 deletions tests/test_results.nim
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,12 @@ doAssert testErr().error == "323"

doAssert testOk().expect("testOk never fails") == 42

static: doAssert isBorrowable(works()) == false
static: doAssert isBorrowable(counter2) == true

func testQn(): Result[int, string] =
let x = ?works() - ?works()
static: doAssert isBorrowable(x) == true
result.ok(x)

func testQn2(): Result[int, string] =
Expand Down