# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-docstring
# ruff: noqa: E501, F841

from tvm_ffi.access_path import AccessPath

import tvm
import tvm.testing
from tvm import IRModule, relax, tirx
from tvm.runtime.script_printer import PrinterConfig, _script
from tvm.script import ir as I
from tvm.script import relax as R
from tvm.script import tirx as T


def _assert_print(obj, expected):
    if not isinstance(obj, str):
        obj = obj.script(verbose_expr=True)
    obj = obj.strip()
    # compare line by line in case there is trailing whitespace in the _middle_
    for obj_line, expected_line in zip(obj.splitlines(), expected.strip().splitlines()):
        assert obj_line.strip() == expected_line.strip(), "\n" + obj


def test_function():
    @R.function
    def func(a: R.Tensor((10, 10))) -> R.Tensor((10, 10)):  # type: ignore
        R.func_attr({"some_attr": 1})
        return a

    _assert_print(
        func,
        """
# from tvm.script import relax as R

@R.function
def func(a: R.Tensor((10, 10))) -> R.Tensor((10, 10)):
    R.func_attr({"some_attr": 1})
    return a""",
    )


def test_function_dependent_shape_escaped_source_spans():
    n = tirx.Var("n", "int64")
    cast = tirx.Cast("int64", n)
    x = relax.Var("x", relax.TensorType([cast], "float32"))
    ret_ty = relax.TensorType(dtype="float32", ndim=1)
    func = relax.Function([x], x, ret_ty=ret_ty).with_attr("global_symbol", "main")
    cast_path = (
        AccessPath.root()
        .attr("params")
        .array_item(0)
        .attr("ty")
        .attr("shape")
        .attr("values")
        .array_item(0)
    )

    def render(path):
        config = PrinterConfig(
            verbose_expr=True,
            num_context_lines=0,
            path_to_underline=[path],
            extra_config={"render_invisible_path_info": True},
        )
        first = _script(func, config)
        assert _script(func, config) == first
        assert first.count("Access path:") == 1
        lines = first.splitlines()
        definition_index = next(i for i, line in enumerate(lines) if "def main" in line)
        assert "Access path:" not in lines[definition_index]
        return lines[definition_index], lines[definition_index + 1]

    expression = r'"T.Cast(\"int64\", n)"'
    definition, underline = render(cast_path)
    expression_start = definition.index(expression)
    assert underline[expression_start : expression_start + len(expression)] == "^" * len(expression)
    assert underline.strip() == "^" * len(expression)

    escaped_dtype = r"\"int64\""
    definition, underline = render(cast_path.attr("dtype"))
    dtype_start = definition.index(escaped_dtype)
    assert underline[dtype_start : dtype_start + len(escaped_dtype)] == "^" * len(escaped_dtype)
    assert underline.strip() == "^" * len(escaped_dtype)

    definition, underline = render(cast_path.attr("value"))
    variable_start = definition.index(expression) + expression.rindex("n")
    assert underline[variable_start] == "^"
    assert underline.strip() == "^"


def test_lone_private_function():
    @R.function(private=True)
    def func(a: R.Tensor((10, 10))) -> R.Tensor((10, 10)):  # type: ignore
        R.func_attr({"some_attr": 1})
        return a

    # name prints as main because without a global symbol, the printer cannot assume a name
    _assert_print(
        func,
        """
# from tvm.script import relax as R

@R.function(private=True)
def main(a: R.Tensor((10, 10))) -> R.Tensor((10, 10)):
    R.func_attr({"some_attr": 1})
    return a""",
    )


def test_extern_func():
    @R.function
    def func(a: R.Tensor((10, 10))) -> R.Tensor((10, 10)):  # type: ignore
        return a

    obj = IRModule(
        {
            "func": func,
            "my_ext": relax.ExternFunc("my_ext"),
        }
    )
    _assert_print(
        obj,
        """
# from tvm.script import ir as I
# from tvm.script import relax as R

@I.ir_module
class Module:
    my_ext = R.ExternFunc("my_ext")
    @R.function
    def func(a: R.Tensor((10, 10))) -> R.Tensor((10, 10)):
        return a
""",
    )


def test_extern_func_with_ty():
    obj = IRModule(
        {
            "my_ext": relax.ExternFunc(
                "my_ext",
                relax.FuncType([], relax.TensorType(dtype="float32", ndim=2), purity=True),
            ),
        }
    )
    _assert_print(
        obj,
        """
# from tvm.script import ir as I
# from tvm.script import relax as R

@I.ir_module
class Module:
    my_ext = R.ExternFunc("my_ext", R.Callable((), R.Tensor(dtype="float32", ndim=2), True))
""",
    )


def test_extern_func_with_ty_roundtrip():
    mod = IRModule(
        {
            "my_ext": relax.ExternFunc(
                "my_ext",
                relax.FuncType([], relax.TensorType(dtype="float32", ndim=2), purity=True),
            ),
        }
    )
    roundtrip = tvm.script.from_source(mod.script(verbose_expr=True))
    tvm.ir.assert_structural_equal(mod, roundtrip)


def test_nested_function():
    @I.ir_module(s_tir=True)
    class NestedFunction:
        @R.function
        def main(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
            @R.function
            def nested(y: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
                return y

            z = nested(x)
            return z

    _assert_print(
        NestedFunction,
        """
# from tvm.script import ir as I
# from tvm.script import relax as R

@I.ir_module
class Module:
    @R.function
    def main(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
        # from tvm.script import relax as R

        @R.function
        def nested(y: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
            return y

        z: R.Tensor((), dtype="int32") = nested(x)
        return z
""",
    )


def test_object_ty():
    obj = relax.AnyType()
    _assert_print(
        obj,
        "R.Any",
    )


def test_prim_ty():
    obj = tvm.ir.PrimType("float32")
    _assert_print(obj, "T.float32")


def test_shape_ty_0():
    obj = relax.ShapeType(ndim=-1)
    _assert_print(obj, "R.Shape(ndim=-1)")


def test_shape_ty_1():
    obj = relax.ShapeType([1, 2, 3])
    _assert_print(obj, "R.Shape([1, 2, 3])")


def test_shape_ty_2():
    obj = relax.ShapeType([1, tirx.Var("a", "int64"), 3])
    _assert_print(
        obj,
        """
a = T.int64()
R.Shape([1, a, 3])""",
    )


def test_tensor_ty():
    obj = relax.TensorType(
        shape=relax.ShapeExpr([1, tirx.Var("a", "int64"), 3]),
        dtype="float32",
    )
    _assert_print(
        obj,
        """
a = T.int64()
R.Tensor((1, a, 3), dtype="float32")
""",
    )


def test_tuple_ty_empty():
    obj = relax.TupleType([])
    _assert_print(obj._relax_script(), "R.Tuple")  # pylint: disable=protected-access


def test_tuple_ty():
    obj = relax.TupleType(
        [
            tvm.ir.PrimType("float32"),
            relax.AnyType(),
            relax.ShapeType([1, tirx.Var("a", "int64"), 3]),
        ]
    )
    _assert_print(
        obj._relax_script(),  # pylint: disable=protected-access
        """
R.Tuple(T.float32, R.Any, R.Shape([1, a, 3]))
""",
    )


def test_func_ty():
    obj = relax.FuncType(
        params=[
            tvm.ir.PrimType("float32"),
            relax.AnyType(),
            relax.ShapeType([1, tirx.Var("a", "int64"), 3]),
            tvm.ir.PrimType("int64"),
        ],
        ret=relax.TensorType(
            shape=relax.ShapeExpr([1, 2, 3]),
            dtype="float32",
        ),
    )
    _assert_print(
        obj,
        "a = T.int64()\n"
        "R.Callable((T.float32, R.Any, R.Shape([1, a, 3]), T.int64), "
        'R.Tensor((1, 2, 3), dtype="float32"), True)',
    )


def test_shape_type():
    obj = relax.ShapeType(ndim=3)
    _assert_print(obj, "R.Shape(ndim=3)")


def test_object_type():
    obj = relax.AnyType()
    _assert_print(obj, "R.Any")


def test_dyn_tensor_type():
    obj = relax.TensorType()
    _assert_print(obj, 'R.Tensor(dtype="float32")')


def test_packed_func_type():
    obj = relax.PackedFuncType()
    _assert_print(obj, "R.PackedFunc")


def test_tuple_type():
    obj = relax.TupleType([relax.ShapeType(ndim=3), relax.AnyType()])
    _assert_print(
        obj._relax_script(),  # pylint: disable=protected-access
        "R.Tuple(R.Shape(ndim=3), R.Any)",
    )


def test_func_type():
    obj = relax.FuncType(
        params=[
            relax.AnyType(),
            relax.ShapeType(ndim=3),
        ],
        ret=relax.TensorType(
            ndim=3,
            dtype="float32",
        ),
    )
    _assert_print(
        obj._relax_script(),  # pylint: disable=protected-access
        'R.Callable((R.Any, R.Shape(ndim=3)), R.Tensor(dtype="float32", ndim=3), True)',
    )


def test_prim_value():
    obj = tirx.IntImm("int64", 1)
    _assert_print(obj, "T.int64(1)")

    @R.function
    def func() -> R.Prim("int64"):
        return R.prim_value(1)

    _assert_print(
        func,
        """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
# from tvm.script import relax as R

@R.function
def func() -> T.int64:
    return 1""",
    )

    @R.function
    def float_func() -> R.Prim("float32"):
        return R.prim_value(T.float32(1.0))

    float_script = float_func.script(verbose_expr=True)
    assert "R.prim_value" not in float_script
    assert "return T.float32(" in float_script
    tvm.ir.assert_structural_equal(tvm.script.from_source(float_script), float_func)


def test_string_imm():
    obj = relax.StringImm("hello")
    _assert_print(obj, 'R.str("hello")')


def test_data_type_imm():
    obj = relax.DataTypeImm("float32")
    _assert_print(obj, 'R.dtype("float32")')


def test_var():
    obj = relax.Var("a", relax.TensorType([1, tirx.Var("x", "int64"), 3], "float32"))
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
a""",
    )


def test_dataflow_var():
    obj = relax.DataflowVar("a", relax.TensorType([1, tirx.Var("x", "int64"), 3], "float32"))
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
a""",
    )


def test_tuple():
    obj = relax.Tuple(
        [
            relax.Var("a", relax.TensorType([1, tirx.Var("x", "int64"), 3], "float32")),
            relax.Var("b", relax.TensorType([1, tirx.Var("y", "int64"), 3], "float32")),
            relax.Var("c", relax.TensorType([1, tirx.Var("z", "int64"), 3], "float32")),
        ]
    )
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
y = T.int64()
b: R.Tensor((1, y, 3), dtype="float32")
z = T.int64()
c: R.Tensor((1, z, 3), dtype="float32")
(a, b, c)
""",
    )


def test_tuple_get_item():
    obj = relax.TupleGetItem(
        relax.Tuple(
            [
                relax.Var("a", relax.TensorType([1, tirx.Var("x", "int64"), 3], "float32")),
                relax.Var("b", relax.TensorType([1, tirx.Var("y", "int64"), 3], "float32")),
                relax.Var("c", relax.TensorType([1, tirx.Var("z", "int64"), 3], "float32")),
            ]
        ),
        0,
    )
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
y = T.int64()
b: R.Tensor((1, y, 3), dtype="float32")
z = T.int64()
c: R.Tensor((1, z, 3), dtype="float32")
(a, b, c)[0]
""",
    )


def test_shape_expr():
    obj = relax.ShapeExpr([1, 2, 3])
    _assert_print(obj, "R.shape([1, 2, 3])")


def test_call():
    x = tirx.Var("x", "int64")
    a = relax.Var("a", relax.TensorType([1, x, 3], "float32"))
    o0 = relax.call_tir(relax.GlobalVar("tir_func"), args=a, out_ty=a.ty, tir_vars=[x])
    o1 = relax.call_dps_packed("my_dps_func", args=a, out_ty=a.ty)
    _assert_print(
        o0,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
R.call_tir(tir_func, (a,), out_ty=R.Tensor((1, x, 3), dtype="float32"), tir_vars=R.shape([x]))
""",
    )
    _assert_print(
        o1,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
R.call_dps_packed("my_dps_func", (a,), out_ty=R.Tensor((1, x, 3), dtype="float32"))
""",
    )


def test_call_tir_with_grad():
    x = tirx.Var("x", "int64")
    v0 = relax.Var("v0", R.Tensor([54, 96], "float32"))
    v1 = relax.call_tir_with_grad(
        relax.GlobalVar("tir_func"),
        (v0,),
        R.Tensor((54, 96), "float32"),
        te_grad_name="grad_func",
        te_grad_kwargs={"k": 1.0, "x": x},
    )

    _assert_print(
        v1,
        """
v0: R.Tensor((54, 96), dtype="float32")
x = T.int64()
R.call_tir_with_grad(tir_func, (v0,), out_ty=R.Tensor((54, 96), dtype="float32"), te_grad_name="grad_func", te_grad_kwargs={"k": 1.0, "x": x})
""",
    )


def test_call_tir_inplace():
    x = relax.Var("x", R.Tensor((32, 32), dtype="int32"))
    y = relax.Var("y", R.Tensor((32, 32), dtype="int32"))
    t = tirx.Var("t", dtype="int64")
    call = relax.call_tir_inplace(
        relax.GlobalVar("tir_func"),
        (
            x,
            y,
        ),
        inplace_indices=[-1, 0],
        out_ty=[R.Tensor((32, 32), dtype="int32"), R.Tensor((32, 32), dtype="int32")],
        tir_vars=[t],
    )
    _assert_print(
        call,
        """
x: R.Tensor((32, 32), dtype="int32")
y: R.Tensor((32, 32), dtype="int32")
t = T.int64()
R.call_tir_inplace(tir_func, (x, y), out_ty=[R.Tensor((32, 32), dtype="int32"), R.Tensor((32, 32), dtype="int32")], inplace_indices=[-1, 0], tir_vars=R.shape([t]))
        """,
    )


def test_seq_expr():
    x = tirx.Var("x", "int64")
    a = relax.Var("a", relax.TensorType([1, x, 3], "float32"))
    b = relax.DataflowVar("b", relax.TensorType([1, x, 3], "float32"))
    c = relax.Var("c", relax.TensorType([1, x, 3], "float32"))

    obj = relax.SeqExpr(
        blocks=[
            relax.DataflowBlock(
                bindings=[
                    relax.VarBinding(b, relax.op.sin(a)),
                    relax.VarBinding(c, relax.op.sin(b)),
                ]
            ),
        ],
        body=c,
    )
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
with R.dataflow():
    b: R.Tensor((1, x, 3), dtype="float32") = R.sin(a)
    c: R.Tensor((1, x, 3), dtype="float32") = R.sin(b)
    R.output(c)
c
""",
    )


def test_binding_block():
    x = tirx.Var("x", "int64")
    a = relax.Var("a", relax.TensorType([1, x, 3], "float32"))
    b = relax.Var("b", relax.TensorType([1, x, 3], "float32"))
    c = relax.Var("c", relax.TensorType([1, x, 3], "float32"))
    obj = relax.BindingBlock(
        bindings=[
            relax.VarBinding(b, relax.op.sin(a)),
            relax.VarBinding(c, relax.op.sin(b)),
        ]
    )
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
b: R.Tensor((1, x, 3), dtype="float32") = R.sin(a)
c: R.Tensor((1, x, 3), dtype="float32") = R.sin(b)
""",
    )


def test_dataflow_block():
    x = tirx.Var("x", "int64")
    a = relax.Var("a", relax.TensorType([1, x, 3], "float32"))
    b = relax.DataflowVar("b", relax.TensorType([1, x, 3], "float32"))
    c = relax.Var("c", relax.TensorType([1, x, 3], "float32"))
    obj = relax.DataflowBlock(
        bindings=[
            relax.VarBinding(b, relax.op.sin(a)),
            relax.VarBinding(c, relax.op.sin(b)),
        ]
    )
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
with R.dataflow():
    b: R.Tensor((1, x, 3), dtype="float32") = R.sin(a)
    c: R.Tensor((1, x, 3), dtype="float32") = R.sin(b)
    R.output(c)
""",
    )


def test_match_cast():
    x = tirx.Var("x", "int64")
    a = relax.Var("a", relax.TensorType([1, x, 3]))
    b = relax.Var("b", relax.TensorType([1, 5, 3]))
    obj = relax.MatchCast(
        var=b,
        value=a,
        ty=b.ty,
    )
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
b: R.Tensor((1, 5, 3), dtype="float32") = R.match_cast(a, R.Tensor((1, 5, 3), dtype="float32"))
""",
    )


def test_var_binding():
    x = tirx.Var("x", "int64")
    a = relax.Var("a", relax.TensorType([1, x, 3], "float32"))
    b = relax.Var("b", relax.TensorType([1, x, 3], "float32"))
    obj = relax.VarBinding(b, relax.op.sin(a))
    _assert_print(
        obj,
        """
x = T.int64()
a: R.Tensor((1, x, 3), dtype="float32")
b: R.Tensor((1, x, 3), dtype="float32") = R.sin(a)
""",
    )


def test_if():
    a = relax.Var("a", relax.TensorType([], "bool"))
    b = relax.Var("b", relax.TensorType([1, 2, 3], "float32"))
    c = relax.Var("c", relax.TensorType([1, 2, 3], "float32"))
    obj = relax.If(
        a,
        relax.SeqExpr([], b),
        relax.SeqExpr([], c),
    )
    _assert_print(
        obj,
        """
a: R.Tensor((), dtype="bool")
if a:
    b: R.Tensor((1, 2, 3), dtype="float32")
    b
else:
    c: R.Tensor((1, 2, 3), dtype="float32")
    c
""",
    )


def test_builtin_keywords():
    x = tirx.Var("x", "int64")
    a = relax.Var("R", relax.TensorType([1, x, 3], "float32"))
    b = relax.Var("T", relax.TensorType([1, x, 3], "float32"))
    obj = relax.VarBinding(b, relax.op.sin(a))
    _assert_print(
        obj,
        """
x = T.int64()
R_1: R.Tensor((1, x, 3), dtype="float32")
T_1: R.Tensor((1, x, 3), dtype="float32") = R.sin(R_1)
""",
    )


def test_module_cross_func_call():
    @I.ir_module(s_tir=True)
    class TestModule:
        @T.prim_func(s_tir=True)
        def tir_func(
            x: T.Buffer((T.int64(128),), "float32"), y: T.Buffer((T.int64(128),), "float32")
        ):
            T.evaluate(0)

        @R.function
        def foo(x: R.Tensor((128,), "float32")) -> R.Tensor((128,), "float32"):
            cls = TestModule
            gv0 = R.call_tir(cls.tir_func, x, R.Tensor((128,), dtype="float32"))
            return gv0

    # default behavior
    _assert_print(
        TestModule,
        """
# from tvm.script import ir as I
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
# from tvm.script import relax as R

@I.ir_module
class Module:
    @T.prim_func(s_tir=True)
    def tir_func(x: T.Buffer((T.int64(128),), "float32"), y: T.Buffer((T.int64(128),), "float32")):
        T.evaluate(0)

    @R.function
    def foo(x: R.Tensor((128,), dtype="float32")) -> R.Tensor((128,), dtype="float32"):
        cls = Module
        gv0 = R.call_tir(cls.tir_func, (x,), out_ty=R.Tensor((128,), dtype="float32"))
        return gv0
""",
    )

    # empty module alias
    module_str = TestModule.script(module_alias="")
    _assert_print(
        module_str,
        """
# from tvm.script import ir as I
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
# from tvm.script import relax as R

@I.ir_module
class Module:
    @T.prim_func(s_tir=True)
    def tir_func(x: T.Buffer((T.int64(128),), "float32"), y: T.Buffer((T.int64(128),), "float32")):
        T.evaluate(0)

    @R.function
    def foo(x: R.Tensor((128,), dtype="float32")) -> R.Tensor((128,), dtype="float32"):
        gv0 = R.call_tir(Module.tir_func, (x,), out_ty=R.Tensor((128,), dtype="float32"))
        return gv0
""",
    )


def test_assert_op():
    @I.ir_module(s_tir=True)
    class AssertOpMod:
        @R.function(pure=False)
        def main(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
            y = R.assert_op(R.const(False, dtype="bool"), x, format="x: {}")
            return x

    _assert_print(
        AssertOpMod,
        """
# from tvm.script import ir as I
# from tvm.script import relax as R

@I.ir_module
class Module:
    @R.function(pure=False)
    def main(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
        R.assert_op(R.const(False, "bool"), x, format=R.str("x: {}"))
        return x
""",
    )


def test_print():
    @I.ir_module(s_tir=True)
    class PrintMod:
        @R.function(pure=False)
        def main(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
            y = R.print(x, format="x: {}")
            return x

    _assert_print(
        PrintMod,
        """
# from tvm.script import ir as I
# from tvm.script import relax as R

@I.ir_module
class Module:
    @R.function(pure=False)
    def main(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
        R.print(x, format=R.str("x: {}"))
        return x
""",
    )


def test_private_function():
    @I.ir_module(s_tir=True)
    class AddMod:
        @R.function(private=True)
        def main(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
            y: R.Tensor((), dtype="int32") = R.add(x, x)
            return y

    _assert_print(
        AddMod,
        """
# from tvm.script import ir as I
# from tvm.script import relax as R

@I.ir_module
class Module:
    @R.function(private=True)
    def main(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
        y: R.Tensor((), dtype="int32") = R.add(x, x)
        return y
""",
    )


def test_directly_construct_private_funcs():
    # public
    @R.function
    def foo(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
        y: R.Tensor((), dtype="int32") = R.add(x, x)
        return y

    # private
    @R.function(private=True)
    def bar(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
        y: R.Tensor((), dtype="int32") = R.multiply(x, x)
        return y

    # public but there's another attribute
    @R.function
    def baz(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
        R.func_attr({"relax.force_pure": True})
        y: R.Tuple = R.print(format="Hi there!")
        z: R.Tensor((), dtype="int32") = R.add(x, x)
        return z

    # private with an attribute
    @R.function(private=True)
    def quux(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
        R.func_attr({"relax.force_pure": True})
        y: R.Tuple = R.print(format="Lol")
        z: R.Tensor((), dtype="int32") = R.multiply(x, x)
        return z

    obj = IRModule(
        {
            "foo": foo,
            "bar": bar,
            "baz": baz,
            "quux": quux,
        }
    )
    _assert_print(
        obj,
        """
# from tvm.script import ir as I
# from tvm.script import relax as R

@I.ir_module
class Module:
    @R.function(private=True)
    def bar(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
        y: R.Tensor((), dtype="int32") = R.multiply(x, x)
        return y

    @R.function
    def baz(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
        R.func_attr({"relax.force_pure": True})
        R.print(format=R.str("Hi there!"))
        z: R.Tensor((), dtype="int32") = R.add(x, x)
        return z

    @R.function
    def foo(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
        y: R.Tensor((), dtype="int32") = R.add(x, x)
        return y

    @R.function(private=True)
    def quux(x: R.Tensor((), dtype="int32")) -> R.Tensor((), dtype="int32"):
        R.func_attr({"relax.force_pure": True})
        R.print(format=R.str("Lol"))
        z: R.Tensor((), dtype="int32") = R.multiply(x, x)
        return z
""",
    )


def test_reused_extern_func():
    """An ExternFunc used in a variable binding should be explicit"""

    @R.function
    def func(x: R.Tensor((128, 128), dtype="float32")) -> R.Tensor((128, 128), dtype="float32"):
        extern_func = R.ExternFunc("extern_func")
        y = R.call_dps_packed(extern_func, (x,), out_ty=R.Tensor((128, 128), dtype="float32"))
        z = R.call_dps_packed(extern_func, (y,), out_ty=R.Tensor((128, 128), dtype="float32"))
        return z

    _assert_print(
        func,
        """
# from tvm.script import relax as R

@R.function
def func(x: R.Tensor((128, 128), dtype="float32")) -> R.Tensor((128, 128), dtype="float32"):
    extern_func: R.Callable = R.ExternFunc("extern_func")
    y = R.call_dps_packed(extern_func, (x,), out_ty=R.Tensor((128, 128), dtype="float32"))
    z = R.call_dps_packed(extern_func, (y,), out_ty=R.Tensor((128, 128), dtype="float32"))
    return z
                  """,
    )


def test_inline_extern_func():
    """An ExternFunc used in-line may be printed as a string"""

    @R.function
    def func(x: R.Tensor((128, 128), dtype="float32")) -> R.Tensor((128, 128), dtype="float32"):
        y = R.call_dps_packed(
            R.ExternFunc("extern_func"), (x,), out_ty=R.Tensor((128, 128), dtype="float32")
        )
        z = R.call_dps_packed(
            R.ExternFunc("extern_func"), (y,), out_ty=R.Tensor((128, 128), dtype="float32")
        )
        return z

    _assert_print(
        func,
        """
# from tvm.script import relax as R

@R.function
def func(x: R.Tensor((128, 128), dtype="float32")) -> R.Tensor((128, 128), dtype="float32"):
    y = R.call_dps_packed("extern_func", (x,), out_ty=R.Tensor((128, 128), dtype="float32"))
    z = R.call_dps_packed("extern_func", (y,), out_ty=R.Tensor((128, 128), dtype="float32"))
    return z
                  """,
    )


def test_hide_inferable_ty():
    """Redundant type annotations can be omitted

    When `show_all_ty=False`, TVMScript type annotations that
    provide redundant type can be omitted.
    """

    @R.function
    def func(A: R.Tensor([10, 20], "float32"), B: R.Tensor(ndim=2, dtype="float32")):
        # R.match_cast has the type as an argument, so it can
        # be omitted from the variable annotation.
        B2 = R.match_cast(B, R.Tensor([10, 20], "float32"))

        # Call nodes may have inferable shapes from their arguments.
        C = R.add(A, B2)

        # Trivial bindings can be inferred to have the same struct
        # info as the RHS.
        D = C

        # Here, the type cannot be omitted.  `R.add(D,B)` has
        # type `R.Tensor(ndim=2)`, but the variable has a shape
        # `R.Tensor([10,20])`.  This is compatible, so it is not an
        # error to have this annotation, but it is not inferrable from
        # the RHS.  Therefore, it must still be printed.
        E: R.Tensor([10, 20], "float32") = R.add(D, B)

        # The return type can be inferred from function body, but is
        # still always printed in the TVMScript.  When parsing an
        # IRModule with functions calling each other, the return type
        # of each callee must be available for use in the caller's
        # shape inference.
        return E

    _assert_print(
        func.script(show_all_ty=False),
        """
# from tvm.script import relax as R

@R.function
def func(A: R.Tensor((10, 20), dtype="float32"), B: R.Tensor(dtype="float32", ndim=2)) -> R.Tensor((10, 20), dtype="float32"):
    B2 = R.match_cast(B, R.Tensor((10, 20), dtype="float32"))
    C = R.add(A, B2)
    D = C
    E: R.Tensor((10, 20), dtype="float32") = R.add(D, B)
    return E""",
    )


if __name__ == "__main__":
    tvm.testing.main()
