Add support for Result types; Implement Try and either
This commit is contained in:
parent
8fd43269dc
commit
8e1755c4e1
|
@ -1,9 +1,9 @@
|
|||
import
|
||||
typetraits, strutils,
|
||||
typetraits, tables, strutils, options,
|
||||
shims/macros, results
|
||||
|
||||
export
|
||||
results
|
||||
results, options
|
||||
|
||||
const
|
||||
enforce_error_handling {.strdefine.}: string = "yes"
|
||||
|
@ -158,55 +158,29 @@ template raising*[E, R](x: Raising[E, R]): R =
|
|||
template raising*[R, E](x: Result[R, E]): R =
|
||||
tryGet(x)
|
||||
|
||||
macro chk*(x: Result, handlers): untyped =
|
||||
discard
|
||||
const
|
||||
incorrectChkSyntaxMsg =
|
||||
"The `check` handlers block should consist of `ExceptionType: Value/Block` pairs"
|
||||
|
||||
macro chk*(x: Raising, handlers: untyped): untyped =
|
||||
## The `chk` macro can be used in 2 different ways
|
||||
##
|
||||
## 1) Try to get the result of an expression. In case of any
|
||||
## errors, substitute the result with a default value:
|
||||
##
|
||||
## ```
|
||||
## let x = chk(foo(), defaultValue)
|
||||
## ```
|
||||
##
|
||||
## We'll handle this case with a simple rewrite to
|
||||
##
|
||||
## ```
|
||||
## let x = try: distinctBase(foo())
|
||||
## except CatchableError: defaultValue
|
||||
## ```
|
||||
##
|
||||
## 2) Try to get the result of an expression while providing exception
|
||||
## handlers that must cover all possible recoverable errors.
|
||||
##
|
||||
## ```
|
||||
## let x = chk foo():
|
||||
## KeyError as err: defaultValue
|
||||
## ValueError: return
|
||||
## _: raise
|
||||
## ```
|
||||
##
|
||||
## The above example will be rewritten to:
|
||||
##
|
||||
## ```
|
||||
## let x = try:
|
||||
## foo()
|
||||
## except KeyError as err:
|
||||
## defaultValue
|
||||
## except ValueError:
|
||||
## return
|
||||
## except CatchableError:
|
||||
## raise
|
||||
## ```
|
||||
##
|
||||
## Please note that the special case `_` is considered equivalent to
|
||||
## `CatchableError`.
|
||||
##
|
||||
## If the `chk` block lacks a default handler and there are unlisted
|
||||
## recoverable errors, the compiler will fail to compile the code with
|
||||
## a message indicating the missing ones.
|
||||
template either*[E, R](x: Raising[E, R], otherwise: R): R =
|
||||
try: distinctBase(x)
|
||||
except CatchableError: otherwise
|
||||
|
||||
template either*[R, E](x: Result[R, E], otherwise: R): R =
|
||||
let r = x
|
||||
if isOk(r):
|
||||
value(r)
|
||||
else:
|
||||
otherwise
|
||||
|
||||
template either*[T](x: Option[T], otherwise: T): T =
|
||||
let o = x
|
||||
if isSome(o):
|
||||
get(o)
|
||||
else:
|
||||
otherwise
|
||||
|
||||
macro check*(x: Raising, handlers: untyped): untyped =
|
||||
let
|
||||
RaisingType = getTypeInst(x)
|
||||
ErrorsSetTuple = RaisingType[1]
|
||||
|
@ -230,13 +204,13 @@ macro chk*(x: Raising, handlers: untyped): untyped =
|
|||
|
||||
# Check how the API was used:
|
||||
if handlers.kind != nnkStmtList:
|
||||
# This is usage type 1: chk(foo(), defaultValue)
|
||||
# This is usage type 1: check(foo(), defaultValue)
|
||||
result.add newTree(nnkExceptBranch,
|
||||
bindSym("CatchableError"),
|
||||
newStmtList(handlers))
|
||||
else:
|
||||
var
|
||||
# This will be a tuple of all the errors handled by the `chk` block.
|
||||
# This will be a tuple of all the errors handled by the `check` block.
|
||||
# In the end, we'll compare it to the Raising list.
|
||||
HandledErrorsTuple = newNimNode(nnkPar, x)
|
||||
# Has the user provided a default `_: value` handler?
|
||||
|
@ -244,8 +218,7 @@ macro chk*(x: Raising, handlers: untyped): untyped =
|
|||
|
||||
for handler in handlers:
|
||||
template err(msg: string) = error msg, handler
|
||||
template unexpectedSyntax = err(
|
||||
"The `chk` handlers block should consist of `ExceptionType: Value/Block` pairs")
|
||||
template unexpectedSyntax = err incorrectChkSyntaxMsg
|
||||
|
||||
case handler.kind
|
||||
of nnkCommentStmt:
|
||||
|
@ -263,7 +236,7 @@ macro chk*(x: Raising, handlers: untyped): untyped =
|
|||
result.add newTree(nnkExceptBranch, infix(ExceptionType, "as", exceptionVar),
|
||||
valueBlock)
|
||||
else:
|
||||
err "The '$1' operator is not expected in a `chk` block" % [$handler[0]]
|
||||
err "The '$1' operator is not expected in a `check` block" % [$handler[0]]
|
||||
of nnkCall:
|
||||
if handler.len != 2:
|
||||
unexpectedSyntax
|
||||
|
@ -284,3 +257,133 @@ macro chk*(x: Raising, handlers: untyped): untyped =
|
|||
result)
|
||||
|
||||
storeMacroResult result
|
||||
|
||||
macro check*[R, E](x: Result[R, E], handlers: untyped): untyped =
|
||||
if handlers.kind != nnkStmtList:
|
||||
return newCall(bindSym"get", x, handlers)
|
||||
|
||||
let
|
||||
R = getTypeInst(x)
|
||||
SuccessResultType = R[1]
|
||||
ErrorResultType = R[2]
|
||||
|
||||
let enumImpl = getImpl(ErrorResultType)[2]
|
||||
if enumImpl.kind != nnkEnumTy:
|
||||
error "`check` handler blocks can be used only with Results based on enums", x
|
||||
|
||||
let tempVar = genSym(nskLet, "res")
|
||||
|
||||
var errorsSwitch = newTree(nnkCaseStmt)
|
||||
var defaultHandler: NimNode
|
||||
errorsSwitch.add newCall(bindSym"error", tempVar)
|
||||
|
||||
for handler in handlers:
|
||||
template err(msg: string) = error msg, handler
|
||||
template unexpectedSyntax = err incorrectChkSyntaxMsg
|
||||
|
||||
case handler.kind
|
||||
of nnkCommentStmt:
|
||||
continue
|
||||
of nnkInfix:
|
||||
if eqIdent(handler[0], "as"):
|
||||
if handler.len != 4:
|
||||
err "The expected syntax is `ExceptionType as exceptionVar: Value/Block`"
|
||||
let
|
||||
ErrorType = handler[1]
|
||||
valueBlock = handler[3]
|
||||
errorsSwitch.add newTree(nnkOfBranch, ErrorType, valueBlock)
|
||||
else:
|
||||
err "The '$1' operator is not expected in a `check` block" % [$handler[0]]
|
||||
of nnkCall:
|
||||
if handler.len != 2:
|
||||
unexpectedSyntax
|
||||
let
|
||||
ErrorType = handler[0]
|
||||
valueBlock = handler[1]
|
||||
if eqIdent(ErrorType, "_"):
|
||||
if defaultHandler != nil:
|
||||
err "Only a single default handler is expected"
|
||||
defaultHandler = valueBlock
|
||||
else:
|
||||
errorsSwitch.add newTree(nnkOfBranch, ErrorType, valueBlock)
|
||||
else:
|
||||
unexpectedSyntax
|
||||
|
||||
if defaultHandler != nil:
|
||||
errorsSwitch.add newTree(nnkElse, defaultHandler)
|
||||
|
||||
result = quote do:
|
||||
let `tempVar` = `x`
|
||||
if isOk `tempVar`:
|
||||
get `tempVar`
|
||||
else:
|
||||
`errorsSwitch`
|
||||
|
||||
storeMacroResult result
|
||||
|
||||
macro Try*(body: typed, handlers: varargs[untyped]): untyped =
|
||||
type ParamRegistryEntry = object
|
||||
origSymbol: NimNode
|
||||
newParam: NimNode
|
||||
|
||||
var params = initTable[string, ParamRegistryEntry]()
|
||||
|
||||
proc makeParam(replacedSym: NimNode,
|
||||
registryEntry: var ParamRegistryEntry): NimNode =
|
||||
if registryEntry.newParam == nil:
|
||||
registryEntry.origSymbol = replacedSym
|
||||
registryEntry.newParam = genSym(nskParam, $replacedSym)
|
||||
return registryEntry.newParam
|
||||
|
||||
proc registerParamsInBody(n: NimNode) =
|
||||
for i in 0 ..< n.len:
|
||||
let son = n[i]
|
||||
case son.kind
|
||||
of nnkIdent, nnkCharLit..nnkTripleStrLit:
|
||||
discard
|
||||
of nnkSym:
|
||||
if son.symKind in {nskLet, nskVar, nskForVar,
|
||||
nskParam, nskTemp, nskResult}:
|
||||
n[i] = makeParam(son, params.mgetOrPut(son.signatureHash,
|
||||
default(ParamRegistryEntry)))
|
||||
of nnkHiddenDeref:
|
||||
registerParamsInBody son
|
||||
n[i] = son[0]
|
||||
else:
|
||||
registerParamsInBody son
|
||||
|
||||
let procName = genSym(nskProc, "Try_payload")
|
||||
|
||||
var procBody = copy body
|
||||
registerParamsInBody procBody
|
||||
|
||||
var raisesList = newTree(nnkBracket)
|
||||
for handler in handlers:
|
||||
raisesList.add handler[0]
|
||||
|
||||
var
|
||||
procCall = newCall(procName)
|
||||
procParams = @[newEmptyNode()]
|
||||
|
||||
for hash, param in params:
|
||||
var paramType = getType(param.origSymbol)
|
||||
if param.origSymbol.symKind in {nskVar, nskResult}:
|
||||
if paramType.kind != nnkBracketExpr or not eqIdent(paramType[0], "var"):
|
||||
paramType = newTree(nnkVarTy, paramType)
|
||||
|
||||
procParams.add newIdentDefs(param.newParam, paramType)
|
||||
procCall.add param.origSymbol
|
||||
|
||||
let procPragma = newTree(nnkPragma,
|
||||
newColonExpr(ident "raises", raisesList))
|
||||
|
||||
let generatedProc = newProc(name = procName,
|
||||
params = procParams,
|
||||
pragmas = procPragma,
|
||||
body = procBody)
|
||||
|
||||
result = newTree(nnkTryStmt, newStmtList(generatedProc, procCall))
|
||||
for handler in handlers: result.add handler
|
||||
|
||||
storeMacroResult result
|
||||
|
||||
|
|
|
@ -257,7 +257,7 @@ type
|
|||
|
||||
Opt*[T] = Result[T, void]
|
||||
|
||||
func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} =
|
||||
func raiseResultError*[T, E](self: Result[T, E]) {.noreturn, noinline.} =
|
||||
# noinline because raising should take as little space as possible at call
|
||||
# site
|
||||
mixin toException
|
||||
|
|
|
@ -7,7 +7,7 @@ proc bar(x: int): int {.noerrors.} =
|
|||
proc toString(x: int): string {.errors: (ValueError, KeyError, OSError).} =
|
||||
$x
|
||||
|
||||
enum
|
||||
type
|
||||
ReadStatus = enum
|
||||
FileNotFound
|
||||
AccessDenied
|
||||
|
@ -19,28 +19,54 @@ proc readFromDevice(path: string): Result[seq[byte], ReadStatus] =
|
|||
proc getGpsCoordinates(): Result[(float, float), cstring] =
|
||||
ok (1.2, 3.4)
|
||||
|
||||
proc takeString(x: string) =
|
||||
echo x
|
||||
|
||||
proc main =
|
||||
let
|
||||
a = bar(10)
|
||||
b = raising toString(20)
|
||||
c = chk toString(30):
|
||||
c = check toString(30):
|
||||
ValueError: "got ValueError"
|
||||
KeyError as err: err.msg
|
||||
OSError: raise
|
||||
|
||||
d = chk(readFromDevice("test"), @[1.byte, 2, 3])
|
||||
d = either(readFromDevice("test"), @[1.byte, 2, 3])
|
||||
|
||||
e = chk readFromDevice("test"):
|
||||
FileNotFound: raise newException()
|
||||
e = check readFromDevice("test"):
|
||||
FileNotFound: raise newException(ValueError, "x")
|
||||
HardwareError: quit 1
|
||||
else: @[]
|
||||
|
||||
_: @[]
|
||||
echo a
|
||||
echo b
|
||||
echo c
|
||||
|
||||
main()
|
||||
|
||||
let n = 10
|
||||
var m = 23
|
||||
|
||||
proc main2(a: int, b: var int) =
|
||||
var x = 10
|
||||
var y = 20
|
||||
var z = 23
|
||||
|
||||
Try:
|
||||
echo a, b, x, y, z, n, m
|
||||
if a > 20:
|
||||
# raise newException(OSError, "Test")
|
||||
discard
|
||||
except ValueError:
|
||||
discard
|
||||
except IOError:
|
||||
discard
|
||||
|
||||
var
|
||||
p = 1
|
||||
k = 2
|
||||
|
||||
main2 p, k
|
||||
|
||||
dumpMacroResults()
|
||||
|
||||
when false:
|
||||
|
@ -51,6 +77,6 @@ when false:
|
|||
proc map[A, E, R](a: A, f: proc (a: A): Raising[E, R])): string {.
|
||||
errors: E|ValueError|ExtraErrors
|
||||
.} =
|
||||
$chk(f(a))
|
||||
$check(f(a))
|
||||
]#
|
||||
|
||||
|
|
|
@ -1,17 +1,44 @@
|
|||
|
||||
## /home/zahary/nimbus/vendor/nim-stew/tests/test_errorhandling.nim(7, 32)
|
||||
proc toString_15835040(x: int): string {.raises: [Defect, KeyError, OSError].} =
|
||||
proc toString_16040040(x: int): string {.raises: [Defect, KeyError, OSError].} =
|
||||
result = $x
|
||||
|
||||
template toString(x: int): untyped =
|
||||
Raising[(ValueError, KeyError, OSError), string](toString_15835040(x))
|
||||
Raising[(ValueError, KeyError, OSError), string](toString_16040040(x))
|
||||
|
||||
## /home/zahary/nimbus/vendor/nim-stew/tests/test_errorhandling.nim(14, 12)
|
||||
## /home/zahary/nimbus/vendor/nim-stew/tests/test_errorhandling.nim(29, 14)
|
||||
try:
|
||||
[type node](Raising[(ValueError, KeyError, OSError), string](toString_15835040(30)))
|
||||
[type node](Raising[(ValueError, KeyError, OSError), string](toString_16040040(30)))
|
||||
except ValueError:
|
||||
"got ValueError"
|
||||
except KeyError as err:
|
||||
err.msg
|
||||
except OSError:
|
||||
raise
|
||||
## /home/zahary/nimbus/vendor/nim-stew/tests/test_errorhandling.nim(36, 14)
|
||||
let res_16125314 = readFromDevice("test")
|
||||
if res_16125314.o: get res_16125314
|
||||
else:
|
||||
case error(res_16125314)
|
||||
of FileNotFound:
|
||||
raise
|
||||
(ref ValueError)(msg: "x", parent: nil)
|
||||
of HardwareError:
|
||||
quit 1
|
||||
else:
|
||||
@[]
|
||||
## /home/zahary/nimbus/vendor/nim-stew/tests/test_errorhandling.nim(54, 2)
|
||||
try:
|
||||
proc Try_payload_16170094(z_16170100: var int; y_16170099: var int; m_16170102: var int;
|
||||
a_16170096: int; x_16170098: var int; b_16170097: var[int];
|
||||
n_16170101: int) {.raises: [ValueError, IOError].} =
|
||||
echo [a_16170096, b_16170097, x_16170098, y_16170099, z_16170100, n_16170101, m_16170102]
|
||||
if (
|
||||
20 < a_16170096):
|
||||
discard
|
||||
|
||||
Try_payload_16170094(z, y, m, a, x, b, n)
|
||||
except ValueError:
|
||||
discard
|
||||
except IOError:
|
||||
discard
|
Loading…
Reference in New Issue