1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# run-async
# call-external
# Control flow (break/continue/return) through try/finally around awaits.
# The unwind machinery must behave identically when suspension points sit
# inside the protected regions — whether the awaited coroutine completes
# immediately (local coroutines) or genuinely suspends and resumes
# (pending external futures resolved via ResolveFutures, with a dump/load
# round-trip at every suspension in the test harness).
async def value(v):
return v
async def boom(msg):
raise ValueError(msg)
# === finally with break swallows an exception from an awaited coroutine ===
async def swallow_awaited_error():
log = []
while True:
try:
await boom('swallowed')
finally:
log.append('finally')
break
return log
assert await swallow_awaited_error() == ['finally'] # pyright: ignore
# === continue in finally swallows an awaited exception and resumes the loop ===
async def continue_swallows_awaited_error():
log = []
for i in range(3):
try:
log.append(await value(i))
if i < 2:
await boom('swallowed')
log.append('clean')
finally:
log.append('finally')
if i < 2:
continue
log.append('after')
return log
assert await continue_swallows_awaited_error() == [0, 'finally', 1, 'finally', 2, 'clean', 'finally', 'after'] # pyright: ignore
# === return in finally swallows an awaited exception ===
async def return_in_finally():
try:
await boom('swallowed')
finally:
return 'returned'
assert await return_in_finally() == 'returned' # pyright: ignore
# === await inside the finally body on the exception path ===
async def await_in_finally():
log = []
try:
try:
raise ValueError('original')
finally:
log.append(await value('from finally'))
except ValueError as e:
log.append(str(e))
return log
assert await await_in_finally() == ['from finally', 'original'] # pyright: ignore
# === return value awaited before the finally runs ===
async def order_check():
log = []
async def tag(v):
log.append(v)
return v
async def inner():
try:
return await tag('value')
finally:
log.append('finally')
result = await inner()
return result, log
assert await order_check() == ('value', ['value', 'finally']) # pyright: ignore
# === break through finally with an await between iterations ===
async def loop_with_awaits():
log = []
for i in range(5):
try:
log.append(await value(i))
if i == 1:
break
finally:
log.append('finally')
return log
assert await loop_with_awaits() == [0, 'finally', 1, 'finally'] # pyright: ignore
# === pending external await: break in finally swallows the suspended failure ===
# `async_fail` suspends into ResolveFutures and resumes with the exception
# in-flight; the finally then suspends again on its own pending await before
# break swallows the exception.
async def swallow_pending_failure():
log = []
while True:
try:
await async_fail('ValueError', 'pending failure') # pyright: ignore
finally:
log.append(await async_call('finally')) # pyright: ignore
break
return log
assert await swallow_pending_failure() == ['finally'] # pyright: ignore
# === pending external await: return in finally swallows the suspended failure ===
async def return_swallows_pending_failure():
try:
await async_fail('ValueError', 'pending failure') # pyright: ignore
finally:
return await async_call('returned') # pyright: ignore
assert await return_swallows_pending_failure() == 'returned' # pyright: ignore
# === pending external await: continue in finally swallows per-iteration failures ===
async def continue_swallows_pending_failures():
log = []
for i in range(3):
try:
log.append(await async_call(i)) # pyright: ignore
if i < 2:
await async_fail('ValueError', 'pending failure') # pyright: ignore
finally:
if i < 2:
continue
log.append('clean')
return log
assert await continue_swallows_pending_failures() == [0, 1, 2, 'clean'] # pyright: ignore