Skip to content

Commit f2a6110

Browse files
author
whitequark
committedJul 22, 2015
Add integration tests for every language construct.
1 parent dff4ce7 commit f2a6110

22 files changed

+384
-92
lines changed
 

Diff for: ‎artiq/compiler/testbench/inferencer.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import sys, fileinput, os
22
from pythonparser import source, diagnostic, algorithm, parse_buffer
33
from .. import prelude, types
4-
from ..transforms import ASTTypedRewriter, Inferencer
4+
from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer
55

66
class Printer(algorithm.Visitor):
77
"""
@@ -42,6 +42,12 @@ def generic_visit(self, node):
4242
":{}".format(self.type_printer.name(node.type)))
4343

4444
def main():
45+
if sys.argv[1] == "+mono":
46+
del sys.argv[1]
47+
monomorphize = True
48+
else:
49+
monomorphize = False
50+
4551
if len(sys.argv) > 1 and sys.argv[1] == "+diag":
4652
del sys.argv[1]
4753
def process_diagnostic(diag):
@@ -62,6 +68,9 @@ def process_diagnostic(diag):
6268
parsed, comments = parse_buffer(buf, engine=engine)
6369
typed = ASTTypedRewriter(engine=engine).visit(parsed)
6470
Inferencer(engine=engine).visit(typed)
71+
if monomorphize:
72+
IntMonomorphizer(engine=engine).visit(typed)
73+
Inferencer(engine=engine).visit(typed)
6574

6675
printer = Printer(buf)
6776
printer.visit(typed)
File renamed without changes.

Diff for: ‎artiq/compiler/testbench/llvmgen.py

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ def process_diagnostic(diag):
1313
engine.process = process_diagnostic
1414

1515
llmod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine).llvm_ir
16+
17+
# Add main so that the result can be executed with lli
18+
llmain = ll.Function(llmod, ll.FunctionType(ll.VoidType(), []), "main")
19+
llbuilder = ll.IRBuilder(llmain.append_basic_block("entry"))
20+
llbuilder.call(llmod.get_global(llmod.name + ".__modinit__"), [])
21+
llbuilder.ret_void()
22+
1623
print(llmod)
1724

1825
if __name__ == "__main__":

Diff for: ‎artiq/compiler/transforms/artiq_ir_generator.py

+68-60
Large diffs are not rendered by default.

Diff for: ‎artiq/compiler/transforms/asttyped_rewriter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ def visit_Subscript(self, node):
300300
node = self.generic_visit(node)
301301
node = asttyped.SubscriptT(type=types.TVar(),
302302
value=node.value, slice=node.slice, ctx=node.ctx,
303-
loc=node.loc)
303+
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
304304
return self.visit(node)
305305

306306
def visit_BoolOp(self, node):

Diff for: ‎artiq/compiler/transforms/inferencer.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def _unify_iterable(self, element, collection):
109109
self.engine.process(diag)
110110

111111
def visit_Index(self, node):
112+
self.generic_visit(node)
112113
value = node.value
113114
if types.is_tuple(value.type):
114115
diag = diagnostic.Diagnostic("error",
@@ -342,7 +343,8 @@ def _coerce_binop(self, op, left, right):
342343
return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ))
343344
elif isinstance(op, ast.Div):
344345
# division always returns a float
345-
return self._coerce_numeric((left, right), lambda typ: (builtins.TFloat(), typ, typ))
346+
return self._coerce_numeric((left, right),
347+
lambda typ: (builtins.TFloat(), builtins.TFloat(), builtins.TFloat()))
346348
else: # MatMult
347349
diag = diagnostic.Diagnostic("error",
348350
"operator '{op}' is not supported", {"op": op.loc.source()},
@@ -377,7 +379,7 @@ def visit_CompareT(self, node):
377379
for left, right in pairs:
378380
self._unify(left.type, right.type,
379381
left.loc, right.loc)
380-
else:
382+
elif any(map(builtins.is_numeric, operand_types)):
381383
typ = self._coerce_numeric(operands)
382384
if typ:
383385
try:
@@ -393,6 +395,8 @@ def wide_enough(opreand):
393395
other_node = next(filter(wide_enough, operands))
394396
node.left, *node.comparators = \
395397
[self._coerce_one(typ, operand, other_node) for operand in operands]
398+
else:
399+
pass # No coercion required.
396400
self._unify(node.type, builtins.TBool(),
397401
node.loc, None)
398402

Diff for: ‎artiq/compiler/transforms/llvm_ir_generator.py

+57-22
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,16 @@ def llbuiltin(self, name):
9999
llty = ll.FunctionType(ll.VoidType(), [])
100100
elif name in "llvm.trap":
101101
llty = ll.FunctionType(ll.VoidType(), [])
102+
elif name == "llvm.floor.f64":
103+
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()])
102104
elif name == "llvm.round.f64":
103105
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()])
104106
elif name == "llvm.pow.f64":
105107
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()])
106108
elif name == "llvm.powi.f64":
107109
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.IntType(32)])
110+
elif name == "llvm.copysign.f64":
111+
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()])
108112
elif name == "printf":
109113
llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True)
110114
else:
@@ -331,27 +335,36 @@ def process_Arith(self, insn):
331335
elif isinstance(insn.op, ast.FloorDiv):
332336
if builtins.is_float(insn.type):
333337
llvalue = self.llbuilder.fdiv(self.map(insn.lhs()), self.map(insn.rhs()))
334-
return self.llbuilder.call(self.llbuiltin("llvm.round.f64"), [llvalue],
338+
return self.llbuilder.call(self.llbuiltin("llvm.floor.f64"), [llvalue],
335339
name=insn.name)
336340
else:
337341
return self.llbuilder.sdiv(self.map(insn.lhs()), self.map(insn.rhs()),
338342
name=insn.name)
339343
elif isinstance(insn.op, ast.Mod):
344+
# Python only has the modulo operator, LLVM only has the remainder
340345
if builtins.is_float(insn.type):
341-
return self.llbuilder.frem(self.map(insn.lhs()), self.map(insn.rhs()),
346+
llvalue = self.llbuilder.frem(self.map(insn.lhs()), self.map(insn.rhs()))
347+
return self.llbuilder.call(self.llbuiltin("llvm.copysign.f64"),
348+
[llvalue, self.map(insn.rhs())],
342349
name=insn.name)
343350
else:
344-
return self.llbuilder.srem(self.map(insn.lhs()), self.map(insn.rhs()),
345-
name=insn.name)
351+
lllhs, llrhs = map(self.map, (insn.lhs(), insn.rhs()))
352+
llxorsign = self.llbuilder.and_(self.llbuilder.xor(lllhs, llrhs),
353+
ll.Constant(lllhs.type, 1 << lllhs.type.width - 1))
354+
llnegate = self.llbuilder.icmp_unsigned('!=',
355+
llxorsign, ll.Constant(llxorsign.type, 0))
356+
llvalue = self.llbuilder.srem(lllhs, llrhs)
357+
llnegvalue = self.llbuilder.sub(ll.Constant(llvalue.type, 0), llvalue)
358+
return self.llbuilder.select(llnegate, llnegvalue, llvalue)
346359
elif isinstance(insn.op, ast.Pow):
347360
if builtins.is_float(insn.type):
348361
return self.llbuilder.call(self.llbuiltin("llvm.pow.f64"),
349362
[self.map(insn.lhs()), self.map(insn.rhs())],
350363
name=insn.name)
351364
else:
365+
lllhs = self.llbuilder.sitofp(self.map(insn.lhs()), ll.DoubleType())
352366
llrhs = self.llbuilder.trunc(self.map(insn.rhs()), ll.IntType(32))
353-
llvalue = self.llbuilder.call(self.llbuiltin("llvm.powi.f64"),
354-
[self.map(insn.lhs()), llrhs])
367+
llvalue = self.llbuilder.call(self.llbuiltin("llvm.powi.f64"), [lllhs, llrhs])
355368
return self.llbuilder.fptosi(llvalue, self.llty_of_type(insn.type),
356369
name=insn.name)
357370
elif isinstance(insn.op, ast.LShift):
@@ -373,9 +386,9 @@ def process_Arith(self, insn):
373386
assert False
374387

375388
def process_Compare(self, insn):
376-
if isinstance(insn.op, ast.Eq):
389+
if isinstance(insn.op, (ast.Eq, ast.Is)):
377390
op = '=='
378-
elif isinstance(insn.op, ast.NotEq):
391+
elif isinstance(insn.op, (ast.NotEq, ast.IsNot)):
379392
op = '!='
380393
elif isinstance(insn.op, ast.Gt):
381394
op = '>'
@@ -388,35 +401,57 @@ def process_Compare(self, insn):
388401
else:
389402
assert False
390403

391-
if builtins.is_float(insn.lhs().type):
392-
return self.llbuilder.fcmp_ordered(op, self.map(insn.lhs()), self.map(insn.rhs()),
404+
lllhs, llrhs = map(self.map, (insn.lhs(), insn.rhs()))
405+
assert lllhs.type == llrhs.type
406+
407+
if isinstance(lllhs.type, ll.IntType):
408+
return self.llbuilder.icmp_signed(op, lllhs, llrhs,
409+
name=insn.name)
410+
elif isinstance(lllhs.type, ll.PointerType):
411+
return self.llbuilder.icmp_unsigned(op, lllhs, llrhs,
412+
name=insn.name)
413+
elif isinstance(lllhs.type, (ll.FloatType, ll.DoubleType)):
414+
return self.llbuilder.fcmp_ordered(op, lllhs, llrhs,
393415
name=insn.name)
416+
elif isinstance(lllhs.type, ll.LiteralStructType):
417+
# Compare aggregates (such as lists or ranges) element-by-element.
418+
llvalue = ll.Constant(ll.IntType(1), True)
419+
for index in range(len(lllhs.type.elements)):
420+
lllhselt = self.llbuilder.extract_value(lllhs, index)
421+
llrhselt = self.llbuilder.extract_value(llrhs, index)
422+
llresult = self.llbuilder.icmp_unsigned('==', lllhselt, llrhselt)
423+
llvalue = self.llbuilder.select(llresult, llvalue,
424+
ll.Constant(ll.IntType(1), False))
425+
return self.llbuilder.icmp_unsigned(op, llvalue, ll.Constant(ll.IntType(1), True),
426+
name=insn.name)
394427
else:
395-
return self.llbuilder.icmp_signed(op, self.map(insn.lhs()), self.map(insn.rhs()),
396-
name=insn.name)
428+
print(lllhs, llrhs)
429+
assert False
397430

398431
def process_Builtin(self, insn):
399432
if insn.op == "nop":
400433
return self.llbuilder.call(self.llbuiltin("llvm.donothing"), [])
401434
if insn.op == "abort":
402435
return self.llbuilder.call(self.llbuiltin("llvm.trap"), [])
403436
elif insn.op == "is_some":
404-
optarg = self.map(insn.operands[0])
405-
return self.llbuilder.extract_value(optarg, 0,
437+
lloptarg = self.map(insn.operands[0])
438+
return self.llbuilder.extract_value(lloptarg, 0,
406439
name=insn.name)
407440
elif insn.op == "unwrap":
408-
optarg = self.map(insn.operands[0])
409-
return self.llbuilder.extract_value(optarg, 1,
441+
lloptarg = self.map(insn.operands[0])
442+
return self.llbuilder.extract_value(lloptarg, 1,
410443
name=insn.name)
411444
elif insn.op == "unwrap_or":
412-
optarg, default = map(self.map, insn.operands)
413-
has_arg = self.llbuilder.extract_value(optarg, 0)
414-
arg = self.llbuilder.extract_value(optarg, 1)
415-
return self.llbuilder.select(has_arg, arg, default,
445+
lloptarg, lldefault = map(self.map, insn.operands)
446+
llhas_arg = self.llbuilder.extract_value(lloptarg, 0)
447+
llarg = self.llbuilder.extract_value(lloptarg, 1)
448+
return self.llbuilder.select(llhas_arg, llarg, lldefault,
416449
name=insn.name)
417450
elif insn.op == "round":
418-
return self.llbuilder.call(self.llbuiltin("llvm.round.f64"), [llvalue],
419-
name=insn.name)
451+
llarg = self.map(insn.operands[0])
452+
llvalue = self.llbuilder.call(self.llbuiltin("llvm.round.f64"), [llarg])
453+
return self.llbuilder.fptosi(llvalue, self.llty_of_type(insn.type),
454+
name=insn.name)
420455
elif insn.op == "globalenv":
421456
def get_outer(llenv, env_ty):
422457
if ".outer" in env_ty.params:

Diff for: ‎artiq/compiler/types.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ def genalnum():
1313
pos = len(ident) - 1
1414
while pos >= 0:
1515
cur_n = string.ascii_lowercase.index(ident[pos])
16-
if cur_n < 26:
16+
if cur_n < 25:
1717
ident[pos] = string.ascii_lowercase[cur_n + 1]
1818
break
1919
else:
2020
ident[pos] = "a"
2121
pos -= 1
2222
if pos < 0:
23-
ident = "a" + ident
23+
ident = ["a"] + ident
2424

2525
class UnificationError(Exception):
2626
def __init__(self, typea, typeb):

Diff for: ‎artiq/compiler/validators/escape.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,15 @@ def visit_NameT(self, node):
7777

7878
# Value lives as long as the current scope, if it's mutable,
7979
# or else forever
80-
def visit_BinOpT(self, node):
80+
def visit_sometimes_allocating(self, node):
8181
if builtins.is_allocated(node.type):
8282
return self.youngest_region
8383
else:
8484
return None
8585

86+
visit_BinOpT = visit_sometimes_allocating
87+
visit_CallT = visit_sometimes_allocating
88+
8689
# Value lives as long as the object/container, if it's mutable,
8790
# or else forever
8891
def visit_accessor(self, node):
@@ -136,7 +139,6 @@ def visit_immutable(self, node):
136139
visit_EllipsisT = visit_immutable
137140
visit_UnaryOpT = visit_immutable
138141
visit_CompareT = visit_immutable
139-
visit_CallT = visit_immutable
140142

141143
# Value is mutable, but still lives forever
142144
def visit_StrT(self, node):

Diff for: ‎artiq/compiler/validators/local_access.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ def traverse(block):
5555
# in order to be initialized in this block.
5656
def merge_state(a, b):
5757
return {var: a[var] and b[var] for var in a}
58-
block_state[env] = reduce(lambda a, b: merge_state(a[env], b[env]),
59-
pred_states)
58+
block_state[env] = reduce(merge_state,
59+
[state[env] for state in pred_states])
6060
elif len(pred_states) == 1:
6161
# The state is the same as at the terminator of predecessor.
6262
# We'll mutate it, so copy.

Diff for: ‎lit-test/compiler/integration/attribute.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
r = range(10)
4+
assert r.start == 0
5+
assert r.stop == 10
6+
assert r.step == 1

Diff for: ‎lit-test/compiler/integration/builtin.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
assert bool() is False
4+
# bool(x) is tested in bool.py
5+
6+
assert int() is 0
7+
assert int(1.0) is 1
8+
assert int(1, width=64) << 40 is 1099511627776
9+
10+
assert float() is 0.0
11+
assert float(1) is 1.0
12+
13+
x = list()
14+
if False: x = [1]
15+
assert x == []
16+
17+
assert range(10) is range(0, 10, 1)
18+
assert range(1, 10) is range(1, 10, 1)
19+
20+
assert len([1, 2, 3]) is 3
21+
assert len(range(10)) is 10
22+
assert len(range(0, 10, 2)) is 5
23+
24+
assert round(1.4) is 1 and round(1.6) is 2

Diff for: ‎lit-test/compiler/integration/compare.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
assert 1 < 2 and not (2 < 1)
4+
assert 2 > 1 and not (1 > 2)
5+
assert 1 == 1 and not (1 == 2)
6+
assert 1 != 2 and not (1 != 1)
7+
assert 1 <= 1 and 1 <= 2 and not (2 <= 1)
8+
assert 1 >= 1 and 2 >= 1 and not (1 >= 2)
9+
assert 1 is 1 and not (1 is 2)
10+
assert 1 is not 2 and not (1 is not 1)
11+
12+
x, y = [1], [1]
13+
assert x is x and x is not y
14+
assert range(10) is range(10) and range(10) is not range(11)
15+
16+
lst = [1, 2, 3]
17+
assert 1 in lst and 0 not in lst
18+
assert 1 in range(10) and 11 not in range(10) and -1 not in range(10)

Diff for: ‎lit-test/compiler/integration/for.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
count = 0
4+
for x in range(10):
5+
count += 1
6+
assert count == 10
7+
8+
for x in range(10):
9+
assert True
10+
else:
11+
assert True
12+
13+
for x in range(0):
14+
assert False
15+
else:
16+
assert True
17+
18+
for x in range(10):
19+
continue
20+
assert False
21+
else:
22+
assert True
23+
24+
for x in range(10):
25+
break
26+
assert False
27+
else:
28+
assert False

Diff for: ‎lit-test/compiler/integration/if.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
if True:
4+
assert True
5+
6+
if False:
7+
assert False
8+
9+
if True:
10+
assert True
11+
else:
12+
assert False
13+
14+
if False:
15+
assert False
16+
else:
17+
assert True
18+
19+
assert (0 if True else 1) == 0
20+
assert (0 if False else 1) == 1

Diff for: ‎lit-test/compiler/integration/list.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
[x, y] = [1, 2]
4+
assert (x, y) == (1, 2)
5+
6+
lst = [1, 2, 3]
7+
assert [x*x for x in lst] == [1, 4, 9]

Diff for: ‎lit-test/compiler/integration/locals.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
x = 1
4+
assert x == 1
5+
x += 1
6+
assert x == 2

Diff for: ‎lit-test/compiler/integration/operator.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
assert (not True) == False
4+
assert (not False) == True
5+
assert -(-1) == 1
6+
assert -(-1.0) == 1.0
7+
assert +1 == 1
8+
assert +1.0 == 1.0
9+
assert 1 + 1 == 2
10+
assert 1.0 + 1.0 == 2.0
11+
assert 1 - 1 == 0
12+
assert 1.0 - 1.0 == 0.0
13+
assert 2 * 2 == 4
14+
assert 2.0 * 2.0 == 4.0
15+
assert 3 / 2 == 1.5
16+
assert 3.0 / 2.0 == 1.5
17+
assert 3 // 2 == 1
18+
assert 3.0 // 2.0 == 1.0
19+
assert 3 % 2 == 1
20+
assert -3 % 2 == 1
21+
assert 3 % -2 == -1
22+
assert -3 % -2 == -1
23+
assert 3.0 % 2.0 == 1.0
24+
assert -3.0 % 2.0 == 1.0
25+
assert 3.0 % -2.0 == -1.0
26+
assert -3.0 % -2.0 == -1.0
27+
assert 3 ** 2 == 9
28+
assert 3.0 ** 2.0 == 9.0
29+
assert 9.0 ** 0.5 == 3.0
30+
assert 1 << 1 == 2
31+
assert 2 >> 1 == 1
32+
assert -2 >> 1 == -1
33+
assert 0x18 & 0x0f == 0x08
34+
assert 0x18 | 0x0f == 0x1f
35+
assert 0x18 ^ 0x0f == 0x17
36+
37+
assert [1] + [2] == [1, 2]
38+
assert [1] * 3 == [1, 1, 1]

Diff for: ‎lit-test/compiler/integration/print.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s >%t
2+
# RUN: OutputCheck %s --file-to-check=%t
3+
4+
# CHECK-L: None
5+
print(None)
6+
7+
# CHECK-L: True False
8+
print(True, False)
9+
10+
# CHECK-L: 1 -1
11+
print(1, -1)
12+
13+
# CHECK-L: 10000000000
14+
print(10000000000)
15+
16+
# CHECK-L: 1.5
17+
print(1.5)
18+
19+
# CHECK-L: (True, 1)
20+
print((True, 1))
21+
22+
# CHECK-L: (True,)
23+
print((True,))
24+
25+
# CHECK-L: [1, 2, 3]
26+
print([1, 2, 3])
27+
28+
# CHECK-L: [[1, 2], [3]]
29+
print([[1, 2], [3]])
30+
31+
# CHECK-L: range(0, 10, 1)
32+
print(range(10))

Diff for: ‎lit-test/compiler/integration/subscript.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
lst = list(range(10))
4+
assert lst[0] == 0
5+
assert lst[1] == 1
6+
assert lst[-1] == 9
7+
assert lst[0:1] == [0]
8+
assert lst[0:2] == [0, 1]
9+
assert lst[0:10] == lst
10+
assert lst[1:-1] == lst[1:9]
11+
assert lst[0:1:2] == [0]
12+
assert lst[0:2:2] == [0]

Diff for: ‎lit-test/compiler/integration/tuple.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
x, y = 2, 1
4+
x, y = y, x
5+
assert x == 1 and y == 2
6+
assert (1, 2) + (3.0,) == (1, 2, 3.0)

Diff for: ‎lit-test/compiler/integration/while.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# RUN: %python -m artiq.compiler.testbench.jit %s
2+
3+
cond, count = True, 0
4+
while cond:
5+
count += 1
6+
cond = False
7+
assert count == 1
8+
9+
while False:
10+
pass
11+
else:
12+
assert True
13+
14+
cond = True
15+
while cond:
16+
cond = False
17+
else:
18+
assert True
19+
20+
while True:
21+
break
22+
assert False
23+
else:
24+
assert False
25+
26+
cond = True
27+
while cond:
28+
cond = False
29+
continue
30+
assert False

0 commit comments

Comments
 (0)
Please sign in to comment.