tuple binding and unpacking support (#29)

* tuple binding and unpacking support

* support tuple binding in nim < 1.6

* Use ? instead of Option[] in tests

* Test binding of optional tuple, not just tuple

* Add tests for tuple unpacking of Result

* Remove unused import

* Rearrange tuple binding code

Introduce separate proc for creation of unpacking
statement.

Use `quote do` to make code as similar as possible
to the `bindLet` and `bindVar` templates.

* Add tests for failed tuple bindings

---------

Co-authored-by: Mark Spanbroek <mark@spanbroek.net>
This commit is contained in:
Eric Mastro 2023-02-14 09:56:32 +11:00 committed by GitHub
parent 1dcef4b302
commit 096ca864b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 189 additions and 1 deletions

View File

@ -30,14 +30,50 @@ template bindVar(name, expression): bool =
placeholder(T)
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 = ident("option")
let evaluated = ident("evaluated")
let T = ident("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`.option
type `T` = typeof(`opt`.unsafeGet())
`letsection`
`opt`.isSome
macro `=?`*(name, expression): bool =
## 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
## instance in an `if` statement.
name.expectKind({nnkIdent, nnkVarTy})
when (NimMajor, NimMinor) < (1, 6):
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr, nnkPar})
else:
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr})
if name.kind == nnkIdent:
quote do: bindLet(`name`, `expression`)
elif name.kind == nnkTupleConstr or name.kind == nnkPar:
quote do: bindTuple(`name`, `expression`)
else:
let name = name[0]
quote do: bindVar(`name`, `expression`)

View File

@ -165,6 +165,82 @@ suite "optionals":
else:
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 "without statement can be used for early returns":
proc test1 =
without a =? 42.some:

View File

@ -165,6 +165,82 @@ suite "result":
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":
proc test1 =
without a =? 42.success: