| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- from __future__ import annotations
- import unittest
- from mypyc.analysis.ircheck import FnError, can_coerce_to, check_func_ir
- from mypyc.ir.class_ir import ClassIR
- from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature
- from mypyc.ir.ops import (
- Assign,
- BasicBlock,
- Goto,
- Integer,
- LoadAddress,
- LoadLiteral,
- Op,
- Register,
- Return,
- )
- from mypyc.ir.pprint import format_func
- from mypyc.ir.rtypes import (
- RInstance,
- RType,
- RUnion,
- bytes_rprimitive,
- int32_rprimitive,
- int64_rprimitive,
- none_rprimitive,
- object_rprimitive,
- pointer_rprimitive,
- str_rprimitive,
- )
- def assert_has_error(fn: FuncIR, error: FnError) -> None:
- errors = check_func_ir(fn)
- assert errors == [error]
- def assert_no_errors(fn: FuncIR) -> None:
- assert not check_func_ir(fn)
- NONE_VALUE = Integer(0, rtype=none_rprimitive)
- class TestIrcheck(unittest.TestCase):
- def setUp(self) -> None:
- self.label = 0
- def basic_block(self, ops: list[Op]) -> BasicBlock:
- self.label += 1
- block = BasicBlock(self.label)
- block.ops = ops
- return block
- def func_decl(self, name: str, ret_type: RType | None = None) -> FuncDecl:
- if ret_type is None:
- ret_type = none_rprimitive
- return FuncDecl(
- name=name,
- class_name=None,
- module_name="module",
- sig=FuncSignature(args=[], ret_type=ret_type),
- )
- def test_valid_fn(self) -> None:
- assert_no_errors(
- FuncIR(
- decl=self.func_decl(name="func_1"),
- arg_regs=[],
- blocks=[self.basic_block(ops=[Return(value=NONE_VALUE)])],
- )
- )
- def test_block_not_terminated_empty_block(self) -> None:
- block = self.basic_block([])
- fn = FuncIR(decl=self.func_decl(name="func_1"), arg_regs=[], blocks=[block])
- assert_has_error(fn, FnError(source=block, desc="Block not terminated"))
- def test_valid_goto(self) -> None:
- block_1 = self.basic_block([Return(value=NONE_VALUE)])
- block_2 = self.basic_block([Goto(label=block_1)])
- fn = FuncIR(decl=self.func_decl(name="func_1"), arg_regs=[], blocks=[block_1, block_2])
- assert_no_errors(fn)
- def test_invalid_goto(self) -> None:
- block_1 = self.basic_block([Return(value=NONE_VALUE)])
- goto = Goto(label=block_1)
- block_2 = self.basic_block([goto])
- fn = FuncIR(
- decl=self.func_decl(name="func_1"),
- arg_regs=[],
- # block_1 omitted
- blocks=[block_2],
- )
- assert_has_error(fn, FnError(source=goto, desc="Invalid control operation target: 1"))
- def test_invalid_register_source(self) -> None:
- ret = Return(value=Register(type=none_rprimitive, name="r1"))
- block = self.basic_block([ret])
- fn = FuncIR(decl=self.func_decl(name="func_1"), arg_regs=[], blocks=[block])
- assert_has_error(fn, FnError(source=ret, desc="Invalid op reference to register 'r1'"))
- def test_invalid_op_source(self) -> None:
- ret = Return(value=LoadLiteral(value="foo", rtype=str_rprimitive))
- block = self.basic_block([ret])
- fn = FuncIR(decl=self.func_decl(name="func_1"), arg_regs=[], blocks=[block])
- assert_has_error(
- fn, FnError(source=ret, desc="Invalid op reference to op of type LoadLiteral")
- )
- def test_invalid_return_type(self) -> None:
- ret = Return(value=Integer(value=5, rtype=int32_rprimitive))
- fn = FuncIR(
- decl=self.func_decl(name="func_1", ret_type=int64_rprimitive),
- arg_regs=[],
- blocks=[self.basic_block([ret])],
- )
- assert_has_error(
- fn, FnError(source=ret, desc="Cannot coerce source type i32 to dest type i64")
- )
- def test_invalid_assign(self) -> None:
- arg_reg = Register(type=int64_rprimitive, name="r1")
- assign = Assign(dest=arg_reg, src=Integer(value=5, rtype=int32_rprimitive))
- ret = Return(value=NONE_VALUE)
- fn = FuncIR(
- decl=self.func_decl(name="func_1"),
- arg_regs=[arg_reg],
- blocks=[self.basic_block([assign, ret])],
- )
- assert_has_error(
- fn, FnError(source=assign, desc="Cannot coerce source type i32 to dest type i64")
- )
- def test_can_coerce_to(self) -> None:
- cls = ClassIR(name="Cls", module_name="cls")
- valid_cases = [
- (int64_rprimitive, int64_rprimitive),
- (str_rprimitive, str_rprimitive),
- (str_rprimitive, object_rprimitive),
- (object_rprimitive, str_rprimitive),
- (RUnion([bytes_rprimitive, str_rprimitive]), str_rprimitive),
- (str_rprimitive, RUnion([bytes_rprimitive, str_rprimitive])),
- (RInstance(cls), object_rprimitive),
- ]
- invalid_cases = [
- (int64_rprimitive, int32_rprimitive),
- (RInstance(cls), str_rprimitive),
- (str_rprimitive, bytes_rprimitive),
- ]
- for src, dest in valid_cases:
- assert can_coerce_to(src, dest)
- for src, dest in invalid_cases:
- assert not can_coerce_to(src, dest)
- def test_duplicate_op(self) -> None:
- arg_reg = Register(type=int32_rprimitive, name="r1")
- assign = Assign(dest=arg_reg, src=Integer(value=5, rtype=int32_rprimitive))
- block = self.basic_block([assign, assign, Return(value=NONE_VALUE)])
- fn = FuncIR(decl=self.func_decl(name="func_1"), arg_regs=[], blocks=[block])
- assert_has_error(fn, FnError(source=assign, desc="Func has a duplicate op"))
- def test_pprint(self) -> None:
- block_1 = self.basic_block([Return(value=NONE_VALUE)])
- goto = Goto(label=block_1)
- block_2 = self.basic_block([goto])
- fn = FuncIR(
- decl=self.func_decl(name="func_1"),
- arg_regs=[],
- # block_1 omitted
- blocks=[block_2],
- )
- errors = [(goto, "Invalid control operation target: 1")]
- formatted = format_func(fn, errors)
- assert formatted == [
- "def func_1():",
- "L0:",
- " goto L1",
- " ERR: Invalid control operation target: 1",
- ]
- def test_load_address_declares_register(self) -> None:
- rx = Register(str_rprimitive, "x")
- ry = Register(pointer_rprimitive, "y")
- load_addr = LoadAddress(pointer_rprimitive, rx)
- assert_no_errors(
- FuncIR(
- decl=self.func_decl(name="func_1"),
- arg_regs=[],
- blocks=[
- self.basic_block(
- ops=[load_addr, Assign(ry, load_addr), Return(value=NONE_VALUE)]
- )
- ],
- )
- )
|