-- Test cases for exception handling insertion transform.
--
-- The result includes refcount handling since these two transforms interact.

[case testListGetAndUnboxError]
from typing import List
def f(x: List[int]) -> int:
    return x[0]
[out]
def f(x):
    x :: list
    r0 :: object
    r1, r2 :: int
L0:
    r0 = CPyList_GetItemShort(x, 0)
    if is_error(r0) goto L3 (error at f:3) else goto L1
L1:
    r1 = unbox(int, r0)
    dec_ref r0
    if is_error(r1) goto L3 (error at f:3) else goto L2
L2:
    return r1
L3:
    r2 = <error> :: int
    return r2

[case testListAppendAndSetItemError]
from typing import List
def f(x: List[int], y: int, z: int) -> None:
    x.append(y)
    x[y] = z
[out]
def f(x, y, z):
    x :: list
    y, z :: int
    r0 :: object
    r1 :: i32
    r2 :: bit
    r3 :: object
    r4 :: bit
    r5 :: None
L0:
    inc_ref y :: int
    r0 = box(int, y)
    r1 = PyList_Append(x, r0)
    dec_ref r0
    r2 = r1 >= 0 :: signed
    if not r2 goto L3 (error at f:3) else goto L1 :: bool
L1:
    inc_ref z :: int
    r3 = box(int, z)
    r4 = CPyList_SetItem(x, y, r3)
    if not r4 goto L3 (error at f:4) else goto L2 :: bool
L2:
    return 1
L3:
    r5 = <error> :: None
    return r5

[case testOptionalHandling]
from typing import Optional

class A: pass

def f(x: Optional[A]) -> int:
    if x is None:
        return 1
    if x is not None:
        return 2
    return 3
[out]
def f(x):
    x :: union[__main__.A, None]
    r0 :: object
    r1 :: bit
    r2 :: __main__.A
    r3 :: object
    r4 :: bit
    r5 :: int
L0:
    r0 = load_address _Py_NoneStruct
    r1 = x == r0
    if r1 goto L1 else goto L2 :: bool
L1:
    return 2
L2:
    r2 = borrow cast(__main__.A, x)
    if is_error(r2) goto L6 (error at f:8) else goto L3
L3:
    r3 = load_address _Py_NoneStruct
    r4 = r2 != r3
    if r4 goto L4 else goto L5 :: bool
L4:
    return 4
L5:
    return 6
L6:
    r5 = <error> :: int
    return r5

[case testListSum]
from typing import List
def sum(a: List[int], l: int) -> int:
    sum = 0
    i = 0
    while i < l:
        sum = sum + a[i]
        i = i + 1
    return sum
[out]
def sum(a, l):
    a :: list
    l, sum, i :: int
    r0 :: native_int
    r1 :: bit
    r2 :: native_int
    r3, r4, r5 :: bit
    r6 :: object
    r7, r8, r9, r10 :: int
L0:
    sum = 0
    i = 0
L1:
    r0 = i & 1
    r1 = r0 != 0
    if r1 goto L3 else goto L2 :: bool
L2:
    r2 = l & 1
    r3 = r2 != 0
    if r3 goto L3 else goto L4 :: bool
L3:
    r4 = CPyTagged_IsLt_(i, l)
    if r4 goto L5 else goto L10 :: bool
L4:
    r5 = i < l :: signed
    if r5 goto L5 else goto L10 :: bool
L5:
    r6 = CPyList_GetItemBorrow(a, i)
    if is_error(r6) goto L11 (error at sum:6) else goto L6
L6:
    r7 = unbox(int, r6)
    if is_error(r7) goto L11 (error at sum:6) else goto L7
L7:
    r8 = CPyTagged_Add(sum, r7)
    dec_ref sum :: int
    dec_ref r7 :: int
    sum = r8
    r9 = CPyTagged_Add(i, 2)
    dec_ref i :: int
    i = r9
    goto L1
L8:
    return sum
L9:
    r10 = <error> :: int
    return r10
L10:
    dec_ref i :: int
    goto L8
L11:
    dec_ref sum :: int
    dec_ref i :: int
    goto L9

[case testTryExcept]
def g() -> None:
    try:
        object()
    except:
        print("weeee")
[out]
def g():
    r0 :: object
    r1 :: str
    r2, r3 :: object
    r4 :: tuple[object, object, object]
    r5 :: str
    r6 :: object
    r7 :: str
    r8, r9 :: object
    r10 :: bit
    r11 :: None
L0:
L1:
    r0 = builtins :: module
    r1 = 'object'
    r2 = CPyObject_GetAttr(r0, r1)
    if is_error(r2) goto L3 (error at g:3) else goto L2
L2:
    r3 = PyObject_CallFunctionObjArgs(r2, 0)
    dec_ref r2
    if is_error(r3) goto L3 (error at g:3) else goto L10
L3:
    r4 = CPy_CatchError()
    r5 = 'weeee'
    r6 = builtins :: module
    r7 = 'print'
    r8 = CPyObject_GetAttr(r6, r7)
    if is_error(r8) goto L6 (error at g:5) else goto L4
L4:
    r9 = PyObject_CallFunctionObjArgs(r8, r5, 0)
    dec_ref r8
    if is_error(r9) goto L6 (error at g:5) else goto L11
L5:
    CPy_RestoreExcInfo(r4)
    dec_ref r4
    goto L8
L6:
    CPy_RestoreExcInfo(r4)
    dec_ref r4
    r10 = CPy_KeepPropagating()
    if not r10 goto L9 else goto L7 :: bool
L7:
    unreachable
L8:
    return 1
L9:
    r11 = <error> :: None
    return r11
L10:
    dec_ref r3
    goto L8
L11:
    dec_ref r9
    goto L5

[case testGenopsTryFinally]
def a() -> str:
    try:
        print()
        return 'hi'
    finally:
        print('goodbye!')
[out]
def a():
    r0 :: object
    r1 :: str
    r2, r3 :: object
    r4, r5 :: str
    r6, r7 :: tuple[object, object, object]
    r8 :: str
    r9 :: tuple[object, object, object]
    r10 :: str
    r11 :: object
    r12 :: str
    r13, r14 :: object
    r15 :: bit
    r16 :: str
L0:
L1:
    r0 = builtins :: module
    r1 = 'print'
    r2 = CPyObject_GetAttr(r0, r1)
    if is_error(r2) goto L5 (error at a:3) else goto L2
L2:
    r3 = PyObject_CallFunctionObjArgs(r2, 0)
    dec_ref r2
    if is_error(r3) goto L5 (error at a:3) else goto L19
L3:
    r4 = 'hi'
    inc_ref r4
    r5 = r4
L4:
    r6 = <error> :: tuple[object, object, object]
    r7 = r6
    goto L6
L5:
    r8 = <error> :: str
    r5 = r8
    r9 = CPy_CatchError()
    r7 = r9
L6:
    r10 = 'goodbye!'
    r11 = builtins :: module
    r12 = 'print'
    r13 = CPyObject_GetAttr(r11, r12)
    if is_error(r13) goto L20 (error at a:6) else goto L7
L7:
    r14 = PyObject_CallFunctionObjArgs(r13, r10, 0)
    dec_ref r13
    if is_error(r14) goto L20 (error at a:6) else goto L21
L8:
    if is_error(r7) goto L11 else goto L22
L9:
    CPy_Reraise()
    if not 0 goto L13 else goto L23 :: bool
L10:
    unreachable
L11:
    if is_error(r5) goto L17 else goto L12
L12:
    return r5
L13:
    if is_error(r7) goto L15 else goto L14
L14:
    CPy_RestoreExcInfo(r7)
    xdec_ref r7
L15:
    r15 = CPy_KeepPropagating()
    if not r15 goto L18 else goto L16 :: bool
L16:
    unreachable
L17:
    unreachable
L18:
    r16 = <error> :: str
    return r16
L19:
    dec_ref r3
    goto L3
L20:
    xdec_ref r5
    goto L13
L21:
    dec_ref r14
    goto L8
L22:
    xdec_ref r5
    goto L9
L23:
    xdec_ref r7
    goto L10

[case testDocstring1]
def lol() -> None:
    """Hello"""
    pass
[out]
def lol():
L0:
    return 1

[case testExceptUndefined1]
from typing import Any
def lol(x: Any) -> object:
    try:
        st = x.foo
    except:
        return ''
    # No uninit check should be generated, since the exception branch always returns
    return st
[out]
def lol(x):
    x :: object
    r0 :: str
    r1, st :: object
    r2 :: tuple[object, object, object]
    r3 :: str
L0:
L1:
    r0 = 'foo'
    r1 = CPyObject_GetAttr(x, r0)
    if is_error(r1) goto L3 (error at lol:4) else goto L2
L2:
    st = r1
    goto L4
L3:
    r2 = CPy_CatchError()
    r3 = ''
    CPy_RestoreExcInfo(r2)
    dec_ref r2
    inc_ref r3
    return r3
L4:
    return st

[case testExceptUndefined2]
from typing import Any
def lol(x: Any) -> object:
    try:
        a = x.foo
        b = x.bar
    except:
        pass
    # uninit checks are needed, since the exception can skip initializing the vars
    return a + b
[out]
def lol(x):
    x, r0, a, r1, b :: object
    r2 :: str
    r3 :: object
    r4 :: str
    r5 :: object
    r6 :: tuple[object, object, object]
    r7, r8 :: bool
    r9, r10 :: object
L0:
    r0 = <error> :: object
    a = r0
    r1 = <error> :: object
    b = r1
L1:
    r2 = 'foo'
    r3 = CPyObject_GetAttr(x, r2)
    if is_error(r3) goto L4 (error at lol:4) else goto L15
L2:
    a = r3
    r4 = 'bar'
    r5 = CPyObject_GetAttr(x, r4)
    if is_error(r5) goto L4 (error at lol:5) else goto L16
L3:
    b = r5
    goto L6
L4:
    r6 = CPy_CatchError()
L5:
    CPy_RestoreExcInfo(r6)
    dec_ref r6
L6:
    if is_error(a) goto L17 else goto L9
L7:
    r7 = raise UnboundLocalError('local variable "a" referenced before assignment')
    if not r7 goto L14 (error at lol:9) else goto L8 :: bool
L8:
    unreachable
L9:
    if is_error(b) goto L18 else goto L12
L10:
    r8 = raise UnboundLocalError('local variable "b" referenced before assignment')
    if not r8 goto L14 (error at lol:9) else goto L11 :: bool
L11:
    unreachable
L12:
    r9 = PyNumber_Add(a, b)
    xdec_ref a
    xdec_ref b
    if is_error(r9) goto L14 (error at lol:9) else goto L13
L13:
    return r9
L14:
    r10 = <error> :: object
    return r10
L15:
    xdec_ref a
    goto L2
L16:
    xdec_ref b
    goto L3
L17:
    xdec_ref b
    goto L7
L18:
    xdec_ref a
    goto L10

[case testMaybeUninitVarExc]
def f(b: bool) -> None:
    u = 'a'
    while b:
        v = 'b'
        if v is not u:
            break
    print(v)
[out]
def f(b):
    b :: bool
    r0, v, r1, u, r2 :: str
    r3, r4 :: bit
    r5 :: object
    r6 :: str
    r7 :: object
    r8 :: bool
    r9 :: object
    r10 :: None
L0:
    r0 = <error> :: str
    v = r0
    r1 = 'a'
    inc_ref r1
    u = r1
L1:
    if b goto L10 else goto L11 :: bool
L2:
    r2 = 'b'
    inc_ref r2
    v = r2
    r3 = v == u
    r4 = r3 ^ 1
    if r4 goto L11 else goto L1 :: bool
L3:
    r5 = builtins :: module
    r6 = 'print'
    r7 = CPyObject_GetAttr(r5, r6)
    if is_error(r7) goto L12 (error at f:7) else goto L4
L4:
    if is_error(v) goto L13 else goto L7
L5:
    r8 = raise UnboundLocalError('local variable "v" referenced before assignment')
    if not r8 goto L9 (error at f:7) else goto L6 :: bool
L6:
    unreachable
L7:
    r9 = PyObject_CallFunctionObjArgs(r7, v, 0)
    dec_ref r7
    xdec_ref v
    if is_error(r9) goto L9 (error at f:7) else goto L14
L8:
    return 1
L9:
    r10 = <error> :: None
    return r10
L10:
    xdec_ref v
    goto L2
L11:
    dec_ref u
    goto L3
L12:
    xdec_ref v
    goto L9
L13:
    dec_ref r7
    goto L5
L14:
    dec_ref r9
    goto L8

[case testExceptionWithOverlappingErrorValue]
from mypy_extensions import i64

def f() -> i64:
    return 0

def g() -> i64:
    return f()
[out]
def f():
L0:
    return 0
def g():
    r0 :: i64
    r1 :: bit
    r2 :: object
    r3 :: i64
L0:
    r0 = f()
    r1 = r0 == -113
    if r1 goto L2 else goto L1 :: bool
L1:
    return r0
L2:
    r2 = PyErr_Occurred()
    if not is_error(r2) goto L3 (error at g:7) else goto L1
L3:
    r3 = <error> :: i64
    return r3

[case testExceptionWithNativeAttributeGetAndSet]
class C:
    def __init__(self, x: int) -> None:
        self.x = x

def foo(c: C, x: int) -> None:
    c.x = x - c.x
[out]
def C.__init__(self, x):
    self :: __main__.C
    x :: int
L0:
    inc_ref x :: int
    self.x = x
    return 1
def foo(c, x):
    c :: __main__.C
    x, r0, r1 :: int
    r2 :: bool
L0:
    r0 = borrow c.x
    r1 = CPyTagged_Subtract(x, r0)
    c.x = r1
    return 1

[case testExceptionWithOverlappingFloatErrorValue]
def f() -> float:
    return 0.0

def g() -> float:
    return f()
[out]
def f():
L0:
    return 0.0
def g():
    r0 :: float
    r1 :: bit
    r2 :: object
    r3 :: float
L0:
    r0 = f()
    r1 = r0 == -113.0
    if r1 goto L2 else goto L1 :: bool
L1:
    return r0
L2:
    r2 = PyErr_Occurred()
    if not is_error(r2) goto L3 (error at g:5) else goto L1
L3:
    r3 = <error> :: float
    return r3

[case testExceptionWithLowLevelIntAttribute]
from mypy_extensions import i32, i64

class C:
    def __init__(self, x: i32, y: i64) -> None:
        self.x = x
        self.y = y

def f(c: C) -> None:
    c.x
    c.y
[out]
def C.__init__(self, x, y):
    self :: __main__.C
    x :: i32
    y :: i64
L0:
    self.x = x
    self.y = y
    return 1
def f(c):
    c :: __main__.C
    r0 :: i32
    r1 :: i64
L0:
    r0 = c.x
    r1 = c.y
    return 1

[case testConditionallyUndefinedI64]
from mypy_extensions import i64

def f(x: i64) -> i64:
    if x:
        y: i64 = 2
    return y
[out]
def f(x):
    x, r0, y :: i64
    __locals_bitmap0 :: u32
    r1 :: bit
    r2, r3 :: u32
    r4 :: bit
    r5 :: bool
    r6 :: i64
L0:
    r0 = <error> :: i64
    y = r0
    __locals_bitmap0 = 0
    r1 = x != 0
    if r1 goto L1 else goto L2 :: bool
L1:
    y = 2
    r2 = __locals_bitmap0 | 1
    __locals_bitmap0 = r2
L2:
    r3 = __locals_bitmap0 & 1
    r4 = r3 == 0
    if r4 goto L3 else goto L5 :: bool
L3:
    r5 = raise UnboundLocalError('local variable "y" referenced before assignment')
    if not r5 goto L6 (error at f:-1) else goto L4 :: bool
L4:
    unreachable
L5:
    return y
L6:
    r6 = <error> :: i64
    return r6

[case testExceptionWithFloatAttribute]
class C:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        if x:
            self.y = y

def f(c: C) -> float:
    return c.x + c.y
[out]
def C.__init__(self, x, y):
    self :: __main__.C
    x, y :: float
    r0 :: bit
L0:
    self.x = x
    r0 = x != 0.0
    if r0 goto L1 else goto L2 :: bool
L1:
    self.y = y
L2:
    return 1
def f(c):
    c :: __main__.C
    r0, r1 :: float
    r2 :: bit
    r3 :: float
    r4 :: object
    r5 :: float
L0:
    r0 = c.x
    r1 = c.y
    r2 = r1 == -113.0
    if r2 goto L2 else goto L1 :: bool
L1:
    r3 = r0 + r1
    return r3
L2:
    r4 = PyErr_Occurred()
    if not is_error(r4) goto L3 (error at f:8) else goto L1
L3:
    r5 = <error> :: float
    return r5
