[case testIsNone]
from typing import Optional

class A: pass

def f(x: Optional[A]) -> int:
    if x is None:
        return 1
    return 2
[out]
def f(x):
    x :: union[__main__.A, None]
    r0 :: object
    r1 :: bit
L0:
    r0 = load_address _Py_NoneStruct
    r1 = x == r0
    if r1 goto L1 else goto L2 :: bool
L1:
    return 2
L2:
    return 4

[case testIsNotNone]
from typing import Optional

class A: pass

def f(x: Optional[A]) -> int:
    if x is not None:
        return 1
    return 2
[out]
def f(x):
    x :: union[__main__.A, None]
    r0 :: object
    r1 :: bit
L0:
    r0 = load_address _Py_NoneStruct
    r1 = x != r0
    if r1 goto L1 else goto L2 :: bool
L1:
    return 2
L2:
    return 4

[case testIsTruthy]
from typing import Optional

class A: pass

def f(x: Optional[A]) -> int:
    if x:
        return 1
    return 2
[out]
def f(x):
    x :: union[__main__.A, None]
    r0 :: object
    r1 :: bit
L0:
    r0 = load_address _Py_NoneStruct
    r1 = x != r0
    if r1 goto L1 else goto L2 :: bool
L1:
    return 2
L2:
    return 4

[case testIsTruthyOverride]
from typing import Optional

class A: pass

class B(A):
    def __bool__(self) -> bool:
        return False


def f(x: Optional[A]) -> int:
    if x:
        return 1
    return 2
[out]
def B.__bool__(self):
    self :: __main__.B
L0:
    return 0
def f(x):
    x :: union[__main__.A, None]
    r0 :: object
    r1 :: bit
    r2 :: __main__.A
    r3 :: i32
    r4 :: bit
    r5 :: bool
L0:
    r0 = load_address _Py_NoneStruct
    r1 = x != r0
    if r1 goto L1 else goto L3 :: bool
L1:
    r2 = cast(__main__.A, x)
    r3 = PyObject_IsTrue(r2)
    r4 = r3 >= 0 :: signed
    r5 = truncate r3: i32 to builtins.bool
    if r5 goto L2 else goto L3 :: bool
L2:
    return 2
L3:
    return 4

[case testAssignToOptional]
from typing import Optional

class A:
    a: Optional[int]

def f(x: Optional[A], y: Optional[A], z: Optional[int]) -> None:
    x = None
    x = A()
    x = y
    z = 1
    a = A()
    a.a = 1
    a.a = None
[out]
def f(x, y, z):
    x, y :: union[__main__.A, None]
    z :: union[int, None]
    r0 :: object
    r1 :: __main__.A
    r2 :: object
    r3, a :: __main__.A
    r4 :: object
    r5 :: bool
    r6 :: object
    r7 :: bool
L0:
    r0 = box(None, 1)
    x = r0
    r1 = A()
    x = r1
    x = y
    r2 = object 1
    z = r2
    r3 = A()
    a = r3
    r4 = object 1
    a.a = r4; r5 = is_error
    r6 = box(None, 1)
    a.a = r6; r7 = is_error
    return 1

[case testBoxOptionalListItem]
from typing import List, Optional

def f(x: List[Optional[int]]) -> None:
    x[0] = 0
    x[1] = None
[out]
def f(x):
    x :: list
    r0 :: object
    r1 :: bit
    r2 :: object
    r3 :: bit
L0:
    r0 = object 0
    r1 = CPyList_SetItem(x, 0, r0)
    r2 = box(None, 1)
    r3 = CPyList_SetItem(x, 2, r2)
    return 1

[case testNarrowDownFromOptional]
from typing import Optional

class A: pass

def f(x: Optional[A]) -> A:
    y = A()
    if x is not None:
        y = x
        return x
    return y
[out]
def f(x):
    x :: union[__main__.A, None]
    r0, y :: __main__.A
    r1 :: object
    r2 :: bit
    r3, r4 :: __main__.A
L0:
    r0 = A()
    y = r0
    r1 = load_address _Py_NoneStruct
    r2 = x != r1
    if r2 goto L1 else goto L2 :: bool
L1:
    r3 = cast(__main__.A, x)
    y = r3
    r4 = cast(__main__.A, x)
    return r4
L2:
    return y

[case testPartialOptionalType]
def f(y: int) -> None:
    x = None
    if y == 1:
        x = y
    if x is not None:
        y = x
[out]
def f(y):
    y :: int
    r0 :: object
    x :: union[int, None]
    r1 :: bit
    r2, r3 :: object
    r4 :: bit
    r5 :: int
L0:
    r0 = box(None, 1)
    x = r0
    r1 = y == 2
    if r1 goto L1 else goto L2 :: bool
L1:
    r2 = box(int, y)
    x = r2
L2:
    r3 = load_address _Py_NoneStruct
    r4 = x != r3
    if r4 goto L3 else goto L4 :: bool
L3:
    r5 = unbox(int, x)
    y = r5
L4:
    return 1

[case testUnionType]
from typing import Union

class A:
    a: int

def f(x: Union[int, A]) -> int:
    if isinstance(x, int):
        return x + 1
    else:
        return x.a
[out]
def f(x):
    x :: union[int, __main__.A]
    r0 :: object
    r1 :: i32
    r2 :: bit
    r3 :: bool
    r4, r5 :: int
    r6 :: __main__.A
    r7 :: int
L0:
    r0 = load_address PyLong_Type
    r1 = PyObject_IsInstance(x, r0)
    r2 = r1 >= 0 :: signed
    r3 = truncate r1: i32 to builtins.bool
    if r3 goto L1 else goto L2 :: bool
L1:
    r4 = unbox(int, x)
    r5 = CPyTagged_Add(r4, 2)
    return r5
L2:
    r6 = borrow cast(__main__.A, x)
    r7 = r6.a
    keep_alive x
    return r7
L3:
    unreachable

[case testUnionTypeInList]
from typing import List, Union

def f(x: List[Union[int, str]]) -> object:
    return x[0]
[out]
def f(x):
    x :: list
    r0 :: object
    r1 :: union[int, str]
L0:
    r0 = CPyList_GetItemShort(x, 0)
    r1 = cast(union[int, str], r0)
    return r1

[case testUnionAttributeAccess]
from typing import Union
class A:
    a: int
class B:
    a: object
def get(o: Union[A, B]) -> None:
    z = o.a
def set(o: Union[A, B], s: str) -> None:
    o.a = s

[out]
def get(o):
    o :: union[__main__.A, __main__.B]
    r0 :: object
    r1 :: ptr
    r2 :: object
    r3 :: bit
    r4 :: __main__.A
    r5 :: int
    r6, r7 :: object
    r8 :: __main__.B
    r9, z :: object
L0:
    r0 = __main__.A :: type
    r1 = get_element_ptr o ob_type :: PyObject
    r2 = load_mem r1 :: builtins.object*
    keep_alive o
    r3 = r2 == r0
    if r3 goto L1 else goto L2 :: bool
L1:
    r4 = cast(__main__.A, o)
    r5 = r4.a
    r6 = box(int, r5)
    r7 = r6
    goto L3
L2:
    r8 = cast(__main__.B, o)
    r9 = r8.a
    r7 = r9
L3:
    z = r7
    return 1
def set(o, s):
    o :: union[__main__.A, __main__.B]
    s, r0 :: str
    r1 :: i32
    r2 :: bit
L0:
    r0 = 'a'
    r1 = PyObject_SetAttr(o, r0, s)
    r2 = r1 >= 0 :: signed
    return 1

[case testUnionMethodCall]
from typing import Union
class A:
    def f(self, x: int) -> int:
        return x
class B:
    def f(self, x: object) -> object:
        return x
class C:
    def f(self, x: object) -> int:
        return 0
def g(o: Union[A, B, C]) -> None:
    z = o.f(1)

[out]
def A.f(self, x):
    self :: __main__.A
    x :: int
L0:
    return x
def B.f(self, x):
    self :: __main__.B
    x :: object
L0:
    return x
def C.f(self, x):
    self :: __main__.C
    x :: object
L0:
    return 0
def g(o):
    o :: union[__main__.A, __main__.B, __main__.C]
    r0 :: object
    r1 :: ptr
    r2 :: object
    r3 :: bit
    r4 :: __main__.A
    r5 :: int
    r6, r7, r8 :: object
    r9 :: ptr
    r10 :: object
    r11 :: bit
    r12 :: __main__.B
    r13, r14 :: object
    r15 :: __main__.C
    r16 :: object
    r17 :: int
    r18, z :: object
L0:
    r0 = __main__.A :: type
    r1 = get_element_ptr o ob_type :: PyObject
    r2 = load_mem r1 :: builtins.object*
    keep_alive o
    r3 = r2 == r0
    if r3 goto L1 else goto L2 :: bool
L1:
    r4 = cast(__main__.A, o)
    r5 = r4.f(2)
    r6 = box(int, r5)
    r7 = r6
    goto L5
L2:
    r8 = __main__.B :: type
    r9 = get_element_ptr o ob_type :: PyObject
    r10 = load_mem r9 :: builtins.object*
    keep_alive o
    r11 = r10 == r8
    if r11 goto L3 else goto L4 :: bool
L3:
    r12 = cast(__main__.B, o)
    r13 = object 1
    r14 = r12.f(r13)
    r7 = r14
    goto L5
L4:
    r15 = cast(__main__.C, o)
    r16 = object 1
    r17 = r15.f(r16)
    r18 = box(int, r17)
    r7 = r18
L5:
    z = r7
    return 1

[case testUnionWithNonNativeItem]
from typing import Union
from m import B

class A:
    x: int

def f(o: Union[A, B]) -> None:
    o.x

def g(o: Union[B, A]) -> None:
    o.x

[file m.py]
class B:
    x: int

[out]
def f(o):
    o :: union[__main__.A, object]
    r0 :: object
    r1 :: ptr
    r2 :: object
    r3 :: bit
    r4 :: __main__.A
    r5, r6 :: int
    r7 :: object
    r8 :: str
    r9 :: object
    r10 :: int
L0:
    r0 = __main__.A :: type
    r1 = get_element_ptr o ob_type :: PyObject
    r2 = load_mem r1 :: builtins.object*
    keep_alive o
    r3 = r2 == r0
    if r3 goto L1 else goto L2 :: bool
L1:
    r4 = cast(__main__.A, o)
    r5 = r4.x
    r6 = r5
    goto L3
L2:
    r7 = o
    r8 = 'x'
    r9 = CPyObject_GetAttr(r7, r8)
    r10 = unbox(int, r9)
    r6 = r10
L3:
    return 1
def g(o):
    o :: union[object, __main__.A]
    r0 :: object
    r1 :: ptr
    r2 :: object
    r3 :: bit
    r4 :: __main__.A
    r5, r6 :: int
    r7 :: object
    r8 :: str
    r9 :: object
    r10 :: int
L0:
    r0 = __main__.A :: type
    r1 = get_element_ptr o ob_type :: PyObject
    r2 = load_mem r1 :: builtins.object*
    keep_alive o
    r3 = r2 == r0
    if r3 goto L1 else goto L2 :: bool
L1:
    r4 = cast(__main__.A, o)
    r5 = r4.x
    r6 = r5
    goto L3
L2:
    r7 = o
    r8 = 'x'
    r9 = CPyObject_GetAttr(r7, r8)
    r10 = unbox(int, r9)
    r6 = r10
L3:
    return 1

[case testUnionWithNoNativeItems]
from typing import Union
from m import A, B

def f(o: Union[A, B]) -> None:
    o.x

[file m.py]
class A:
    x: object
class B:
    x: int

[out]
def f(o):
    o :: object
    r0 :: str
    r1 :: object
L0:
    r0 = 'x'
    r1 = CPyObject_GetAttr(o, r0)
    return 1
