nim-chronos/chronos/internal/raisesfutures.nim

137 lines
4.4 KiB
Nim

import
std/macros,
../futures
type
InternalRaisesFuture*[T, E] = ref object of Future[T]
## Future with a tuple of possible exception types
## eg InternalRaisesFuture[void, (ValueError, OSError)]
##
## This type gets injected by `async: (raises: ...)` and similar utilities
## and should not be used manually as the internal exception representation
## is subject to change in future chronos versions.
proc makeNoRaises*(): NimNode {.compileTime.} =
# An empty tuple would have been easier but...
# https://github.com/nim-lang/Nim/issues/22863
# https://github.com/nim-lang/Nim/issues/22865
ident"void"
proc isNoRaises*(n: NimNode): bool {.compileTime.} =
n.eqIdent("void")
iterator members(tup: NimNode): NimNode =
# Given a typedesc[tuple] = (A, B, C), yields the tuple members (A, B C)
if not isNoRaises(tup):
for n in getType(getTypeInst(tup)[1])[1..^1]:
yield n
proc members(tup: NimNode): seq[NimNode] {.compileTime.} =
for t in tup.members():
result.add(t)
proc containsSignature(members: openArray[NimNode], typ: NimNode): bool {.compileTime.} =
let typHash = signatureHash(typ)
for err in members:
if signatureHash(err) == typHash:
return true
false
# Utilities for working with the E part of InternalRaisesFuture - unstable
macro prepend*(tup: typedesc[tuple], typs: varargs[typed]): typedesc =
result = nnkTupleConstr.newTree()
for err in typs:
if not tup.members().containsSignature(err):
result.add err
for err in tup.members():
result.add err
if result.len == 0:
result = makeNoRaises()
macro remove*(tup: typedesc[tuple], typs: varargs[typed]): typedesc =
result = nnkTupleConstr.newTree()
for err in tup.members():
if not typs[0..^1].containsSignature(err):
result.add err
if result.len == 0:
result = makeNoRaises()
macro union*(tup0: typedesc[tuple], tup1: typedesc[tuple]): typedesc =
## Join the types of the two tuples deduplicating the entries
result = nnkTupleConstr.newTree()
for err in tup0.members():
var found = false
for err2 in tup1.members():
if signatureHash(err) == signatureHash(err2):
found = true
if not found:
result.add err
for err2 in getType(getTypeInst(tup1)[1])[1..^1]:
result.add err2
if result.len == 0:
result = makeNoRaises()
proc getRaises*(future: NimNode): NimNode {.compileTime.} =
# Given InternalRaisesFuture[T, (A, B, C)], returns (A, B, C)
let types = getType(getTypeInst(future)[2])
if isNoRaises(types):
nnkBracketExpr.newTree(newEmptyNode())
else:
expectKind(types, nnkBracketExpr)
expectKind(types[0], nnkSym)
assert types[0].strVal == "tuple"
assert types.len >= 1
types
macro checkRaises*[T: CatchableError](
future: InternalRaisesFuture, error: ref T, warn: static bool = true): untyped =
## Generate code that checks that the given error is compatible with the
## raises restrictions of `future`.
##
## This check is done either at compile time or runtime depending on the
## information available at compile time - in particular, if the raises
## inherit from `error`, we end up with the equivalent of a downcast which
## raises a Defect if it fails.
let raises = getRaises(future)
expectKind(getTypeInst(error), nnkRefTy)
let toMatch = getTypeInst(error)[0]
var
typeChecker = ident"false"
maybeChecker = ident"false"
runtimeChecker = ident"false"
for errorType in raises[1..^1]:
typeChecker = infix(typeChecker, "or", infix(toMatch, "is", errorType))
maybeChecker = infix(maybeChecker, "or", infix(errorType, "is", toMatch))
runtimeChecker = infix(
runtimeChecker, "or",
infix(error, "of", nnkBracketExpr.newTree(ident"typedesc", errorType)))
let
errorMsg = "`fail`: `" & repr(toMatch) & "` incompatible with `raises: " & repr(raises[1..^1]) & "`"
warningMsg = "Can't verify `fail` exception type at compile time - expected one of " & repr(raises[1..^1]) & ", got `" & repr(toMatch) & "`"
# A warning from this line means exception type will be verified at runtime
warning = if warn:
quote do: {.warning: `warningMsg`.}
else: newEmptyNode()
# Cannot check inhertance in macro so we let `static` do the heavy lifting
quote do:
when not(`typeChecker`):
when not(`maybeChecker`):
static:
{.error: `errorMsg`.}
else:
`warning`
assert(`runtimeChecker`, `errorMsg`)