* implements https://github.com/nim-lang/RFCs/issues/258

* don't be too strict with custom pragma blocks

* cast pragmas: documentation

* added most missing inference query procs to effecttraits.nim
This commit is contained in:
Andreas Rumpf
2020-10-06 16:47:15 +02:00
committed by GitHub
parent acd71dd6bb
commit 92163fa330
16 changed files with 232 additions and 68 deletions

View File

@@ -304,10 +304,13 @@ proc mydiv(a, b): int {.raises: [].} =
- `system.deepcopy` has to be enabled explicitly for `--gc:arc` and `--gc:orc` via
`--deepcopy:on`.
- Added a `std/effecttraits` module for introspection of the inferred `.raise` effects.
- Added a `std/effecttraits` module for introspection of the inferred effects.
We hope this enables `async` macros that are precise about the possible exceptions that
can be raised.
- Added `critbits.toCritBitTree`, similar to `tables.toTable`, creates a new `CritBitTree` with given arguments.
- The pragma blocks `{.gcsafe.}: ...` and `{.noSideEffect.}: ...` can now also be
written as `{.cast(gcsafe).}: ...` and `{.cast(noSideEffect).}: ...`. This is the new
preferred way of writing these, emphasizing their unsafe nature.
## Compiler changes

View File

@@ -121,3 +121,5 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasLentIterators")
defineSymbol("nimHasDeclaredMagic")
defineSymbol("nimHasStacktracesModule")
defineSymbol("nimHasEffectTraitsModule")
defineSymbol("nimHasCastPragmaBlocks")

View File

@@ -485,17 +485,24 @@ proc setOrTableConstr(p: var Parser): PNode =
eat(p, tkCurlyRi) # skip '}'
proc parseCast(p: var Parser): PNode =
#| castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')'
#| castExpr = 'cast' ('[' optInd typeDesc optPar ']' '(' optInd expr optPar ')') /
# ('(' optInd exprColonEqExpr optPar ')')
result = newNodeP(nkCast, p)
getTok(p)
eat(p, tkBracketLe)
optInd(p, result)
result.add(parseTypeDesc(p))
optPar(p)
eat(p, tkBracketRi)
eat(p, tkParLe)
optInd(p, result)
result.add(parseExpr(p))
if p.tok.tokType == tkBracketLe:
getTok(p)
optInd(p, result)
result.add(parseTypeDesc(p))
optPar(p)
eat(p, tkBracketRi)
eat(p, tkParLe)
optInd(p, result)
result.add(parseExpr(p))
else:
result.add p.emptyNode
eat(p, tkParLe)
optInd(p, result)
result.add(exprColonEqExpr(p))
optPar(p)
eat(p, tkParRi)

View File

@@ -771,6 +771,15 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
if key.kind == nkBracketExpr:
processNote(c, it)
return
elif key.kind == nkCast:
if comesFromPush:
localError(c.config, n.info, "a 'cast' pragma cannot be pushed")
elif not isStatement:
localError(c.config, n.info, "'cast' pragma only allowed in a statement context")
case whichPragma(key[1])
of wRaises, wTags: pragmaRaisesOrTags(c, key[1])
else: discard
return
elif key.kind notin nkIdentKinds:
n[i] = semCustomPragma(c, it)
return

View File

@@ -1057,9 +1057,10 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
put(g, tkSymbol, "(wrong conv)")
of nkCast:
put(g, tkCast, "cast")
put(g, tkBracketLe, "[")
gsub(g, n, 0)
put(g, tkBracketRi, "]")
if n.len > 0 and n[0].kind != nkEmpty:
put(g, tkBracketLe, "[")
gsub(g, n, 0)
put(g, tkBracketRi, "]")
put(g, tkParLe, "(")
gsub(g, n, 1)
put(g, tkParRi, ")")

View File

@@ -325,7 +325,7 @@ proc createTag(g: ModuleGraph; n: PNode): PNode =
result.typ = g.sysTypeFromName(n.info, "RootEffect")
if not n.isNil: result.info = n.info
proc addEffect(a: PEffects, e, comesFrom: PNode) =
proc addRaiseEffect(a: PEffects, e, comesFrom: PNode) =
assert e.kind != nkRaiseStmt
var aa = a.exc
for i in a.bottom..<aa.len:
@@ -345,11 +345,11 @@ proc addTag(a: PEffects, e, comesFrom: PNode) =
if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)): return
throws(a.tags, e, comesFrom)
proc mergeEffects(a: PEffects, b, comesFrom: PNode) =
proc mergeRaises(a: PEffects, b, comesFrom: PNode) =
if b.isNil:
addEffect(a, createRaise(a.graph, comesFrom), comesFrom)
addRaiseEffect(a, createRaise(a.graph, comesFrom), comesFrom)
else:
for effect in items(b): addEffect(a, effect, comesFrom)
for effect in items(b): addRaiseEffect(a, effect, comesFrom)
proc mergeTags(a: PEffects, b, comesFrom: PNode) =
if b.isNil:
@@ -489,7 +489,7 @@ proc mergeLockLevels(tracked: PEffects, n: PNode, lockLevel: TLockLevel) =
proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
let pragma = s.ast[pragmasPos]
let spec = effectSpec(pragma, wRaises)
mergeEffects(tracked, spec, n)
mergeRaises(tracked, spec, n)
let tagSpec = effectSpec(pragma, wTags)
mergeTags(tracked, tagSpec, n)
@@ -535,7 +535,7 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
of impYes: discard
proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) =
addEffect(tracked, createRaise(tracked.graph, n), nil)
addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
addTag(tracked, createTag(tracked.graph, n), nil)
let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel
else: op.lockLevel
@@ -581,7 +581,7 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner):
markSideEffect(tracked, a)
else:
mergeEffects(tracked, effectList[exceptionEffects], n)
mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n)
if notGcSafe(op):
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
@@ -780,7 +780,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
assumeTheWorst(tracked, n, op)
gcsafeAndSideeffectCheck()
else:
mergeEffects(tracked, effectList[exceptionEffects], n)
mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n)
gcsafeAndSideeffectCheck()
if a.kind != nkSym or a.sym.magic != mNBindSym:
@@ -837,6 +837,64 @@ proc trackCall(tracked: PEffects; n: PNode) =
# initVar(tracked, n[i].skipAddr, false)
else: discard
type
PragmaBlockContext = object
oldLocked: int
oldLockLevel: TLockLevel
enforcedGcSafety, enforceNoSideEffects: bool
oldExc, oldTags: int
exc, tags: PNode
proc createBlockContext(tracked: PEffects): PragmaBlockContext =
result = PragmaBlockContext(oldLocked: tracked.locked.len,
oldLockLevel: tracked.currLockLevel,
enforcedGcSafety: false, enforceNoSideEffects: false,
oldExc: tracked.exc.len, oldTags: tracked.tags.len)
proc applyBlockContext(tracked: PEffects, bc: PragmaBlockContext) =
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = true
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = false
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
setLen(tracked.locked, bc.oldLocked)
tracked.currLockLevel = bc.oldLockLevel
if bc.exc != nil:
# beware that 'raises: []' is very different from not saying
# anything about 'raises' in the 'cast' at all. Same applies for 'tags'.
setLen(tracked.exc.sons, bc.oldExc)
for e in bc.exc:
addRaiseEffect(tracked, e, e)
if bc.tags != nil:
setLen(tracked.tags.sons, bc.oldTags)
for t in bc.tags:
addTag(tracked, t, t)
proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
case whichPragma(pragma)
of wGcSafe:
bc.enforcedGcSafety = true
of wNoSideEffect:
bc.enforceNoSideEffects = true
of wTags:
let n = pragma[1]
if n.kind in {nkCurly, nkBracket}:
bc.tags = n
else:
bc.tags = newNodeI(nkArgList, pragma.info)
bc.tags.add n
of wRaises:
let n = pragma[1]
if n.kind in {nkCurly, nkBracket}:
bc.exc = n
else:
bc.exc = newNodeI(nkArgList, pragma.info)
bc.exc.add n
else:
localError(tracked.config, pragma.info,
"invalid pragma block: " & $pragma)
proc track(tracked: PEffects, n: PNode) =
case n.kind
of nkSym:
@@ -854,7 +912,7 @@ proc track(tracked: PEffects, n: PNode) =
if n[0].kind != nkEmpty:
n[0].info = n.info
#throws(tracked.exc, n[0])
addEffect(tracked, n[0], nil)
addRaiseEffect(tracked, n[0], nil)
for i in 0..<n.safeLen:
track(tracked, n[i])
createTypeBoundOps(tracked, n[0].typ, n.info)
@@ -862,7 +920,7 @@ proc track(tracked: PEffects, n: PNode) =
# A `raise` with no arguments means we're going to re-raise the exception
# being handled or, if outside of an `except` block, a `ReraiseDefect`.
# Here we add a `Exception` tag in order to cover both the cases.
addEffect(tracked, createRaise(tracked.graph, n), nil)
addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
of nkCallKinds:
trackCall(tracked, n)
of nkDotExpr:
@@ -1011,26 +1069,24 @@ proc track(tracked: PEffects, n: PNode) =
checkForSink(tracked.config, tracked.owner, n[i])
of nkPragmaBlock:
let pragmaList = n[0]
let oldLocked = tracked.locked.len
let oldLockLevel = tracked.currLockLevel
var enforcedGcSafety = false
var enforceNoSideEffects = false
var bc = createBlockContext(tracked)
for i in 0..<pragmaList.len:
let pragma = whichPragma(pragmaList[i])
if pragma == wLocks:
case pragma
of wLocks:
lockLocations(tracked, pragmaList[i])
elif pragma == wGcSafe:
enforcedGcSafety = true
elif pragma == wNoSideEffect:
enforceNoSideEffects = true
if enforcedGcSafety: tracked.inEnforcedGcSafe = true
if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
of wGcSafe:
bc.enforcedGcSafety = true
of wNoSideEffect:
bc.enforceNoSideEffects = true
of wCast:
castBlock(tracked, pragmaList[i][1], bc)
else:
discard
applyBlockContext(tracked, bc)
track(tracked, n.lastSon)
if enforcedGcSafety: tracked.inEnforcedGcSafe = false
if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
setLen(tracked.locked, oldLocked)
tracked.currLockLevel = oldLockLevel
unapplyBlockContext(tracked, bc)
of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
discard

View File

@@ -2169,7 +2169,7 @@ proc setLine(n: PNode, info: TLineInfo) =
proc semPragmaBlock(c: PContext, n: PNode): PNode =
checkSonsLen(n, 2, c.config)
let pragmaList = n[0]
pragma(c, nil, pragmaList, exprPragmas)
pragma(c, nil, pragmaList, exprPragmas, isStatement = true)
n[1] = semExpr(c, n[1])
result = n
result.typ = n[1].typ

View File

@@ -1009,6 +1009,7 @@ proc transform(c: PTransf, n: PNode): PNode =
# Constants can be inlined here, but only if they cannot result in a cast
# in the back-end (e.g. var p: pointer = someProc)
let exprIsPointerCast = n.kind in {nkCast, nkConv, nkHiddenStdConv} and
n.typ != nil and
n.typ.kind == tyPointer
if not exprIsPointerCast:
var cnst = getConstExpr(c.module, result, c.graph)

View File

@@ -130,7 +130,19 @@ proc isRange*(n: PNode): bool {.inline.} =
proc whichPragma*(n: PNode): TSpecialWord =
let key = if n.kind in nkPragmaCallKinds and n.len > 0: n[0] else: n
if key.kind == nkIdent: result = whichKeyword(key.ident)
case key.kind
of nkIdent: result = whichKeyword(key.ident)
of nkSym: result = whichKeyword(key.sym.name)
of nkCast: result = wCast
of nkClosedSymChoice, nkOpenSymChoice:
result = whichPragma(key[0])
else: result = wInvalid
proc isNoSideEffectPragma*(n: PNode): bool =
var k = whichPragma(n)
if k == wCast:
k = whichPragma(n[1])
result = k == wNoSideEffect
proc findPragma*(n: PNode, which: TSpecialWord): PNode =
if n.kind == nkPragma:

View File

@@ -29,8 +29,7 @@
## for a high-level description of how borrow checking works.
import ast, types, lineinfos, options, msgs, renderer, typeallowed
from trees import getMagic, whichPragma, stupidStmtListExpr
from wordrecg import wNoSideEffect
from trees import getMagic, isNoSideEffectPragma, stupidStmtListExpr
from isolation_check import canAlias
type
@@ -713,7 +712,7 @@ proc traverse(c: var Partitions; n: PNode) =
let pragmaList = n[0]
var enforceNoSideEffects = 0
for i in 0..<pragmaList.len:
if whichPragma(pragmaList[i]) == wNoSideEffect:
if isNoSideEffectPragma(pragmaList[i]):
enforceNoSideEffects = 1
break

View File

@@ -262,11 +262,25 @@ proc registerAdditionalOps*(c: PCtx) =
registerCallback c, "stdlib.times.getTime", proc (a: VmArgs) {.nimcall.} =
setResult(a, times.getTime().toLit)
registerCallback c, "stdlib.effecttraits.getRaisesListImpl", proc (a: VmArgs) =
proc getEffectList(c: PCtx; a: VmArgs; effectIndex: int) =
let fn = getNode(a, 0)
if fn.typ != nil and fn.typ.n != nil and fn.typ.n[0].len >= effectListLen and
fn.typ.n[0][exceptionEffects] != nil:
fn.typ.n[0][effectIndex] != nil:
var list = newNodeI(nkBracket, fn.info)
for e in fn.typ.n[0][exceptionEffects]:
for e in fn.typ.n[0][effectIndex]:
list.add opMapTypeInstToAst(c.cache, e.typ.skipTypes({tyRef}), e.info)
setResult(a, list)
registerCallback c, "stdlib.effecttraits.getRaisesListImpl", proc (a: VmArgs) =
getEffectList(c, a, exceptionEffects)
registerCallback c, "stdlib.effecttraits.getTagsListImpl", proc (a: VmArgs) =
getEffectList(c, a, tagEffects)
registerCallback c, "stdlib.effecttraits.isGcSafeImpl", proc (a: VmArgs) =
let fn = getNode(a, 0)
setResult(a, fn.typ != nil and tfGcSafe in fn.typ.flags)
registerCallback c, "stdlib.effecttraits.hasNoSideEffectsImpl", proc (a: VmArgs) =
let fn = getNode(a, 0)
setResult(a, (fn.typ != nil and tfNoSideEffect in fn.typ.flags) or
(fn.kind == nkSym and fn.sym.kind == skFunc))

View File

@@ -6,7 +6,7 @@ colon = ':' COMMENT?
colcom = ':' COMMENT?
operator = OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9
| 'or' | 'xor' | 'and'
| 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from' |
| 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from'
| 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'static' | '..'
prefixOperator = operator
optInd = COMMENT? IND?
@@ -31,15 +31,15 @@ dotExpr = expr '.' optInd (symbol | '[:' exprList ']')
explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )?
qualifiedIdent = symbol ('.' optInd symbol)?
setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}'
castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')'
castExpr = 'cast' ('[' optInd typeDesc optPar ']' '(' optInd expr optPar ')') /
parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try'
| 'finally' | 'except' | 'for' | 'block' | 'const' | 'let'
| 'when' | 'var' | 'mixin'
par = '(' optInd
( &parKeyw complexOrSimpleStmt ^+ ';'
| ';' complexOrSimpleStmt ^+ ';'
( &parKeyw (ifExpr \ complexOrSimpleStmt) ^+ ';'
| ';' (ifExpr \ complexOrSimpleStmt) ^+ ';'
| pragmaStmt
| simpleExpr ( ('=' expr (';' complexOrSimpleStmt ^+ ';' )? )
| simpleExpr ( ('=' expr (';' (ifExpr \ complexOrSimpleStmt) ^+ ';' )? )
| (':' expr (',' exprColonEqExpr ^+ ',' )? ) ) )
optPar ')'
literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT
@@ -59,11 +59,6 @@ primarySuffix = '(' (exprColonEqExpr comma?)* ')'
| '[' optInd exprColonEqExprList optPar ']'
| '{' optInd exprColonEqExprList optPar '}'
| &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax
condExpr = expr colcom expr optInd
('elif' expr colcom expr optInd)*
'else' colcom expr
ifExpr = 'if' condExpr
whenExpr = 'when' condExpr
pragma = '{.' optInd (exprColonEqExpr comma?)* optPar ('.}' | '}')
identVis = symbol OPR? # postfix position
identVisDot = symbol '.' optInd symbol OPR?
@@ -130,6 +125,11 @@ condStmt = expr colcom stmt COMMENT?
(IND{=} 'else' colcom stmt)?
ifStmt = 'if' condStmt
whenStmt = 'when' condStmt
condExpr = expr colcom expr optInd
('elif' expr colcom expr optInd)*
'else' colcom expr
ifExpr = 'if' condExpr
whenExpr = 'when' condExpr
whileStmt = 'while' expr colcom stmt
ofBranch = 'of' exprList colcom stmt
ofBranches = ofBranch (IND{=} ofBranch)*
@@ -193,8 +193,8 @@ complexOrSimpleStmt = (ifStmt | whenStmt | whileStmt
| tryStmt | forStmt
| blockStmt | staticStmt | deferStmt | asmStmt
| 'proc' routine
| 'func' routine
| 'method' routine
| 'func' routine
| 'iterator' routine
| 'macro' routine
| 'template' routine

View File

@@ -6031,12 +6031,12 @@ so that it can be used for debugging routines marked as ``noSideEffect``.
To override the compiler's side effect analysis a ``{.noSideEffect.}``
pragma block can be used:
``cast`` pragma block can be used:
.. code-block:: nim
func f() =
{.noSideEffect.}:
{.cast(noSideEffect).}:
echo "test"
@@ -7501,7 +7501,7 @@ To disable the GC-safety checking the ``--threadAnalysis:off`` command line
switch can be used. This is a temporary workaround to ease the porting effort
from old code to the new threading model.
To override the compiler's gcsafety analysis a ``{.gcsafe.}`` pragma block can
To override the compiler's gcsafety analysis a ``{.cast(gcsafe).}`` pragma block can
be used:
.. code-block:: nim
@@ -7511,7 +7511,7 @@ be used:
perThread {.threadvar.}: string
proc setPerThread() =
{.gcsafe.}:
{.cast(gcsafe).}:
deepCopy(perThread, someGlobal)

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2018 Nim contributors
# (c) Copyright 2020 Nim contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -9,11 +9,46 @@
## This module provides access to the inferred .raises effects
## for Nim's macro system.
## **Since**: Version 1.4.
##
## One can test for the existance of this standard module
## via ``defined(nimHasEffectTraitsModule)``.
import macros
proc getRaisesListImpl(n: NimNode): NimNode = discard "see compiler/vmops.nim"
proc getTagsListImpl(n: NimNode): NimNode = discard "see compiler/vmops.nim"
proc isGcSafeImpl(n: NimNode): bool = discard "see compiler/vmops.nim"
proc hasNoSideEffectsImpl(n: NimNode): bool = discard "see compiler/vmops.nim"
proc getRaisesList*(call: NimNode): NimNode =
expectKind call, nnkCallKinds
result = getRaisesListImpl(call[0])
proc getRaisesList*(fn: NimNode): NimNode =
## Extracts the ``.raises`` list of the func/proc/etc ``fn``.
## ``fn`` has to be a resolved symbol of kind ``nnkSym``. This
## implies that the macro that calls this proc should accept ``typed``
## arguments and not ``untyped`` arguments.
expectKind fn, nnkSym
result = getRaisesListImpl(fn)
proc getTagsList*(fn: NimNode): NimNode =
## Extracts the ``.tags`` list of the func/proc/etc ``fn``.
## ``fn`` has to be a resolved symbol of kind ``nnkSym``. This
## implies that the macro that calls this proc should accept ``typed``
## arguments and not ``untyped`` arguments.
expectKind fn, nnkSym
result = getTagsListImpl(fn)
proc isGcSafe*(fn: NimNode): bool =
## Return true if the func/proc/etc ``fn`` is `gcsafe`.
## ``fn`` has to be a resolved symbol of kind ``nnkSym``. This
## implies that the macro that calls this proc should accept ``typed``
## arguments and not ``untyped`` arguments.
expectKind fn, nnkSym
result = isGcSafeImpl(fn)
proc hasNoSideEffects*(fn: NimNode): bool =
## Return true if the func/proc/etc ``fn`` has `noSideEffect`.
## ``fn`` has to be a resolved symbol of kind ``nnkSym``. This
## implies that the macro that calls this proc should accept ``typed``
## arguments and not ``untyped`` arguments.
expectKind fn, nnkSym
result = hasNoSideEffectsImpl(fn)

View File

@@ -0,0 +1,18 @@
discard """
cmd: "nim c $file"
action: "compile"
"""
proc taggy() {.tags: RootEffect.} = discard
proc m {.raises: [], tags: [].} =
{.cast(noSideEffect).}:
echo "hi"
{.cast(raises: []).}:
raise newException(ValueError, "bah")
{.cast(tags: []).}:
taggy()
m()

View File

@@ -1,5 +1,7 @@
discard """
nimout: '''##[ValueError, Gen[string]]##'''
nimout: '''##[ValueError, Gen[string]]##
%%[RootEffect]%%
true true'''
"""
import macros
@@ -10,13 +12,18 @@ type
x: T
macro m(call: typed): untyped =
echo "##", repr getRaisesList(call), "##"
echo "##", repr getRaisesList(call[0]), "##"
echo "%%", repr getTagsList(call[0]), "%%"
echo isGcSafe(call[0]), " ", hasNoSideEffects(call[0])
result = call
proc gutenTag() {.tags: RootEffect.} = discard
proc r(inp: int) =
if inp == 0:
raise newException(ValueError, "bah")
elif inp == 1:
raise newException(Gen[string], "bahB")
gutenTag()
m r(2)