results: work around field access bugs (#167)

* results: work around field access bugs

See:

* https://github.com/nim-lang/Nim/issues/3770
* https://github.com/nim-lang/Nim/issues/20900

* comment fixes

* add test

* document test better
This commit is contained in:
Jacek Sieka 2023-01-20 15:32:51 +01:00 committed by GitHub
parent 19a6aea53b
commit 447b23d3bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 142 additions and 122 deletions

View File

@ -305,34 +305,40 @@ type
## https://github.com/nim-lang/Nim/issues/14318 - generic error raises pragma ## https://github.com/nim-lang/Nim/issues/14318 - generic error raises pragma
# TODO https://github.com/nim-lang/Nim/issues/20699 # TODO https://github.com/nim-lang/Nim/issues/20699
# case o: bool # case oResultPrivate: bool
# of false: # of false:
# e: E # eResultPrivate: E
# of true: # of true:
# v: T # vResultPrivate: T
# TODO ResultPrivate works around
# * https://github.com/nim-lang/Nim/issues/3770
# * https://github.com/nim-lang/Nim/issues/20900
#
# Do not use these fields directly in your code, they're not meant to be
# public!
when T is void: when T is void:
when E is void: when E is void:
o: bool oResultPrivate*: bool
else: else:
case o: bool case oResultPrivate*: bool
of false: of false:
e: E eResultPrivate*: E
of true: of true:
discard discard
else: else:
when E is void: when E is void:
case o: bool case oResultPrivate*: bool
of false: of false:
discard discard
of true: of true:
v: T vResultPrivate*: T
else: else:
case o: bool case oResultPrivate*: bool
of false: of false:
e: E eResultPrivate*: E
of true: of true:
v: T vResultPrivate*: T
Opt*[T] = Result[T, void] Opt*[T] = Result[T, void]
@ -342,7 +348,7 @@ func raiseResultOk[T, E](self: Result[T, E]) {.noreturn, noinline.} =
when T is void: when T is void:
raise (ref ResultError[void])(msg: "Trying to access error with value") raise (ref ResultError[void])(msg: "Trying to access error with value")
else: else:
raise (ref ResultError[T])(msg: "Trying to access error with value", error: self.v) raise (ref ResultError[T])(msg: "Trying to access error with value", error: self.vResultPrivate)
func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} = func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} =
# noinline because raising should take as little space as possible at call # noinline because raising should take as little space as possible at call
@ -350,18 +356,18 @@ func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} =
mixin toException mixin toException
when E is ref Exception: when E is ref Exception:
if self.e.isNil: # for example Result.default()! if self.eResultPrivate.isNil: # for example Result.default()!
raise (ref ResultError[void])(msg: "Trying to access value with err (nil)") raise (ref ResultError[void])(msg: "Trying to access value with err (nil)")
raise self.e raise self.eResultPrivate
elif E is void: elif E is void:
raise (ref ResultError[void])(msg: "Trying to access value with err") raise (ref ResultError[void])(msg: "Trying to access value with err")
elif compiles(toException(self.e)): elif compiles(toException(self.eResultPrivate)):
raise toException(self.e) raise toException(self.eResultPrivate)
elif compiles($self.e): elif compiles($self.eResultPrivate):
raise (ref ResultError[E])( raise (ref ResultError[E])(
error: self.e, msg: $self.e) error: self.eResultPrivate, msg: $self.eResultPrivate)
else: else:
raise (ref ResultError[E])(msg: "Trying to access value with err", error: self.e) raise (ref ResultError[E])(msg: "Trying to access value with err", error: self.eResultPrivate)
func raiseResultDefect(m: string, v: auto) {.noreturn, noinline.} = func raiseResultDefect(m: string, v: auto) {.noreturn, noinline.} =
mixin `$` mixin `$`
@ -373,21 +379,21 @@ func raiseResultDefect(m: string) {.noreturn, noinline.} =
template assertOk(self: Result) = template assertOk(self: Result) =
# Careful - `self` evaluated multiple times, which is fine in all current uses # Careful - `self` evaluated multiple times, which is fine in all current uses
if not self.o: if not self.oResultPrivate:
when self.E isnot void: when self.E isnot void:
raiseResultDefect("Trying to access value with err Result", self.e) raiseResultDefect("Trying to access value with err Result", self.eResultPrivate)
else: else:
raiseResultDefect("Trying to access value with err Result") raiseResultDefect("Trying to access value with err Result")
template ok*[T, E](R: type Result[T, E], x: untyped): R = template ok*[T, E](R: type Result[T, E], x: untyped): R =
## Initialize a result with a success and value ## Initialize a result with a success and value
## Example: `Result[int, string].ok(42)` ## Example: `Result[int, string].ok(42)`
R(o: true, v: x) R(oResultPrivate: true, vResultPrivate: x)
template ok*[E](R: type Result[void, E]): R = template ok*[E](R: type Result[void, E]): R =
## Initialize a result with a success and value ## Initialize a result with a success and value
## Example: `Result[void, string].ok()` ## Example: `Result[void, string].ok()`
R(o: true) R(oResultPrivate: true)
template ok*[T: not void, E](self: var Result[T, E], x: untyped) = template ok*[T: not void, E](self: var Result[T, E], x: untyped) =
## Set the result to success and update value ## Set the result to success and update value
@ -402,18 +408,18 @@ template ok*[E](self: var Result[void, E]) =
template err*[T, E](R: type Result[T, E], x: untyped): R = template err*[T, E](R: type Result[T, E], x: untyped): R =
## Initialize the result to an error ## Initialize the result to an error
## Example: `Result[int, string].err("uh-oh")` ## Example: `Result[int, string].err("uh-oh")`
R(o: false, e: x) R(oResultPrivate: false, eResultPrivate: x)
template err*[T](R: type Result[T, cstring], x: string): R = template err*[T](R: type Result[T, cstring], x: string): R =
## Initialize the result to an error ## Initialize the result to an error
## Example: `Result[int, string].err("uh-oh")` ## Example: `Result[int, string].err("uh-oh")`
const s = x # avoid dangling cstring pointers const s = x # avoid dangling cstring pointers
R(o: false, e: cstring(s)) R(oResultPrivate: false, eResultPrivate: cstring(s))
template err*[T](R: type Result[T, void]): R = template err*[T](R: type Result[T, void]): R =
## Initialize the result to an error ## Initialize the result to an error
## Example: `Result[int, void].err()` ## Example: `Result[int, void].err()`
R(o: false) R(oResultPrivate: false)
template err*[T, E](self: var Result[T, E], x: untyped) = template err*[T, E](self: var Result[T, E], x: untyped) =
## Set the result as an error ## Set the result as an error
@ -435,8 +441,8 @@ template ok*(): auto = ok(typeof(result))
template err*(v: auto): auto = err(typeof(result), v) template err*(v: auto): auto = err(typeof(result), v)
template err*(): auto = err(typeof(result)) template err*(): auto = err(typeof(result))
template isOk*(self: Result): bool = self.o template isOk*(self: Result): bool = self.oResultPrivate
template isErr*(self: Result): bool = not self.o template isErr*(self: Result): bool = not self.oResultPrivate
when not defined(nimHasEffectsOfs): when not defined(nimHasEffectsOfs):
template effectsOf(f: untyped) {.pragma.} template effectsOf(f: untyped) {.pragma.}
@ -450,13 +456,13 @@ func map*[T0, E, T1](
## let r = Result[int, cstring).ok(42) ## let r = Result[int, cstring).ok(42)
## assert r.map(proc (v: int): int = $v).get() == "42" ## assert r.map(proc (v: int): int = $v).get() == "42"
## ``` ## ```
if self.o: if self.oResultPrivate:
result.ok(f(self.v)) result.ok(f(self.vResultPrivate))
else: else:
when E is void: when E is void:
result.err() result.err()
else: else:
result.err(self.e) result.err(self.eResultPrivate)
func map*[T, E]( func map*[T, E](
self: Result[T, E], f: proc(x: T)): self: Result[T, E], f: proc(x: T)):
@ -467,81 +473,81 @@ func map*[T, E](
## let r = Result[int, cstring).ok(42) ## let r = Result[int, cstring).ok(42)
## assert r.map(proc (v: int): int = $v).get() == "42" ## assert r.map(proc (v: int): int = $v).get() == "42"
## ``` ## ```
if self.o: if self.oResultPrivate:
f(self.v) f(self.vResultPrivate)
result.ok() result.ok()
else: else:
when E is void: when E is void:
result.err() result.err()
else: else:
result.err(self.e) result.err(self.eResultPrivate)
func map*[E, T1]( func map*[E, T1](
self: Result[void, E], f: proc(): T1): self: Result[void, E], f: proc(): T1):
Result[T1, E] {.inline, effectsOf: f.} = Result[T1, E] {.inline, effectsOf: f.} =
## Transform value using f, or return error ## Transform value using f, or return error
if self.o: if self.oResultPrivate:
result.ok(f()) result.ok(f())
else: else:
when E is void: when E is void:
result.err() result.err()
else: else:
result.err(self.e) result.err(self.eResultPrivate)
func map*[E]( func map*[E](
self: Result[void, E], f: proc()): self: Result[void, E], f: proc()):
Result[void, E] {.inline, effectsOf: f.} = Result[void, E] {.inline, effectsOf: f.} =
## Call f if value is ## Call f if value is
if self.o: if self.oResultPrivate:
f() f()
result.ok() result.ok()
else: else:
when E is void: when E is void:
result.err() result.err()
else: else:
result.err(self.e) result.err(self.eResultPrivate)
func flatMap*[T0, E, T1]( func flatMap*[T0, E, T1](
self: Result[T0, E], f: proc(x: T0): Result[T1, E]): self: Result[T0, E], f: proc(x: T0): Result[T1, E]):
Result[T1, E] {.inline, effectsOf: f.} = Result[T1, E] {.inline, effectsOf: f.} =
if self.o: f(self.v) if self.oResultPrivate: f(self.vResultPrivate)
else: else:
when E is void: when E is void:
Result[T1, void].err() Result[T1, void].err()
else: else:
Result[T1, E].err(self.e) Result[T1, E].err(self.eResultPrivate)
func flatMap*[E, T1]( func flatMap*[E, T1](
self: Result[void, E], f: proc(): Result[T1, E]): self: Result[void, E], f: proc(): Result[T1, E]):
Result[T1, E] {.inline, effectsOf: f.} = Result[T1, E] {.inline, effectsOf: f.} =
if self.o: f() if self.oResultPrivate: f()
else: else:
when E is void: when E is void:
Result[T1, void].err() Result[T1, void].err()
else: else:
Result[T1, E].err(self.e) Result[T1, E].err(self.eResultPrivate)
func mapErr*[T, E0, E1]( func mapErr*[T, E0, E1](
self: Result[T, E0], f: proc(x: E0): E1): self: Result[T, E0], f: proc(x: E0): E1):
Result[T, E1] {.inline, effectsOf: f.} = Result[T, E1] {.inline, effectsOf: f.} =
## Transform error using f, or leave untouched ## Transform error using f, or leave untouched
if self.o: if self.oResultPrivate:
when T is void: when T is void:
result.ok() result.ok()
else: else:
result.ok(self.v) result.ok(self.vResultPrivate)
else: else:
result.err(f(self.e)) result.err(f(self.eResultPrivate))
func mapErr*[T, E1]( func mapErr*[T, E1](
self: Result[T, void], f: proc(): E1): self: Result[T, void], f: proc(): E1):
Result[T, E1] {.inline, effectsOf: f.} = Result[T, E1] {.inline, effectsOf: f.} =
## Transform error using f, or return value ## Transform error using f, or return value
if self.o: if self.oResultPrivate:
when T is void: when T is void:
result.ok() result.ok()
else: else:
result.ok(self.v) result.ok(self.vResultPrivate)
else: else:
result.err(f()) result.err(f())
@ -549,24 +555,24 @@ func mapErr*[T, E0](
self: Result[T, E0], f: proc(x: E0)): self: Result[T, E0], f: proc(x: E0)):
Result[T, void] {.inline, effectsOf: f.} = Result[T, void] {.inline, effectsOf: f.} =
## Transform error using f, or return value ## Transform error using f, or return value
if self.o: if self.oResultPrivate:
when T is void: when T is void:
result.ok() result.ok()
else: else:
result.ok(self.v) result.ok(self.vResultPrivate)
else: else:
f(self.e) f(self.eResultPrivate)
result.err() result.err()
func mapErr*[T]( func mapErr*[T](
self: Result[T, void], f: proc()): self: Result[T, void], f: proc()):
Result[T, void] {.inline, effectsOf: f.} = Result[T, void] {.inline, effectsOf: f.} =
## Transform error using f, or return value ## Transform error using f, or return value
if self.o: if self.oResultPrivate:
when T is void: when T is void:
result.ok() result.ok()
else: else:
result.ok(self.v) result.ok(self.vResultPrivate)
else: else:
f() f()
result.err() result.err()
@ -575,33 +581,33 @@ func mapConvert*[T0, E](
self: Result[T0, E], T1: type): Result[T1, E] {.inline.} = self: Result[T0, E], T1: type): Result[T1, E] {.inline.} =
## Convert result value to A using an conversion ## Convert result value to A using an conversion
# Would be nice if it was automatic... # Would be nice if it was automatic...
if self.o: if self.oResultPrivate:
when T1 is void: when T1 is void:
result.ok() result.ok()
else: else:
result.ok(T1(self.v)) result.ok(T1(self.vResultPrivate))
else: else:
when E is void: when E is void:
result.err() result.err()
else: else:
result.err(self.e) result.err(self.eResultPrivate)
func mapCast*[T0, E]( func mapCast*[T0, E](
self: Result[T0, E], T1: type): Result[T1, E] {.inline.} = self: Result[T0, E], T1: type): Result[T1, E] {.inline.} =
## Convert result value to A using a cast ## Convert result value to A using a cast
## Would be nice with nicer syntax... ## Would be nice with nicer syntax...
if self.o: result.ok(cast[T1](self.v)) if self.oResultPrivate: result.ok(cast[T1](self.vResultPrivate))
else: else:
when E is void: when E is void:
result.err() result.err()
else: else:
result.err(self.e) result.err(self.eResultPrivate)
template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1, E] = template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1, E] =
## Evaluate `other` iff self.isOk, else return error ## Evaluate `other` iff self.isOk, else return error
## fail-fast - will not evaluate other if a is an error ## fail-fast - will not evaluate other if a is an error
let s = (self) # TODO avoid copy let s = (self) # TODO avoid copy
if s.o: if s.oResultPrivate:
other other
else: else:
when type(self) is type(other): when type(self) is type(other):
@ -611,7 +617,7 @@ template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1
when E is void: when E is void:
err(R) err(R)
else: else:
err(R, s.e) err(R, s.eResultPrivate)
template `or`*[T, E0, E1](self: Result[T, E0], other: Result[T, E1]): Result[T, E1] = template `or`*[T, E0, E1](self: Result[T, E0], other: Result[T, E1]): Result[T, E1] =
## Evaluate `other` iff `not self.isOk`, else return `self` ## Evaluate `other` iff `not self.isOk`, else return `self`
@ -622,7 +628,7 @@ template `or`*[T, E0, E1](self: Result[T, E0], other: Result[T, E1]): Result[T,
## f2() or err(SomeEnum.V) # Collapse errors from other module / function ## f2() or err(SomeEnum.V) # Collapse errors from other module / function
## ``` ## ```
let s = (self) # TODO avoid copy let s = (self) # TODO avoid copy
if s.o: if s.oResultPrivate:
when type(self) is type(other): when type(self) is type(other):
s s
else: else:
@ -630,7 +636,7 @@ template `or`*[T, E0, E1](self: Result[T, E0], other: Result[T, E1]): Result[T,
when T is void: when T is void:
ok(R) ok(R)
else: else:
ok(R, s.v) ok(R, s.vResultPrivate)
else: else:
other other
@ -646,14 +652,14 @@ template orErr*[T, E0, E1](self: Result[T, E0], error: E1): Result[T, E1] =
## ** Experimental, may be removed ** ## ** Experimental, may be removed **
let s = (self) # TODO avoid copy let s = (self) # TODO avoid copy
type R = Result[T, E1] type R = Result[T, E1]
if s.o: if s.oResultPrivate:
when type(self) is R: when type(self) is R:
s s
else: else:
when T is void: when T is void:
ok(R) ok(R)
else: else:
ok(R, s.v) ok(R, s.vResultPrivate)
else: else:
err(R, error) err(R, error)
@ -668,16 +674,16 @@ template catch*(body: typed): Result[type(body), ref CatchableError] =
try: try:
R.ok(body) R.ok(body)
except CatchableError as e: except CatchableError as eResultPrivate:
R.err(e) R.err(eResultPrivate)
template capture*[E: Exception](T: type, someExceptionExpr: ref E): Result[T, ref E] = template capture*[E: Exception](T: type, someExceptionExpr: ref E): Result[T, ref E] =
## Evaluate someExceptionExpr and put the exception into a result, making sure ## Evaluate someExceptionExpr and put the exception into a result, making sure
## to capture a call stack at the capture site: ## to capture a call stack at the capture site:
## ##
## ``` ## ```
## let e: Result[void, ValueError] = void.capture((ref ValueError)(msg: "test")) ## let eResultPrivate: Result[void, ValueError] = void.capture((ref ValueError)(msg: "test"))
## echo e.error().getStackTrace() ## echo eResultPrivate.error().getStackTrace()
## ``` ## ```
type R = Result[T, ref E] type R = Result[T, ref E]
@ -696,28 +702,28 @@ func `==`*[
T0: not void, E0: not void, T0: not void, E0: not void,
T1: not void, E1: not void]( T1: not void, E1: not void](
lhs: Result[T0, E0], rhs: Result[T1, E1]): bool {.inline.} = lhs: Result[T0, E0], rhs: Result[T1, E1]): bool {.inline.} =
if lhs.o != rhs.o: if lhs.oResultPrivate != rhs.oResultPrivate:
false false
elif lhs.o: # and rhs.o implied elif lhs.oResultPrivate: # and rhs.oResultPrivate implied
lhs.v == rhs.v lhs.vResultPrivate == rhs.vResultPrivate
else: else:
lhs.e == rhs.e lhs.eResultPrivate == rhs.eResultPrivate
func `==`*[E0, E1]( func `==`*[E0, E1](
lhs: Result[void, E0], rhs: Result[void, E1]): bool {.inline.} = lhs: Result[void, E0], rhs: Result[void, E1]): bool {.inline.} =
if lhs.o != rhs.o: if lhs.oResultPrivate != rhs.oResultPrivate:
false false
elif lhs.o: # and rhs.o implied elif lhs.oResultPrivate: # and rhs.oResultPrivate implied
true true
else: else:
lhs.e == rhs.e lhs.eResultPrivate == rhs.eResultPrivate
func `==`*[T0, T1]( func `==`*[T0, T1](
lhs: Result[T0, void], rhs: Result[T1, void]): bool {.inline.} = lhs: Result[T0, void], rhs: Result[T1, void]): bool {.inline.} =
if lhs.o != rhs.o: if lhs.oResultPrivate != rhs.oResultPrivate:
false false
elif lhs.o: # and rhs.o implied elif lhs.oResultPrivate: # and rhs.oResultPrivate implied
lhs.v == rhs.v lhs.vResultPrivate == rhs.vResultPrivate
else: else:
true true
@ -727,21 +733,21 @@ func get*[T, E](self: Result[T, E]): T {.inline.} =
## See also: Option.get ## See also: Option.get
assertOk(self) assertOk(self)
when T isnot void: when T isnot void:
self.v self.vResultPrivate
func tryGet*[T, E](self: Result[T, E]): T {.inline.} = func tryGet*[T, E](self: Result[T, E]): T {.inline.} =
## Fetch value of result if set, or raise ## Fetch value of result if set, or raise
## When E is an Exception, raise that exception - otherwise, raise a ResultError[E] ## When E is an Exception, raise that exception - otherwise, raise a ResultError[E]
mixin raiseResultError mixin raiseResultError
if not self.o: self.raiseResultError() if not self.oResultPrivate: self.raiseResultError()
when T isnot void: when T isnot void:
self.v self.vResultPrivate
func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} = func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} =
## Fetch value of result if set, or return the value `otherwise` ## Fetch value of result if set, or return the value `otherwise`
## See `valueOr` for a template version that avoids evaluating `otherwise` ## See `valueOr` for a template version that avoids evaluating `otherwise`
## unless necessary ## unless necessary
if self.o: self.v if self.oResultPrivate: self.vResultPrivate
else: otherwise else: otherwise
func get*[T: not void, E](self: var Result[T, E]): var T {.inline.} = func get*[T: not void, E](self: var Result[T, E]): var T {.inline.} =
@ -749,7 +755,7 @@ func get*[T: not void, E](self: var Result[T, E]): var T {.inline.} =
## Exception bridge mode: raise given Exception instead ## Exception bridge mode: raise given Exception instead
## See also: Option.get ## See also: Option.get
assertOk(self) assertOk(self)
self.v self.vResultPrivate
template `[]`*[T: not void, E](self: Result[T, E]): T = template `[]`*[T: not void, E](self: Result[T, E]): T =
## Fetch value of result if set, or raise Defect ## Fetch value of result if set, or raise Defect
@ -769,12 +775,12 @@ template `[]`*[T: not void, E](self: var Result[T, E]): var T =
template unsafeGet*[T: not void, E](self: Result[T, E]): T = template unsafeGet*[T: not void, E](self: Result[T, E]): T =
## Fetch value of result if set, undefined behavior if unset ## Fetch value of result if set, undefined behavior if unset
## See also: `unsafeError` ## See also: `unsafeError`
self.v self.vResultPrivate
template unsafeGet*[E](self: Result[void, E]) = template unsafeGet*[E](self: Result[void, E]) =
## Fetch value of result if set, undefined behavior if unset ## Fetch value of result if set, undefined behavior if unset
## See also: `unsafeError` ## See also: `unsafeError`
assert self.o assert self.oResultPrivate
func expect*[T, E](self: Result[T, E], m: string): T = func expect*[T, E](self: Result[T, E], m: string): T =
## Return value of Result, or raise a `Defect` with the given message - use ## Return value of Result, or raise a `Defect` with the given message - use
@ -786,58 +792,58 @@ func expect*[T, E](self: Result[T, E], m: string): T =
## # Put here a helpful comment why you think this won't fail ## # Put here a helpful comment why you think this won't fail
## echo r.expect("r was just set to ok(42)") ## echo r.expect("r was just set to ok(42)")
## ``` ## ```
if not self.o: if not self.oResultPrivate:
when E isnot void: when E isnot void:
raiseResultDefect(m, self.e) raiseResultDefect(m, self.eResultPrivate)
else: else:
raiseResultDefect(m) raiseResultDefect(m)
when T isnot void: when T isnot void:
self.v self.vResultPrivate
func expect*[T: not void, E](self: var Result[T, E], m: string): var T = func expect*[T: not void, E](self: var Result[T, E], m: string): var T =
if not self.o: if not self.oResultPrivate:
when E isnot void: when E isnot void:
raiseResultDefect(m, self.e) raiseResultDefect(m, self.eResultPrivate)
else: else:
raiseResultDefect(m) raiseResultDefect(m)
self.v self.vResultPrivate
func `$`*[T, E](self: Result[T, E]): string = func `$`*[T, E](self: Result[T, E]): string =
## Returns string representation of `self` ## Returns string representation of `self`
if self.o: if self.oResultPrivate:
when T is void: "ok()" when T is void: "ok()"
else: "ok(" & $self.v & ")" else: "ok(" & $self.vResultPrivate & ")"
else: else:
when E is void: "none()" when E is void: "none()"
else: "err(" & $self.e & ")" else: "err(" & $self.eResultPrivate & ")"
func error*[T, E](self: Result[T, E]): E = func error*[T, E](self: Result[T, E]): E =
## Fetch error of result if set, or raise Defect ## Fetch error of result if set, or raise Defect
if self.o: if self.oResultPrivate:
when T isnot void: when T isnot void:
raiseResultDefect("Trying to access error when value is set", self.v) raiseResultDefect("Trying to access error when value is set", self.vResultPrivate)
else: else:
raiseResultDefect("Trying to access error when value is set") raiseResultDefect("Trying to access error when value is set")
when E isnot void: when E isnot void:
self.e self.eResultPrivate
func tryError*[T, E](self: Result[T, E]): E {.inline.} = func tryError*[T, E](self: Result[T, E]): E {.inline.} =
## Fetch error of result if set, or raise ## Fetch error of result if set, or raise
## Raises a ResultError[T] ## Raises a ResultError[T]
mixin raiseResultOk mixin raiseResultOk
if self.o: self.raiseResultOk() if self.oResultPrivate: self.raiseResultOk()
when E isnot void: when E isnot void:
self.e self.eResultPrivate
template unsafeError*[T, E: not void](self: Result[T, E]): E = template unsafeError*[T, E: not void](self: Result[T, E]): E =
## Fetch value of result if set, undefined behavior if unset ## Fetch value of result if set, undefined behavior if unset
## See also: `unsafeGet` ## See also: `unsafeGet`
self.e self.eResultPrivate
template unsafeError*[T](self: Result[T, void]) = template unsafeError*[T](self: Result[T, void]) =
## Fetch value of result if set, undefined behavior if unset ## Fetch value of result if set, undefined behavior if unset
## See also: `unsafeGet` ## See also: `unsafeGet`
assert not self.o # Emulate field access defect in debug builds assert not self.oResultPrivate # Emulate field access defect in debug builds
# Alternative spellings for get # Alternative spellings for get
template value*[T, E](self: Result[T, E]): T = self.get() template value*[T, E](self: Result[T, E]): T = self.get()
@ -867,10 +873,10 @@ template valueOr*[T: not void, E](self: Result[T, E], def: untyped): T =
## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386
## ##
let s = (self) # TODO avoid copy let s = (self) # TODO avoid copy
if s.o: s.v if s.oResultPrivate: s.vResultPrivate
else: else:
when E isnot void: when E isnot void:
template error: E {.used, inject.} = s.e template error: E {.used, inject.} = s.eResultPrivate
def def
template errorOr*[T, E: not void](self: Result[T, E], def: untyped): E = template errorOr*[T, E: not void](self: Result[T, E], def: untyped): E =
@ -878,16 +884,16 @@ template errorOr*[T, E: not void](self: Result[T, E], def: untyped): E =
## `def` is evaluated lazily, and must be an expression of `T` or exit ## `def` is evaluated lazily, and must be an expression of `T` or exit
## the scope (for example using `return` / `raise`) ## the scope (for example using `return` / `raise`)
let s = (self) # TODO avoid copy let s = (self) # TODO avoid copy
if not s.o: s.e if not s.oResultPrivate: s.eResultPrivate
else: else:
when T isnot void: when T isnot void:
template value: T {.used, inject.} = s.v template value: T {.used, inject.} = s.vResultPrivate
def def
func flatten*[T, E](self: Result[Result[T, E], E]): Result[T, E] = func flatten*[T, E](self: Result[Result[T, E], E]): Result[T, E] =
## Remove one level of nesting ## Remove one level of nesting
if self.o: if self.oResultPrivate:
self.v self.vResultPrivate
else: else:
when E is void: when E is void:
err(Result[T, E]) err(Result[T, E])
@ -901,8 +907,8 @@ func filter*[T, E](
## Apply `callback` to the `self`, iff `self` is not an error. If `callback` ## Apply `callback` to the `self`, iff `self` is not an error. If `callback`
## returns an error, return that error, else return `self` ## returns an error, return that error, else return `self`
if self.o: if self.oResultPrivate:
callback(self.v) and self callback(self.vResultPrivate) and self
else: else:
self self
@ -913,7 +919,7 @@ func filter*[E](
## Apply `callback` to the `self`, iff `self` is not an error. If `callback` ## Apply `callback` to the `self`, iff `self` is not an error. If `callback`
## returns an error, return that error, else return `self` ## returns an error, return that error, else return `self`
if self.o: if self.oResultPrivate:
callback() and self callback() and self
else: else:
self self
@ -925,8 +931,8 @@ func filter*[T](
## Apply `callback` to the `self`, iff `self` is not an error. If `callback` ## Apply `callback` to the `self`, iff `self` is not an error. If `callback`
## returns an error, return that error, else return `self` ## returns an error, return that error, else return `self`
if self.o: if self.oResultPrivate:
if callback(self.v): if callback(self.vResultPrivate):
self self
else: else:
Result[T, void].err() Result[T, void].err()
@ -939,8 +945,8 @@ template some*[T](O: type Opt, v: T): Opt[T] =
## Create an `Opt` set to a value ## Create an `Opt` set to a value
## ##
## ``` ## ```
## let o = Opt.some(42) ## let oResultPrivate = Opt.some(42)
## assert o.isSome and o.get() == 42 ## assert oResultPrivate.isSome and oResultPrivate.get() == 42
## ``` ## ```
Opt[T].ok(v) Opt[T].ok(v)
@ -948,18 +954,18 @@ template none*(O: type Opt, T: type): Opt[T] =
## Create an `Opt` set to none ## Create an `Opt` set to none
## ##
## ``` ## ```
## let o = Opt.none(int) ## let oResultPrivate = Opt.none(int)
## assert o.isNone ## assert oResultPrivate.isNone
## ``` ## ```
Opt[T].err() Opt[T].err()
template isSome*(o: Opt): bool = template isSome*(oResultPrivate: Opt): bool =
## Alias for `isOk` ## Alias for `isOk`
isOk o isOk oResultPrivate
template isNone*(o: Opt): bool = template isNone*(oResultPrivate: Opt): bool =
## Alias of `isErr` ## Alias of `isErr`
isErr o isErr oResultPrivate
# Syntactic convenience # Syntactic convenience
@ -975,14 +981,14 @@ template `?`*[T, E](self: Result[T, E]): auto =
# TODO the v copy is here to prevent multiple evaluations of self - could # TODO the v copy is here to prevent multiple evaluations of self - could
# probably avoid it with some fancy macro magic.. # probably avoid it with some fancy macro magic..
let v = (self) let v = (self)
if not v.o: if not v.oResultPrivate:
when typeof(result) is typeof(v): when typeof(result) is typeof(v):
return v return v
else: else:
when E is void: when E is void:
return err(typeof(result)) return err(typeof(result))
else: else:
return err(typeof(result), v.e) return err(typeof(result), v.eResultPrivate)
when not(T is void): when not(T is void):
v.v v.vResultPrivate

View File

@ -32,5 +32,6 @@ import
test_sets, test_sets,
test_templateutils, test_templateutils,
test_results, test_results,
test_results2,
test_varints, test_varints,
test_winacl test_winacl

13
tests/test_results2.nim Normal file
View File

@ -0,0 +1,13 @@
import ../stew/results
{.used.}
# Oddly, this piece of code works when placed in `test_results.nim`
# See also https://github.com/status-im/nim-stew/pull/167
template repeater(b: Opt[int]): untyped =
# Check that Result can be used inside a template - this fails
# sometimes with field access errors as noted in above issue
b
let x = repeater(Opt.none(int))
doAssert x.isNone()