-- Test cases for copy propagation optimization.  This also tests IR transforms in general,
-- as copy propagation was the first IR transform that was implemented.

[case testCopyPropagationSimple]
def g() -> int:
    return 1

def f() -> int:
    y = g()
    return y
[out]
def g():
L0:
    return 2
def f():
    r0 :: int
L0:
    r0 = g()
    return r0

[case testCopyPropagationChain]
def f(x: int) -> int:
    y = x
    z = y
    return z
[out]
def f(x):
    x :: int
L0:
    return x

[case testCopyPropagationChainPartial]
def f(x: int) -> int:
    y = x
    z = y
    x = 2
    return z
[out]
def f(x):
    x, y :: int
L0:
    y = x
    x = 4
    return y

[case testCopyPropagationChainBad]
def f(x: int) -> int:
    y = x
    z = y
    y = 2
    return z
[out]
def f(x):
    x, y, z :: int
L0:
    y = x
    z = y
    y = 4
    return z

[case testCopyPropagationMutatedSource1]
def f(x: int) -> int:
    y = x
    x = 1
    return y
[out]
def f(x):
    x, y :: int
L0:
    y = x
    x = 2
    return y

[case testCopyPropagationMutatedSource2]
def f() -> int:
    z = 1
    y = z
    z = 2
    return y
[out]
def f():
    z, y :: int
L0:
    z = 2
    y = z
    z = 4
    return y

[case testCopyPropagationTooComplex]
def f(b: bool, x: int) -> int:
    if b:
        y = x
        return y
    else:
        y = 1
        return y
[out]
def f(b, x):
    b :: bool
    x, y :: int
L0:
    if b goto L1 else goto L2 :: bool
L1:
    y = x
    return y
L2:
    y = 2
    return y

[case testCopyPropagationArg]
def f(x: int) -> int:
    x = 2
    return x
[out]
def f(x):
    x :: int
L0:
    x = 4
    return x

[case testCopyPropagationPartiallyDefined1]
def f(b: bool) -> int:
    if b:
        x = 1
    y = x
    return y
[out]
def f(b):
    b :: bool
    r0, x :: int
    r1 :: bool
    y :: int
L0:
    r0 = <error> :: int
    x = r0
    if b goto L1 else goto L2 :: bool
L1:
    x = 2
L2:
    if is_error(x) goto L3 else goto L4
L3:
    r1 = raise UnboundLocalError('local variable "x" referenced before assignment')
    unreachable
L4:
    y = x
    return y

-- The remaining test cases test basic IRTransform functionality and are not
-- all needed for testing copy propagation as such.

[case testIRTransformBranch]
from mypy_extensions import i64

def f(x: bool) -> int:
    y = x
    if y:
        return 1
    else:
        return 2
[out]
def f(x):
    x :: bool
L0:
    if x goto L1 else goto L2 :: bool
L1:
    return 2
L2:
    return 4

[case testIRTransformAssignment]
def f(b: bool, x: int) -> int:
    y = x
    if b:
        return y
    else:
        return 1
[out]
def f(b, x):
    b :: bool
    x :: int
L0:
    if b goto L1 else goto L2 :: bool
L1:
    return x
L2:
    return 2

[case testIRTransformRegisterOps1]
from __future__ import annotations
from typing import cast

class C:
    a: int

    def m(self, x: int) -> None: pass

def get_attr(x: C) -> int:
    y = x
    return y.a

def set_attr(x: C) -> None:
    y = x
    y.a = 1

def tuple_get(x: tuple[int, int]) -> int:
    y = x
    return y[0]

def tuple_set(x: int, xx: int) -> tuple[int, int]:
    y = x
    z = xx
    return y, z

def call(x: int) -> int:
    y = x
    return call(y)

def method_call(c: C, x: int) -> None:
    y = x
    c.m(y)

def cast_op(x: object) -> str:
    y = x
    return cast(str, y)

def box(x: int) -> object:
    y = x
    return y

def unbox(x: object) -> int:
    y = x
    return cast(int, y)

def call_c(x: list[str]) -> None:
    y = x
    y.append("x")

def keep_alive(x: C) -> int:
    y = x
    return y.a + 1
[out]
def C.m(self, x):
    self :: __main__.C
    x :: int
L0:
    return 1
def get_attr(x):
    x :: __main__.C
    r0 :: int
L0:
    r0 = x.a
    return r0
def set_attr(x):
    x :: __main__.C
    r0 :: bool
L0:
    x.a = 2; r0 = is_error
    return 1
def tuple_get(x):
    x :: tuple[int, int]
    r0 :: int
L0:
    r0 = x[0]
    return r0
def tuple_set(x, xx):
    x, xx :: int
    r0 :: tuple[int, int]
L0:
    r0 = (x, xx)
    return r0
def call(x):
    x, r0 :: int
L0:
    r0 = call(x)
    return r0
def method_call(c, x):
    c :: __main__.C
    x :: int
    r0 :: None
L0:
    r0 = c.m(x)
    return 1
def cast_op(x):
    x :: object
    r0 :: str
L0:
    r0 = cast(str, x)
    return r0
def box(x):
    x :: int
    r0 :: object
L0:
    r0 = box(int, x)
    return r0
def unbox(x):
    x :: object
    r0 :: int
L0:
    r0 = unbox(int, x)
    return r0
def call_c(x):
    x :: list
    r0 :: str
    r1 :: i32
    r2 :: bit
L0:
    r0 = 'x'
    r1 = PyList_Append(x, r0)
    r2 = r1 >= 0 :: signed
    return 1
def keep_alive(x):
    x :: __main__.C
    r0, r1 :: int
L0:
    r0 = borrow x.a
    r1 = CPyTagged_Add(r0, 2)
    keep_alive x
    return r1

[case testIRTransformRegisterOps2]
from mypy_extensions import i32, i64

def truncate(x: i64) -> i32:
    y = x
    return i32(y)

def extend(x: i32) -> i64:
    y = x
    return i64(y)

def int_op(x: i64, xx: i64) -> i64:
    y = x
    z = xx
    return y + z

def comparison_op(x: i64, xx: i64) -> bool:
    y = x
    z = xx
    return y == z

def float_op(x: float, xx: float) -> float:
    y = x
    z = xx
    return y + z

def float_neg(x: float) -> float:
    y = x
    return -y

def float_comparison_op(x: float, xx: float) -> bool:
    y = x
    z = xx
    return y == z
[out]
def truncate(x):
    x :: i64
    r0 :: i32
L0:
    r0 = truncate x: i64 to i32
    return r0
def extend(x):
    x :: i32
    r0 :: i64
L0:
    r0 = extend signed x: i32 to i64
    return r0
def int_op(x, xx):
    x, xx, r0 :: i64
L0:
    r0 = x + xx
    return r0
def comparison_op(x, xx):
    x, xx :: i64
    r0 :: bit
L0:
    r0 = x == xx
    return r0
def float_op(x, xx):
    x, xx, r0 :: float
L0:
    r0 = x + xx
    return r0
def float_neg(x):
    x, r0 :: float
L0:
    r0 = -x
    return r0
def float_comparison_op(x, xx):
    x, xx :: float
    r0 :: bit
L0:
    r0 = x == xx
    return r0

-- Note that transforms of these ops aren't tested here:
-- * LoadMem
-- * SetMem
-- * GetElementPtr
-- * LoadAddress
-- * Unborrow
