Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/review-stew-result' into bridge-…
Browse files Browse the repository at this point in the history
…mode
  • Loading branch information
arnetheduck committed Mar 30, 2020
2 parents 0680222 + 06f9064 commit 9f45d6a
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ respective folders
- `bitops2` - an updated version of `bitops.nim`, filling in gaps in original code
- `byteutils` - utilities that make working with the Nim `byte` type convenient
- `endians2` - utilities for converting to and from little / big endian integers
- `objects` - get an object's base type at runtime, as a string
- `ptrops` - pointer arithmetic utilities
- `ranges` - utility functions for working with parts and blobs of memory
- `shims` - backports of nim `devel` code to the stable version that Status is using
Expand Down
2 changes: 1 addition & 1 deletion stew.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ requires "nim >= 0.19.0"

task test, "Run all tests":
exec "nim c -r --threads:off tests/all_tests"
exec "nim c -r --threads:on tests/all_tests"
exec "nim c -r --threads:on -d:nimTypeNames tests/all_tests"
12 changes: 12 additions & 0 deletions stew/objects.nim
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ when not compiles(len((1, 2))):
func len*(x: tuple): int =
arity(type(x))

# Get an object's base type, as a cstring. Ref objects will have an ":ObjectType"
# suffix.
# From: https://gist.github.com/stefantalpalaru/82dc71bb547d6f9178b916e3ed5b527d
proc baseType*(obj: RootObj): cstring =
when not defined(nimTypeNames):
raiseAssert("you need to compile this with '-d:nimTypeNames'")
else:
{.emit: "result = `obj`->m_type->name;".}

proc baseType*(obj: ref RootObj): cstring =
obj[].baseType

30 changes: 18 additions & 12 deletions stew/result.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type
ResultError*[E] = object of ValueError
## Error raised when using `tryGet` value of result when error is set
## Note: If error is of exception type, it will be raised instead!
error: E
error*: E

Result*[T, E] = object
## Result type that can hold either a value or an error, but not both
Expand Down Expand Up @@ -157,6 +157,9 @@ type
## conversions would be nice here
## * Pattern matching in rust allows convenient extraction of value or error
## in one go.
##
## Relevant nim bugs:
## https://github.com/nim-lang/Nim/issues/13799

case o: bool
of false:
Expand Down Expand Up @@ -193,23 +196,23 @@ template ok*(R: type Result, x: auto): auto =
## Example: `Result[int, string].ok(42)`
R(o: true, v: x)

template ok*(self: var Result, x: auto) =
template ok*[T, E](self: var Result[T, E], x: auto) =
## Set the result to success and update value
## Example: `result.ok(42)`
self = ok(type self, x)

template err*(R: type Result, x: auto): auto =
template err*[T, E](R: type Result[T, E], x: auto): R =
## Initialize the result to an error
## Example: `Result[int, string].err("uh-oh")`
R(o: false, e: x)

template err*(self: var Result, x: auto) =
template err*[T, E](self: var Result[T, E], x: auto) =
## Set the result as an error
## Example: `result.err("uh-oh")`
self = err(type self, x)

template ok*(v: auto): auto = typeof(result).ok(v)
template err*(v: auto): auto = typeof(result).err(v)
template ok*(v: auto): auto = ok(typeof(result), v)
template err*(v: auto): auto = err(typeof(result), v)

template isOk*(self: Result): bool = self.o
template isErr*(self: Result): bool = not self.o
Expand Down Expand Up @@ -245,7 +248,7 @@ func mapCast*[T0, E0](
if self.isOk: result.ok(cast[T1](self.v))
else: result.err(self.e)

template `and`*(self, other: Result): Result =
template `and`*[T, E](self, other: Result[T, E]): Result[T, E] =
## Evaluate `other` iff self.isOk, else return error
## fail-fast - will not evaluate other if a is an error
##
Expand All @@ -257,7 +260,7 @@ template `and`*(self, other: Result): Result =
type R = type(other)
R.err(self.e)

template `or`*(self, other: Result): Result =
template `or`*[T, E](self, other: Result[T, E]): Result[T, E] =
## Evaluate `other` iff not self.isOk, else return self
## fail-fast - will not evaluate other if a is a value
##
Expand All @@ -279,7 +282,7 @@ template catch*(body: typed): Result[type(body), ref CatchableError] =
except CatchableError as e:
R.err(e)

template capture*[E: Exception](T: type, someExceptionExpr: ref E): auto =
template capture*[E: Exception](T: type, someExceptionExpr: ref E): Result[T, ref E] =
## Evaluate someExceptionExpr and put the exception into a result, making sure
## to capture a call stack at the capture site:
##
Expand All @@ -300,7 +303,7 @@ template capture*[E: Exception](T: type, someExceptionExpr: ref E): auto =
ret = R.err(caught)
ret

func `==`*(lhs, rhs: Result): bool {.inline.} =
func `==`*[T0, E0, T1, E1](lhs: Result[T0, E0], rhs: Result[T1, E1]): bool {.inline.} =
if lhs.isOk != rhs.isOk:
false
elif lhs.isOk:
Expand Down Expand Up @@ -356,6 +359,7 @@ func `$`*(self: Result): string =

func error*[T, E](self: Result[T, E]): E =
doAssert self.isErr, "Result does not contain an error"

self.e

template value*[T, E](self: Result[T, E]): T = self.get()
Expand All @@ -378,6 +382,9 @@ template ok*[E](self: var Result[void, E]) =
## Example: `result.ok(42)`
self = (type self).ok()

template ok*(): auto = ok(typeof(result))
template err*(): auto = err(typeof(result))

# TODO:
# Supporting `map` and `get` operations on a `void` result is quite
# an unusual API. We should provide some motivating examples.
Expand Down Expand Up @@ -443,7 +450,6 @@ template `?`*[T, E](self: Result[T, E]): T =
# TODO the v copy is here to prevent multiple evaluations of self - could
# probably avoid it with some fancy macro magic..
let v = (self)
if v.isErr: return v
if v.isErr: return err(typeof(result), v.error)

v.value

9 changes: 6 additions & 3 deletions stew/shims/macros.nim
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,16 @@ template readPragma*(field: FieldDescription, pragmaName: static string): NimNod
if p != nil and p.len == 2: p[1] else: p

proc recordFields*(typeImpl: NimNode): seq[FieldDescription] =
# TODO: This doesn't support inheritance yet
if typeImpl.isTuple:
for i in 1 ..< typeImpl.len:
result.add FieldDescription(typ: typeImpl[i], name: ident("Field" & $(i - 1)))
return

# TODO: This doesn't support inheritance yet
let objectType = typeImpl[2]
var objectType = typeImpl[2]
if objectType.kind == nnkRefTy:
objectType = objectType[0]

let recList = objectType[2]

type
Expand Down Expand Up @@ -128,7 +131,7 @@ proc recordFields*(typeImpl: NimNode): seq[FieldDescription] =
discard

else:
doAssert false
doAssert false, "Unexpected nodes in recordFields:\n" & n.treeRepr

if traversalStack.len == 0: break

Expand Down
1 change: 1 addition & 0 deletions tests/all_tests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import
test_bitseqs,
test_byteutils,
test_endians2,
test_objects,
test_ptrops,
test_result,
test_varints
36 changes: 36 additions & 0 deletions tests/test_objects.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import unittest,
../stew/objects

when defined(nimHasUsed):
{.used.}

suite "Objects":
test "baseType":
type
Foo = ref object of RootObj
Bar = ref object of Foo
Baz = object of RootObj
Bob = object of Baz
Bill = ref object of Bob

var
foo = Foo()
bar = Bar()
baz = Baz()
bob = Bob()
bill = Bill()

when defined(nimTypeNames):
check:
foo.baseType == "Foo:ObjectType"
bar.baseType == "Bar:ObjectType"
baz.baseType == "Baz"
bob.baseType == "Bob"
bill.baseType == "Bill:ObjectType"

proc f(o: Foo) =
check $o.type == "Foo"
check o.baseType == "Bar:ObjectType"

f(bar)

0 comments on commit 9f45d6a

Please sign in to comment.