Fix use of =? in generic code

This commit is contained in:
Mark Spanbroek 2021-05-06 17:12:52 +02:00
parent 7303be50da
commit 2700038316
6 changed files with 64 additions and 55 deletions

View File

@ -75,17 +75,6 @@ proc someProc(option: ?int) =
# use value # use value
``` ```
When using `=?` in generic code you may face errors about undeclared
identifiers. This is a limitation of Nim and can be worked around with a `mixin`
statement:
```nim
proc genericProc[T](option: ?T) =
if value =? option:
mixin value
# use value
```
### Option chaining ### Option chaining
To safely access fields and call procs, you can use the `.?` operator: To safely access fields and call procs, you can use the `.?` operator:

25
questionable/binding.nim Normal file
View File

@ -0,0 +1,25 @@
import std/options
import std/macros
proc option[T](option: Option[T]): Option[T] =
option
template bindLet(name, expression): bool =
let option = expression.option
template name: auto {.used.} = option.unsafeGet()
option.isSome
template bindVar(name, expression): bool =
let option = expression.option
var name : typeof(option.unsafeGet())
if option.isSome:
name = option.unsafeGet()
option.isSome
macro `=?`*(name, expression): bool =
name.expectKind({nnkIdent, nnkVarTy})
if name.kind == nnkIdent:
quote do: bindLet(`name`, `expression`)
else:
let name = name[0]
quote do: bindVar(`name`, `expression`)

View File

@ -1,5 +1,6 @@
import std/options import std/options
import std/macros import std/macros
import ./binding
import ./chaining import ./chaining
import ./indexing import ./indexing
import ./operators import ./operators
@ -8,6 +9,7 @@ import ./without
include ./errorban include ./errorban
export options except get export options except get
export binding
export chaining export chaining
export indexing export indexing
export without export without
@ -42,20 +44,6 @@ template `->?`*[T,U,V](options: (?T, ?U), expression: ?V): ?V =
else: else:
V.none V.none
template `=?`*[T](name: untyped{nkIdent}, expression: ?T): bool =
let option = expression
template name: T {.used.} = option.unsafeGet()
option.isSome
macro `=?`*[T](variable: untyped{nkVarTy}, expression: ?T): bool =
let name = variable[0]
quote do:
let option = `expression`
var `name` : typeof(option.unsafeGet())
if option.isSome:
`name` = option.unsafeGet()
option.isSome
template `|?`*[T](option: ?T, fallback: T): T = template `|?`*[T](option: ?T, fallback: T): T =
if option.isSome: if option.isSome:
option.unsafeGet() option.unsafeGet()

View File

@ -1,6 +1,7 @@
import std/macros import std/macros
import ./resultsbase import ./resultsbase
import ./options import ./options
import ./binding
import ./chaining import ./chaining
import ./indexing import ./indexing
import ./operators import ./operators
@ -9,6 +10,7 @@ import ./without
include ./errorban include ./errorban
export resultsbase except ok, err, isOk, isErr, get export resultsbase except ok, err, isOk, isErr, get
export binding
export chaining export chaining
export indexing export indexing
export without export without
@ -76,25 +78,6 @@ template `->?`*[T,U,V](values: (?!T, ?!U), expression: ?!V): ?!V =
template `|?`*[T,E](value: Result[T,E], fallback: T): T = template `|?`*[T,E](value: Result[T,E], fallback: T): T =
value.valueOr(fallback) value.valueOr(fallback)
macro `=?`*[T,E](name: untyped{nkIdent}, expression: Result[T,E]): bool =
let unsafeGet = bindSym"unsafeGet"
let isOk = bindSym"isOk"
quote do:
let value = `expression`
template `name`: T {.used.} = value.`unsafeGet`()
`isOk`(value)
macro `=?`*[T,E](variable: untyped{nkVarTy}, expression: Result[T,E]): bool =
let name = variable[0]
let unsafeGet = bindSym"unsafeGet"
let isOk = bindSym"isOk"
quote do:
let value = `expression`
var `name` : typeof(value.`unsafeGet`())
if `isOk`(value):
`name` = value.`unsafeGet`()
`isOk`(value)
proc option*[T,E](value: Result[T,E]): ?T = proc option*[T,E](value: Result[T,E]): ?T =
if value.isOk: if value.isOk:
value.unsafeGet.some value.unsafeGet.some

View File

@ -126,10 +126,21 @@ suite "optionals":
let b {.used.} = a let b {.used.} = a
check count == 1 check count == 1
test "=? works in generic code with mixin statement": test "=? works in generic code":
proc toString[T](option: ?T): string =
if value =? option:
$value
else:
"none"
check 42.some.toString == "42"
check int.none.toString == "none"
test "=? works in generic code with variable hiding":
let value {.used.} = "ignored"
proc toString[T](option: ?T): string = proc toString[T](option: ?T): string =
if value =? option: if value =? option:
mixin value
$value $value
else: else:
"none" "none"
@ -270,15 +281,6 @@ suite "optionals":
someProc(int.none) someProc(int.none)
someProc(42.some) someProc(42.some)
# generics
proc genericProc[T](option: ?T) =
if value =? option:
mixin value
check value == 42
genericProc(42.some)
# Option chaining # Option chaining
var numbers: ?seq[int] var numbers: ?seq[int]

View File

@ -129,6 +129,28 @@ suite "result":
let b {.used.} = a let b {.used.} = a
check count == 1 check count == 1
test "=? works in generic code":
proc toString[T](res: ?!T): string =
if value =? res:
$value
else:
"error"
check 42.success.toString == "42"
check int.failure(error).toString == "error"
test "=? works in generic code with variable hiding":
let value {.used.} = "ignored"
proc toString[T](res: ?!T): string =
if value =? res:
$value
else:
"error"
check 42.success.toString == "42"
check int.failure(error).toString == "error"
test "without statement works for results": test "without statement works for results":
proc test1 = proc test1 =
without a =? 42.success: without a =? 42.success: