Skip to content

Commit

Permalink
improve semantics to distinguish valid from invalid values
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheecour committed Jan 6, 2020
1 parent 5eb8a3b commit 889a770
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 13 deletions.
31 changes: 20 additions & 11 deletions lib/std/wrapnils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,40 @@ runnableExamples:
assert Foo(x1: "a").wrapnil.x1[] == "a"

type Wrapnil*[T] = object
valueImpl*: T
valueImpl: T
validImpl: bool

proc wrapnil*[T](a: T): Wrapnil[T] =
Wrapnil[T](valueImpl: a)
Wrapnil[T](valueImpl: a, validImpl: true)

{.push experimental: "dotOperators".}

template `.`*(a: Wrapnil, b): untyped =
let a2 = a.valueImpl # to avoid double evaluations
when type(a2) is ref|ptr:
if a2 == nil:
wrapnil(default(type(a2.b)))
let a1 = a # to avoid double evaluations
let a2 = a1.valueImpl
if a1.validImpl:
when type(a2) is ref|ptr:
if a2 == nil:
default(Wrapnil[type(a2.b)])
else:
wrapnil(a2.b)
else:
wrapnil(a2.b)
else:
wrapnil(a2.b)
# nil is "sticky"; this is needed, see tests
default(Wrapnil[type(a2.b)])

{.pop.}

template `[]`*(a: Wrapnil): untyped =
a.valueImpl

proc isNotNil*(a: Wrapnil): bool = a.validImpl

template `[]`*[I](a: Wrapnil, i: I): untyped =
let a2 = a.valueImpl # to avoid double evaluations
if len(a2) == 0:
wrapnil(default(type(a2[i])))
let a1 = a # to avoid double evaluations
if a1.validImpl:
# correctly will raise IndexError if a is valid but wraps an empty container
wrapnil(a1.valueImpl[i])
else:
wrapnil(a2[i])
default(Wrapnil[type(a1.valueImpl[i])])
28 changes: 26 additions & 2 deletions tests/stdlib/twrapnils.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import std/wrapnils


proc checkNotZero(x: float): float =
doAssert x != 0
x

var witness = 0

proc main() =
type Bar = object
b1: int
Expand All @@ -9,13 +16,17 @@ proc main() =
x3: string
x4: Bar
x5: seq[int]
x6: ptr Bar
x7: array[2, string]
x8: seq[int]

var a: Foo
var a2 = Foo(x1: 1.0, x5: @[10, 11])
var x6 = create(Bar)
x6.b1 = 42
var a2 = Foo(x1: 1.0, x5: @[10, 11], x6: x6)
var a3 = Foo(x1: 1.2, x3: "abc")
a3.x2 = a3

var witness = 0
proc initFoo(x1: float): auto =
witness.inc
result = Foo(x1: x1)
Expand All @@ -28,6 +39,19 @@ proc main() =
doAssert a3.wrapnil.x2.x2.x3[1][] == 'b'
doAssert a.wrapnil.x2.x2.x3[1][] == default(char)

doAssert a2.wrapnil.x6[][] == Bar(b1: 42) # 2nd deref for ptr Bar

doAssert a2.wrapnil.x1.checkNotZero[] == 1.0
doAssert a == nil
# shows that checkNotZero won't be called if a nil is found earlier in chain
doAssert a.wrapnil.x1.checkNotZero[] == 0.0

doAssert a2.wrapnil.x4.isNotNil
doAssert not a.wrapnil.x4.isNotNil

# checks that a chain without nil but with an empty seq still throws IndexError
doAssertRaises(IndexError): discard a2.wrapnil.x8[3]

# make sure no double evaluation bug
doAssert witness == 0
doAssert initFoo(1.3).wrapnil.x1[] == 1.3
Expand Down

0 comments on commit 889a770

Please sign in to comment.