diff --git a/Readme.md b/Readme.md index acf2388..599cd9a 100644 --- a/Readme.md +++ b/Readme.md @@ -75,17 +75,6 @@ proc someProc(option: ?int) = # 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 To safely access fields and call procs, you can use the `.?` operator: diff --git a/questionable/binding.nim b/questionable/binding.nim new file mode 100644 index 0000000..357e683 --- /dev/null +++ b/questionable/binding.nim @@ -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`) diff --git a/questionable/options.nim b/questionable/options.nim index 748dd84..89b4709 100644 --- a/questionable/options.nim +++ b/questionable/options.nim @@ -1,5 +1,6 @@ import std/options import std/macros +import ./binding import ./chaining import ./indexing import ./operators @@ -8,6 +9,7 @@ import ./without include ./errorban export options except get +export binding export chaining export indexing export without @@ -42,20 +44,6 @@ template `->?`*[T,U,V](options: (?T, ?U), expression: ?V): ?V = else: 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 = if option.isSome: option.unsafeGet() diff --git a/questionable/results.nim b/questionable/results.nim index 47ad46f..4dc0de5 100644 --- a/questionable/results.nim +++ b/questionable/results.nim @@ -1,6 +1,7 @@ import std/macros import ./resultsbase import ./options +import ./binding import ./chaining import ./indexing import ./operators @@ -9,6 +10,7 @@ import ./without include ./errorban export resultsbase except ok, err, isOk, isErr, get +export binding export chaining export indexing 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 = 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 = if value.isOk: value.unsafeGet.some diff --git a/testmodules/options/test.nim b/testmodules/options/test.nim index c069e3d..6f7683f 100644 --- a/testmodules/options/test.nim +++ b/testmodules/options/test.nim @@ -126,10 +126,21 @@ suite "optionals": let b {.used.} = a 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 = if value =? option: - mixin value $value else: "none" @@ -270,15 +281,6 @@ suite "optionals": someProc(int.none) someProc(42.some) - # generics - - proc genericProc[T](option: ?T) = - if value =? option: - mixin value - check value == 42 - - genericProc(42.some) - # Option chaining var numbers: ?seq[int] diff --git a/testmodules/result/test.nim b/testmodules/result/test.nim index 0708ca5..07b1f97 100644 --- a/testmodules/result/test.nim +++ b/testmodules/result/test.nim @@ -129,6 +129,28 @@ suite "result": let b {.used.} = a 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": proc test1 = without a =? 42.success: