Merge pull request #7770 from yglukhov/yield-in-try

Yield in try
This commit is contained in:
Andreas Rumpf 2018-06-05 19:58:00 +02:00 • committed by GitHub
commit 3cbc07ac78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1790 additions and 77 deletions

View file

@ -2326,7 +2326,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
of nkParForStmt: genParForStmt(p, n) of nkParForStmt: genParForStmt(p, n)
of nkState: genState(p, n) of nkState: genState(p, n)
of nkGotoState: genGotoState(p, n) of nkGotoState: genGotoState(p, n)
of nkBreakState: genBreakState(p, n) of nkBreakState: genBreakState(p, n, d)
else: internalError(p.config, n.info, "expr(" & $n.kind & "); unknown node kind") else: internalError(p.config, n.info, "expr(" & $n.kind & "); unknown node kind")
proc genNamedConstExpr(p: BProc, n: PNode): Rope = proc genNamedConstExpr(p: BProc, n: PNode): Rope =

View file

@ -157,6 +157,39 @@ proc genState(p: BProc, n: PNode) =
elif n0.kind == nkStrLit: elif n0.kind == nkStrLit:
linefmt(p, cpsStmts, "$1: ;$n", n0.strVal.rope) linefmt(p, cpsStmts, "$1: ;$n", n0.strVal.rope)
proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
# Called by return and break stmts.
# Deals with issues faced when jumping out of try/except/finally stmts,
var stack = newSeq[tuple[n: PNode, inExcept: bool]](0)
for i in countup(1, howManyTrys):
let tryStmt = p.nestedTryStmts.pop
if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions:
# Pop safe points generated by try
if not tryStmt.inExcept:
linefmt(p, cpsStmts, "#popSafePoint();$n")
# Pop this try-stmt of the list of nested trys
# so we don't infinite recurse on it in the next step.
stack.add(tryStmt)
# Find finally-stmt for this try-stmt
# and generate a copy of its sons
var finallyStmt = lastSon(tryStmt.n)
if finallyStmt.kind == nkFinally:
genStmts(p, finallyStmt.sons[0])
# push old elements again:
for i in countdown(howManyTrys-1, 0):
p.nestedTryStmts.add(stack[i])
if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions:
# Pop exceptions that was handled by the
# except-blocks we are in
for i in countdown(howManyExcepts-1, 0):
linefmt(p, cpsStmts, "#popCurrentException();$n")
proc genGotoState(p: BProc, n: PNode) = proc genGotoState(p: BProc, n: PNode) =
# we resist the temptation to translate it into duff's device as it later # we resist the temptation to translate it into duff's device as it later
# will be translated into computed gotos anyway for GCC at least: # will be translated into computed gotos anyway for GCC at least:
@ -167,7 +200,11 @@ proc genGotoState(p: BProc, n: PNode) =
initLocExpr(p, n.sons[0], a) initLocExpr(p, n.sons[0], a)
lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)]) lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)])
p.beforeRetNeeded = true p.beforeRetNeeded = true
lineF(p, cpsStmts, "case -1: goto BeforeRet_;$n", []) lineF(p, cpsStmts, "case -1:$n", [])
blockLeaveActions(p,
howManyTrys = p.nestedTryStmts.len,
howManyExcepts = p.inExceptBlockLen)
lineF(p, cpsStmts, " goto BeforeRet_;$n", [])
var statesCounter = lastOrd(n.sons[0].typ) var statesCounter = lastOrd(n.sons[0].typ)
if n.len >= 2 and n[1].kind == nkIntLit: if n.len >= 2 and n[1].kind == nkIntLit:
statesCounter = n[1].intVal statesCounter = n[1].intVal
@ -177,17 +214,17 @@ proc genGotoState(p: BProc, n: PNode) =
lineF(p, cpsStmts, "case $2: goto $1$2;$n", [prefix, rope(i)]) lineF(p, cpsStmts, "case $2: goto $1$2;$n", [prefix, rope(i)])
lineF(p, cpsStmts, "}$n", []) lineF(p, cpsStmts, "}$n", [])
proc genBreakState(p: BProc, n: PNode) = proc genBreakState(p: BProc, n: PNode, d: var TLoc) =
var a: TLoc var a: TLoc
initLoc(d, locExpr, n, OnUnknown)
if n.sons[0].kind == nkClosure: if n.sons[0].kind == nkClosure:
# XXX this produces quite inefficient code!
initLocExpr(p, n.sons[0].sons[1], a) initLocExpr(p, n.sons[0].sons[1], a)
lineF(p, cpsStmts, "if (((NI*) $1)[1] < 0) break;$n", [rdLoc(a)]) d.r = "(((NI*) $1)[1] < 0)" % [rdLoc(a)]
else: else:
initLocExpr(p, n.sons[0], a) initLocExpr(p, n.sons[0], a)
# the environment is guaranteed to contain the 'state' field at offset 1: # the environment is guaranteed to contain the 'state' field at offset 1:
lineF(p, cpsStmts, "if ((((NI*) $1.ClE_0)[1]) < 0) break;$n", [rdLoc(a)]) d.r = "((((NI*) $1.ClE_0)[1]) < 0)" % [rdLoc(a)]
# lineF(p, cpsStmts, "if (($1) < 0) break;$n", [rdLoc(a)])
proc genGotoVar(p: BProc; value: PNode) = proc genGotoVar(p: BProc; value: PNode) =
if value.kind notin {nkCharLit..nkUInt64Lit}: if value.kind notin {nkCharLit..nkUInt64Lit}:
@ -328,40 +365,6 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) =
else: internalError(p.config, n.info, "genIf()") else: internalError(p.config, n.info, "genIf()")
if sonsLen(n) > 1: fixLabel(p, lend) if sonsLen(n) > 1: fixLabel(p, lend)
proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
# Called by return and break stmts.
# Deals with issues faced when jumping out of try/except/finally stmts,
var stack = newSeq[tuple[n: PNode, inExcept: bool]](0)
for i in countup(1, howManyTrys):
let tryStmt = p.nestedTryStmts.pop
if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions:
# Pop safe points generated by try
if not tryStmt.inExcept:
linefmt(p, cpsStmts, "#popSafePoint();$n")
# Pop this try-stmt of the list of nested trys
# so we don't infinite recurse on it in the next step.
stack.add(tryStmt)
# Find finally-stmt for this try-stmt
# and generate a copy of its sons
var finallyStmt = lastSon(tryStmt.n)
if finallyStmt.kind == nkFinally:
genStmts(p, finallyStmt.sons[0])
# push old elements again:
for i in countdown(howManyTrys-1, 0):
p.nestedTryStmts.add(stack[i])
if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions:
# Pop exceptions that was handled by the
# except-blocks we are in
for i in countdown(howManyExcepts-1, 0):
linefmt(p, cpsStmts, "#popCurrentException();$n")
proc genReturnStmt(p: BProc, t: PNode) = proc genReturnStmt(p: BProc, t: PNode) =
if nfPreventCg in t.flags: return if nfPreventCg in t.flags: return
p.beforeRetNeeded = true p.beforeRetNeeded = true

1287
compiler/closureiters.nim Normal file

File diff suppressed because it is too large Load diff

View file

@ -7,12 +7,11 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
# This include file implements lambda lifting for the transformator. # This file implements lambda lifting for the transformator.
import import
intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os, intsets, strutils, options, ast, astalgo, trees, treetab, msgs,
idents, renderer, types, magicsys, rodread, lowerings, tables, idents, renderer, types, magicsys, rodread, lowerings, tables, modulegraphs
modulegraphs
discard """ discard """
The basic approach is that captured vars need to be put on the heap and The basic approach is that captured vars need to be put on the heap and
@ -126,7 +125,7 @@ proc newCall(a: PSym, b: PNode): PNode =
result.add newSymNode(a) result.add newSymNode(a)
result.add b result.add b
proc createStateType(g: ModuleGraph; iter: PSym): PType = proc createClosureIterStateType*(g: ModuleGraph; iter: PSym): PType =
var n = newNodeI(nkRange, iter.info) var n = newNodeI(nkRange, iter.info)
addSon(n, newIntNode(nkIntLit, -1)) addSon(n, newIntNode(nkIntLit, -1))
addSon(n, newIntNode(nkIntLit, 0)) addSon(n, newIntNode(nkIntLit, 0))
@ -137,8 +136,8 @@ proc createStateType(g: ModuleGraph; iter: PSym): PType =
rawAddSon(result, intType) rawAddSon(result, intType)
proc createStateField(g: ModuleGraph; iter: PSym): PSym = proc createStateField(g: ModuleGraph; iter: PSym): PSym =
result = newSym(skField, getIdent(":state"), iter, iter.info, {}) result = newSym(skField, getIdent(":state"), iter, iter.info)
result.typ = createStateType(g, iter) result.typ = createClosureIterStateType(g, iter)
proc createEnvObj(g: ModuleGraph; owner: PSym; info: TLineInfo): PType = proc createEnvObj(g: ModuleGraph; owner: PSym; info: TLineInfo): PType =
# YYY meh, just add the state field for every closure for now, it's too # YYY meh, just add the state field for every closure for now, it's too
@ -146,7 +145,7 @@ proc createEnvObj(g: ModuleGraph; owner: PSym; info: TLineInfo): PType =
result = createObj(g, owner, info, final=false) result = createObj(g, owner, info, final=false)
rawAddField(result, createStateField(g, owner)) rawAddField(result, createStateField(g, owner))
proc getIterResult(iter: PSym): PSym = proc getClosureIterResult*(iter: PSym): PSym =
if resultPos < iter.ast.len: if resultPos < iter.ast.len:
result = iter.ast.sons[resultPos].sym result = iter.ast.sons[resultPos].sym
else: else:
@ -400,7 +399,11 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) =
if not c.capturedVars.containsOrIncl(s.id): if not c.capturedVars.containsOrIncl(s.id):
let obj = getHiddenParam(c.graph, owner).typ.lastSon let obj = getHiddenParam(c.graph, owner).typ.lastSon
#let obj = c.getEnvTypeForOwner(s.owner).lastSon #let obj = c.getEnvTypeForOwner(s.owner).lastSon
addField(obj, s)
if s.name == getIdent(":state"):
obj.n[0].sym.id = -s.id
else:
addField(obj, s)
# but always return because the rest of the proc is only relevant when # but always return because the rest of the proc is only relevant when
# ow != owner: # ow != owner:
return return
@ -598,7 +601,7 @@ proc accessViaEnvVar(n: PNode; owner: PSym; d: DetectionPass;
localError(d.graph.config, n.info, "internal error: not part of closure object type") localError(d.graph.config, n.info, "internal error: not part of closure object type")
result = n result = n
proc getStateField(g: ModuleGraph; owner: PSym): PSym = proc getStateField*(g: ModuleGraph; owner: PSym): PSym =
getHiddenParam(g, owner).typ.sons[0].n.sons[0].sym getHiddenParam(g, owner).typ.sons[0].n.sons[0].sym
proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass;
@ -625,7 +628,7 @@ proc transformYield(n: PNode; owner: PSym; d: DetectionPass;
if n.sons[0].kind != nkEmpty: if n.sons[0].kind != nkEmpty:
var a = newNodeI(nkAsgn, n.sons[0].info) var a = newNodeI(nkAsgn, n.sons[0].info)
var retVal = liftCapturedVars(n.sons[0], owner, d, c) var retVal = liftCapturedVars(n.sons[0], owner, d, c)
addSon(a, newSymNode(getIterResult(owner))) addSon(a, newSymNode(getClosureIterResult(owner)))
addSon(a, retVal) addSon(a, retVal)
retStmt.add(a) retStmt.add(a)
else: else:
@ -718,7 +721,9 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass;
# echo renderTree(s.getBody, {renderIds}) # echo renderTree(s.getBody, {renderIds})
let oldInContainer = c.inContainer let oldInContainer = c.inContainer
c.inContainer = 0 c.inContainer = 0
let body = wrapIterBody(d.graph, liftCapturedVars(s.getBody, s, d, c), s) var body = liftCapturedVars(s.getBody, s, d, c)
if oldIterTransf in d.graph.config.features:
body = wrapIterBody(d.graph, body, s)
if c.envvars.getOrDefault(s.id).isNil: if c.envvars.getOrDefault(s.id).isNil:
s.ast.sons[bodyPos] = body s.ast.sons[bodyPos] = body
else: else:
@ -761,9 +766,9 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass;
if n[1].kind == nkClosure: result = n[1] if n[1].kind == nkClosure: result = n[1]
else: else:
if owner.isIterator: if owner.isIterator:
if n.kind == nkYieldStmt: if oldIterTransf in d.graph.config.features and n.kind == nkYieldStmt:
return transformYield(n, owner, d, c) return transformYield(n, owner, d, c)
elif n.kind == nkReturnStmt: elif oldIterTransf in d.graph.config.features and n.kind == nkReturnStmt:
return transformReturn(n, owner, d, c) return transformReturn(n, owner, d, c)
elif nfLL in n.flags: elif nfLL in n.flags:
# special case 'when nimVm' due to bug #3636: # special case 'when nimVm' due to bug #3636:
@ -811,7 +816,7 @@ proc liftIterToProc*(g: ModuleGraph; fn: PSym; body: PNode; ptrType: PType): PNo
fn.typ.callConv = oldCC fn.typ.callConv = oldCC
proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool): PNode = proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool): PNode =
# XXX conf.cmd == cmdCompileToJS does not suffice! The compiletime stuff needs # XXX gCmd == cmdCompileToJS does not suffice! The compiletime stuff needs
# the transformation even when compiling to JS ... # the transformation even when compiling to JS ...
# However we can do lifting for the stuff which is *only* compiletime. # However we can do lifting for the stuff which is *only* compiletime.
@ -820,6 +825,7 @@ proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool): PN
if body.kind == nkEmpty or ( if body.kind == nkEmpty or (
g.config.cmd == cmdCompileToJS and not isCompileTime) or g.config.cmd == cmdCompileToJS and not isCompileTime) or
fn.skipGenericOwner.kind != skModule: fn.skipGenericOwner.kind != skModule:
# ignore forward declaration: # ignore forward declaration:
result = body result = body
tooEarly = true tooEarly = true
@ -831,10 +837,12 @@ proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool): PN
d.somethingToDo = true d.somethingToDo = true
if d.somethingToDo: if d.somethingToDo:
var c = initLiftingPass(fn) var c = initLiftingPass(fn)
var newBody = liftCapturedVars(body, fn, d, c) result = liftCapturedVars(body, fn, d, c)
if c.envvars.getOrDefault(fn.id) != nil: if c.envvars.getOrDefault(fn.id) != nil:
newBody = newTree(nkStmtList, rawClosureCreation(fn, d, c), newBody) result = newTree(nkStmtList, rawClosureCreation(fn, d, c), result)
result = wrapIterBody(g, newBody, fn)
if oldIterTransf in g.config.features:
result = wrapIterBody(g, result, fn)
else: else:
result = body result = body
#if fn.name.s == "get2": #if fn.name.s == "get2":
@ -872,7 +880,8 @@ proc liftForLoop*(g: ModuleGraph; body: PNode; owner: PSym): PNode =
cl = createClosure() cl = createClosure()
while true: while true:
let i = foo(cl) let i = foo(cl)
nkBreakState(cl.state) if (nkBreakState(cl.state)):
break
... ...
""" """
if liftingHarmful(g.config, owner): return body if liftingHarmful(g.config, owner): return body
@ -932,5 +941,16 @@ proc liftForLoop*(g: ModuleGraph; body: PNode; owner: PSym): PNode =
loopBody.sons[0] = v2 loopBody.sons[0] = v2
var bs = newNodeI(nkBreakState, body.info) var bs = newNodeI(nkBreakState, body.info)
bs.addSon(call.sons[0]) bs.addSon(call.sons[0])
loopBody.sons[1] = bs
let ibs = newNode(nkIfStmt)
let elifBranch = newNode(nkElifBranch)
elifBranch.add(bs)
let br = newNode(nkBreakStmt)
br.add(emptyNode)
elifBranch.add(br)
ibs.add(elifBranch)
loopBody.sons[1] = ibs
loopBody.sons[2] = body[L-1] loopBody.sons[2] = body[L-1]

View file

@ -116,7 +116,8 @@ type
callOperator, callOperator,
parallel, parallel,
destructor, destructor,
notnil notnil,
oldIterTransf
SymbolFilesOption* = enum SymbolFilesOption* = enum
disabledSf, enabledSf, writeOnlySf, readOnlySf, v2Sf disabledSf, enabledSf, writeOnlySf, readOnlySf, v2Sf

View file

@ -1413,11 +1413,21 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
put(g, tkParLe, "(ComesFrom|") put(g, tkParLe, "(ComesFrom|")
gsub(g, n, 0) gsub(g, n, 0)
put(g, tkParRi, ")") put(g, tkParRi, ")")
of nkGotoState, nkState: of nkGotoState:
var c: TContext var c: TContext
initContext c initContext c
putWithSpace g, tkSymbol, if n.kind == nkState: "state" else: "goto" putWithSpace g, tkSymbol, "goto"
gsons(g, n, c) gsons(g, n, c)
of nkState:
var c: TContext
initContext c
putWithSpace g, tkSymbol, "state"
gsub(g, n[0], c)
putWithSpace(g, tkColon, ":")
indentNL(g)
gsons(g, n, c, 1)
dedent(g)
of nkBreakState: of nkBreakState:
put(g, tkTuple, "breakstate") put(g, tkTuple, "breakstate")
of nkTypeClassTy: of nkTypeClassTy:

View file

@ -1556,7 +1556,7 @@ proc semYield(c: PContext, n: PNode): PNode =
checkSonsLen(n, 1, c.config) checkSonsLen(n, 1, c.config)
if c.p.owner == nil or c.p.owner.kind != skIterator: if c.p.owner == nil or c.p.owner.kind != skIterator:
localError(c.config, n.info, errYieldNotAllowedHere) localError(c.config, n.info, errYieldNotAllowedHere)
elif c.p.inTryStmt > 0 and c.p.owner.typ.callConv != ccInline: elif oldIterTransf in c.features and c.p.inTryStmt > 0 and c.p.owner.typ.callConv != ccInline:
localError(c.config, n.info, errYieldNotAllowedInTryStmt) localError(c.config, n.info, errYieldNotAllowedInTryStmt)
elif n.sons[0].kind != nkEmpty: elif n.sons[0].kind != nkEmpty:
n.sons[0] = semExprWithType(c, n.sons[0]) # check for type compatibility: n.sons[0] = semExprWithType(c, n.sons[0]) # check for type compatibility:

View file

@ -19,9 +19,9 @@
# * transforms 'defer' into a 'try finally' statement # * transforms 'defer' into a 'try finally' statement
import import
intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os, intsets, strutils, options, ast, astalgo, trees, treetab, msgs, lookups,
idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread, idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread,
lambdalifting, sempass2, lowerings, lookups, destroyer, liftlocals, lambdalifting, sempass2, lowerings, destroyer, liftlocals, closureiters,
modulegraphs modulegraphs
type type
@ -984,6 +984,10 @@ proc transformBody*(g: ModuleGraph; module: PSym, n: PNode, prc: PSym): PNode =
result = liftLocalsIfRequested(prc, result, g.config) result = liftLocalsIfRequested(prc, result, g.config)
if c.needsDestroyPass: #and newDestructors: if c.needsDestroyPass: #and newDestructors:
result = injectDestructorCalls(g, prc, result) result = injectDestructorCalls(g, prc, result)
if prc.isIterator and oldIterTransf notin g.config.features:
result = g.transformClosureIterator(prc, result)
incl(result.flags, nfTransf) incl(result.flags, nfTransf)
#if prc.name.s == "testbody": #if prc.name.s == "testbody":
# echo renderTree(result) # echo renderTree(result)

View file

@ -41,3 +41,6 @@ proc reraiseException() {.compilerRtl.} =
proc writeStackTrace() = discard proc writeStackTrace() = discard
proc setControlCHook(hook: proc () {.noconv.}) = discard proc setControlCHook(hook: proc () {.noconv.}) = discard
proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} =
sysFatal(ReraiseError, "exception handling is not available")

View file

@ -131,6 +131,10 @@ proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
quitOrDebug() quitOrDebug()
prev.up = cur.up prev.up = cur.up
proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} =
if not e.isNil:
currException = e
# some platforms have native support for stack traces: # some platforms have native support for stack traces:
const const
nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and

View file

@ -1,18 +1,25 @@
discard """ discard """
errormsg: "invalid control flow: 'yield' within a constructor" output: '''
line: 16 @[1, 2, 3, 4]
123
'''
""" """
# bug #5314, bug #6626 # bug #5314, bug #6626
import asyncdispatch import asyncdispatch
proc bar(): Future[int] {.async.} = proc bar(i: int): Future[int] {.async.} =
await sleepAsync(500) await sleepAsync(2)
result = 3 result = i
proc foo(): Future[seq[int]] {.async.} = proc foo(): Future[seq[int]] {.async.} =
await sleepAsync(500) await sleepAsync(2)
result = @[1, 2, await bar(), 4] # <--- The bug is here result = @[1, 2, await bar(3), 4] # <--- The bug is here
proc foo2() {.async.} =
await sleepAsync(2)
echo(await bar(1), await bar(2), await bar(3))
echo waitFor foo() echo waitFor foo()
waitFor foo2()

View file

@ -82,7 +82,7 @@ Async traceback:
asyncmacro\.nim\(\d+?\)\s+?a asyncmacro\.nim\(\d+?\)\s+?a
asyncmacro\.nim\(\d+?\)\s+?a_continue asyncmacro\.nim\(\d+?\)\s+?a_continue
## Resumes an async procedure ## Resumes an async procedure
asyncmacro\.nim\(\d+?\)\s+?aIter tasync_traceback\.nim\(\d+?\)\s+?aIter
asyncfutures\.nim\(\d+?\)\s+?read asyncfutures\.nim\(\d+?\)\s+?read
\]# \]#
Exception message: b failure Exception message: b failure
@ -110,7 +110,7 @@ Async traceback:
## Executes pending callbacks ## Executes pending callbacks
asyncmacro\.nim\(\d+?\)\s+?foo_continue asyncmacro\.nim\(\d+?\)\s+?foo_continue
## Resumes an async procedure ## Resumes an async procedure
asyncmacro\.nim\(\d+?\)\s+?fooIter tasync_traceback\.nim\(\d+?\)\s+?fooIter
asyncfutures\.nim\(\d+?\)\s+?read asyncfutures\.nim\(\d+?\)\s+?read
\]# \]#
Exception message: bar failure Exception message: bar failure

View file

@ -1,10 +1,12 @@
discard """ discard """
file: "tasynctry2.nim" file: "tasynctry2.nim"
errormsg: "\'yield\' cannot be used within \'try\' in a non-inlined iterator" errormsg: "\'yield\' cannot be used within \'try\' in a non-inlined iterator"
line: 15 line: 17
""" """
import asyncdispatch import asyncdispatch
{.experimental: "oldIterTransf".}
proc foo(): Future[bool] {.async.} = discard proc foo(): Future[bool] {.async.} = discard
proc test5(): Future[int] {.async.} = proc test5(): Future[int] {.async.} =

372
tests/iter/tyieldintry.nim Normal file
View file

@ -0,0 +1,372 @@
discard """
targets: "c cpp"
output: "ok"
"""
var closureIterResult = newSeq[int]()
proc checkpoint(arg: int) =
closureIterResult.add(arg)
type
TestException = object of Exception
AnotherException = object of Exception
proc testClosureIterAux(it: iterator(): int, exceptionExpected: bool, expectedResults: varargs[int]) =
closureIterResult.setLen(0)
var exceptionCaught = false
try:
for i in it():
closureIterResult.add(i)
except TestException:
exceptionCaught = true
if closureIterResult != @expectedResults or exceptionCaught != exceptionExpected:
if closureIterResult != @expectedResults:
echo "Expected: ", @expectedResults
echo "Actual: ", closureIterResult
if exceptionCaught != exceptionExpected:
echo "Expected exception: ", exceptionExpected
echo "Got exception: ", exceptionCaught
doAssert(false)
proc test(it: iterator(): int, expectedResults: varargs[int]) =
testClosureIterAux(it, false, expectedResults)
proc testExc(it: iterator(): int, expectedResults: varargs[int]) =
testClosureIterAux(it, true, expectedResults)
proc raiseException() =
raise newException(TestException, "Test exception!")
block:
iterator it(): int {.closure.} =
var i = 5
while i != 0:
yield i
if i == 3:
yield 123
dec i
test(it, 5, 4, 3, 123, 2, 1)
block:
iterator it(): int {.closure.} =
yield 0
try:
checkpoint(1)
raiseException()
except TestException:
checkpoint(2)
yield 3
checkpoint(4)
finally:
checkpoint(5)
checkpoint(6)
test(it, 0, 1, 2, 3, 4, 5, 6)
block:
iterator it(): int {.closure.} =
yield 0
try:
yield 1
checkpoint(2)
finally:
checkpoint(3)
yield 4
checkpoint(5)
yield 6
test(it, 0, 1, 2, 3, 4, 5, 6)
block:
iterator it(): int {.closure.} =
yield 0
try:
yield 1
raiseException()
yield 2
finally:
checkpoint(3)
yield 4
checkpoint(5)
yield 6
testExc(it, 0, 1, 3, 4, 5, 6)
block:
iterator it(): int {.closure.} =
try:
try:
raiseException()
except AnotherException:
yield 123
finally:
checkpoint(3)
finally:
checkpoint(4)
testExc(it, 3, 4)
block:
iterator it(): int {.closure.} =
try:
yield 1
raiseException()
except AnotherException:
checkpoint(123)
finally:
checkpoint(2)
checkpoint(3)
testExc(it, 1, 2)
block:
iterator it(): int {.closure.} =
try:
yield 0
try:
yield 1
try:
yield 2
raiseException()
except AnotherException:
yield 123
finally:
yield 3
except AnotherException:
yield 124
finally:
yield 4
checkpoint(1234)
except:
yield 5
checkpoint(6)
finally:
checkpoint(7)
yield 8
checkpoint(9)
test(it, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
block:
iterator it(): int {.closure.} =
try:
yield 0
return 2
finally:
checkpoint(1)
checkpoint(123)
test(it, 0, 1)
block:
iterator it(): int {.closure.} =
try:
try:
yield 0
raiseException()
finally:
checkpoint(1)
except TestException:
yield 2
return
finally:
yield 3
checkpoint(123)
test(it, 0, 1, 2, 3)
block:
iterator it(): int {.closure.} =
try:
try:
yield 0
raiseException()
finally:
return # Return in finally should stop exception propagation
except AnotherException:
yield 2
return
finally:
yield 3
checkpoint(123)
test(it, 0, 3)
block: # Yield in yield
iterator it(): int {.closure.} =
template foo(): int =
yield 1
2
for i in 0 .. 2:
checkpoint(0)
yield foo()
test(it, 0, 1, 2, 0, 1, 2, 0, 1, 2)
block:
iterator it(): int {.closure.} =
let i = if true:
yield 0
1
else:
2
yield i
test(it, 0, 1)
block:
iterator it(): int {.closure.} =
var foo = 123
let i = try:
yield 0
raiseException()
1
except TestException as e:
assert(e.msg == "Test exception!")
case foo
of 1:
yield 123
2
of 123:
yield 5
6
else:
7
yield i
test(it, 0, 5, 6)
block:
iterator it(): int {.closure.} =
proc voidFoo(i1, i2, i3: int) =
checkpoint(i1)
checkpoint(i2)
checkpoint(i3)
proc foo(i1, i2, i3: int): int =
voidFoo(i1, i2, i3)
i3
proc bar(i1: int): int =
checkpoint(i1)
template tryexcept: int =
try:
yield 1
raiseException()
123
except TestException:
yield 2
checkpoint(3)
4
let e1 = true
template ifelse1: int =
if e1:
yield 10
11
else:
12
template ifelse2: int =
if ifelse1() == 12:
yield 20
21
else:
yield 22
23
let i = foo(bar(0), tryexcept, ifelse2)
discard foo(bar(0), tryexcept, ifelse2)
voidFoo(bar(0), tryexcept, ifelse2)
yield i
test(it,
# let i = foo(bar(0), tryexcept, ifelse2)
0, # bar(0)
1, 2, 3, # tryexcept
10, # ifelse1
22, # ifelse22
0, 4, 23, # foo
# discard foo(bar(0), tryexcept, ifelse2)
0, # bar(0)
1, 2, 3, # tryexcept
10, # ifelse1
22, # ifelse22
0, 4, 23, # foo
# voidFoo(bar(0), tryexcept, ifelse2)
0, # bar(0)
1, 2, 3, # tryexcept
10, # ifelse1
22, # ifelse22
0, 4, 23, # foo
23 # i
)
block:
iterator it(): int {.closure.} =
checkpoint(0)
for i in 0 .. 1:
try:
yield 1
raiseException()
except TestException as e:
doAssert(e.msg == "Test exception!")
yield 2
except AnotherException:
yield 123
except:
yield 1234
finally:
yield 3
checkpoint(4)
yield 5
test(it, 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
block:
iterator it(): int {.closure.} =
var i = 5
template foo(): bool =
yield i
true
while foo():
dec i
if i == 0:
break
test(it, 5, 4, 3, 2, 1)
block: # Short cirquits
iterator it(): int {.closure.} =
template trueYield: bool =
yield 1
true
template falseYield: bool =
yield 0
false
if trueYield or falseYield:
discard falseYield and trueYield
if falseYield and trueYield:
checkpoint(123)
test(it, 1, 0, 0)
echo "ok"