Major fixes and new features
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
13
venv/lib/python3.12/site-packages/mypyc/test/config.py
Normal file
13
venv/lib/python3.12/site-packages/mypyc/test/config.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
provided_prefix = os.getenv("MYPY_TEST_PREFIX", None)
|
||||
if provided_prefix:
|
||||
PREFIX = provided_prefix
|
||||
else:
|
||||
this_file_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
PREFIX = os.path.dirname(os.path.dirname(this_file_dir))
|
||||
|
||||
# Location of test data files such as test case descriptions.
|
||||
test_data_prefix = os.path.join(PREFIX, "mypyc", "test-data")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Test cases for inferring always defined attributes in classes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
|
||||
from mypy.errors import CompileError
|
||||
from mypy.test.config import test_temp_dir
|
||||
from mypy.test.data import DataDrivenTestCase
|
||||
from mypyc.test.testutil import (
|
||||
ICODE_GEN_BUILTINS,
|
||||
MypycDataSuite,
|
||||
assert_test_output,
|
||||
build_ir_for_single_file2,
|
||||
infer_ir_build_options_from_test_name,
|
||||
use_custom_builtins,
|
||||
)
|
||||
|
||||
files = ["alwaysdefined.test"]
|
||||
|
||||
|
||||
class TestAlwaysDefined(MypycDataSuite):
|
||||
files = files
|
||||
base_path = test_temp_dir
|
||||
|
||||
def run_case(self, testcase: DataDrivenTestCase) -> None:
|
||||
"""Perform a runtime checking transformation test case."""
|
||||
options = infer_ir_build_options_from_test_name(testcase.name)
|
||||
if options is None:
|
||||
# Skipped test case
|
||||
return
|
||||
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
|
||||
try:
|
||||
ir = build_ir_for_single_file2(testcase.input, options)
|
||||
except CompileError as e:
|
||||
actual = e.messages
|
||||
else:
|
||||
actual = []
|
||||
for cl in ir.classes:
|
||||
if cl.name.startswith("_"):
|
||||
continue
|
||||
actual.append(
|
||||
"{}: [{}]".format(cl.name, ", ".join(sorted(cl._always_initialized_attrs)))
|
||||
)
|
||||
|
||||
assert_test_output(testcase, actual, "Invalid test output", testcase.output)
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Test runner for data-flow analysis test cases."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
|
||||
from mypy.errors import CompileError
|
||||
from mypy.test.config import test_temp_dir
|
||||
from mypy.test.data import DataDrivenTestCase
|
||||
from mypyc.analysis import dataflow
|
||||
from mypyc.common import TOP_LEVEL_NAME
|
||||
from mypyc.ir.func_ir import all_values
|
||||
from mypyc.ir.ops import Value
|
||||
from mypyc.ir.pprint import format_func, generate_names_for_ir
|
||||
from mypyc.test.testutil import (
|
||||
ICODE_GEN_BUILTINS,
|
||||
MypycDataSuite,
|
||||
assert_test_output,
|
||||
build_ir_for_single_file,
|
||||
use_custom_builtins,
|
||||
)
|
||||
from mypyc.transform import exceptions
|
||||
|
||||
files = ["analysis.test"]
|
||||
|
||||
|
||||
class TestAnalysis(MypycDataSuite):
|
||||
files = files
|
||||
base_path = test_temp_dir
|
||||
optional_out = True
|
||||
|
||||
def run_case(self, testcase: DataDrivenTestCase) -> None:
|
||||
"""Perform a data-flow analysis test case."""
|
||||
|
||||
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
|
||||
try:
|
||||
ir = build_ir_for_single_file(testcase.input)
|
||||
except CompileError as e:
|
||||
actual = e.messages
|
||||
else:
|
||||
actual = []
|
||||
for fn in ir:
|
||||
if fn.name == TOP_LEVEL_NAME and not testcase.name.endswith("_toplevel"):
|
||||
continue
|
||||
exceptions.insert_exception_handling(fn)
|
||||
actual.extend(format_func(fn))
|
||||
cfg = dataflow.get_cfg(fn.blocks)
|
||||
args: set[Value] = set(fn.arg_regs)
|
||||
name = testcase.name
|
||||
if name.endswith("_MaybeDefined"):
|
||||
# Forward, maybe
|
||||
analysis_result = dataflow.analyze_maybe_defined_regs(fn.blocks, cfg, args)
|
||||
elif name.endswith("_Liveness"):
|
||||
# Backward, maybe
|
||||
analysis_result = dataflow.analyze_live_regs(fn.blocks, cfg)
|
||||
elif name.endswith("_MustDefined"):
|
||||
# Forward, must
|
||||
analysis_result = dataflow.analyze_must_defined_regs(
|
||||
fn.blocks, cfg, args, regs=all_values(fn.arg_regs, fn.blocks)
|
||||
)
|
||||
elif name.endswith("_BorrowedArgument"):
|
||||
# Forward, must
|
||||
analysis_result = dataflow.analyze_borrowed_arguments(fn.blocks, cfg, args)
|
||||
else:
|
||||
assert False, "No recognized _AnalysisName suffix in test case"
|
||||
|
||||
names = generate_names_for_ir(fn.arg_regs, fn.blocks)
|
||||
|
||||
for key in sorted(
|
||||
analysis_result.before.keys(), key=lambda x: (x[0].label, x[1])
|
||||
):
|
||||
pre = ", ".join(sorted(names[reg] for reg in analysis_result.before[key]))
|
||||
post = ", ".join(sorted(names[reg] for reg in analysis_result.after[key]))
|
||||
actual.append(
|
||||
"%-8s %-23s %s" % ((key[0].label, key[1]), "{%s}" % pre, "{%s}" % post)
|
||||
)
|
||||
assert_test_output(testcase, actual, "Invalid source code output")
|
||||
45
venv/lib/python3.12/site-packages/mypyc/test/test_cheader.py
Normal file
45
venv/lib/python3.12/site-packages/mypyc/test/test_cheader.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""Test that C functions used in primitives are declared in a header such as CPy.h."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from mypyc.primitives import registry
|
||||
from mypyc.primitives.registry import CFunctionDescription
|
||||
|
||||
|
||||
class TestHeaderInclusion(unittest.TestCase):
|
||||
def test_primitives_included_in_header(self) -> None:
|
||||
base_dir = os.path.join(os.path.dirname(__file__), "..", "lib-rt")
|
||||
with open(os.path.join(base_dir, "CPy.h")) as f:
|
||||
header = f.read()
|
||||
with open(os.path.join(base_dir, "pythonsupport.h")) as f:
|
||||
header += f.read()
|
||||
|
||||
def check_name(name: str) -> None:
|
||||
if name.startswith("CPy"):
|
||||
assert re.search(
|
||||
rf"\b{name}\b", header
|
||||
), f'"{name}" is used in mypyc.primitives but not declared in CPy.h'
|
||||
|
||||
for values in [
|
||||
registry.method_call_ops.values(),
|
||||
registry.function_ops.values(),
|
||||
registry.binary_ops.values(),
|
||||
registry.unary_ops.values(),
|
||||
]:
|
||||
for ops in values:
|
||||
if isinstance(ops, CFunctionDescription):
|
||||
ops = [ops]
|
||||
for op in ops:
|
||||
check_name(op.c_function_name)
|
||||
|
||||
primitives_path = os.path.join(os.path.dirname(__file__), "..", "primitives")
|
||||
for fnam in glob.glob(f"{primitives_path}/*.py"):
|
||||
with open(fnam) as f:
|
||||
content = f.read()
|
||||
for name in re.findall(r'c_function_name=["\'](CPy[A-Z_a-z0-9]+)', content):
|
||||
check_name(name)
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Test cases for invoking mypyc on the command line.
|
||||
|
||||
These are slow -- do not add test cases unless you have a very good reason to do so.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from mypy.test.config import test_temp_dir
|
||||
from mypy.test.data import DataDrivenTestCase
|
||||
from mypy.test.helpers import normalize_error_messages
|
||||
from mypyc.test.testutil import MypycDataSuite, assert_test_output
|
||||
|
||||
files = ["commandline.test"]
|
||||
|
||||
|
||||
base_path = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
|
||||
python3_path = sys.executable
|
||||
|
||||
|
||||
class TestCommandLine(MypycDataSuite):
|
||||
files = files
|
||||
base_path = test_temp_dir
|
||||
optional_out = True
|
||||
|
||||
def run_case(self, testcase: DataDrivenTestCase) -> None:
|
||||
# Parse options from test case description (arguments must not have spaces)
|
||||
text = "\n".join(testcase.input)
|
||||
m = re.search(r"# *cmd: *(.*)", text)
|
||||
assert m is not None, 'Test case missing "# cmd: <files>" section'
|
||||
args = m.group(1).split()
|
||||
|
||||
# Write main program to run (not compiled)
|
||||
program = "_%s.py" % testcase.name
|
||||
program_path = os.path.join(test_temp_dir, program)
|
||||
with open(program_path, "w") as f:
|
||||
f.write(text)
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = base_path
|
||||
|
||||
out = b""
|
||||
try:
|
||||
# Compile program
|
||||
cmd = subprocess.run(
|
||||
[sys.executable, "-m", "mypyc", *args],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd="tmp",
|
||||
env=env,
|
||||
)
|
||||
if "ErrorOutput" in testcase.name or cmd.returncode != 0:
|
||||
out += cmd.stdout
|
||||
elif "WarningOutput" in testcase.name:
|
||||
# Strip out setuptools build related output since we're only
|
||||
# interested in the messages emitted during compilation.
|
||||
messages, _, _ = cmd.stdout.partition(b"running build_ext")
|
||||
out += messages
|
||||
|
||||
if cmd.returncode == 0:
|
||||
# Run main program
|
||||
out += subprocess.check_output([python3_path, program], cwd="tmp")
|
||||
finally:
|
||||
suffix = "pyd" if sys.platform == "win32" else "so"
|
||||
so_paths = glob.glob(f"tmp/**/*.{suffix}", recursive=True)
|
||||
for path in so_paths:
|
||||
os.remove(path)
|
||||
|
||||
# Strip out 'tmp/' from error message paths in the testcase output,
|
||||
# due to a mismatch between this test and mypy's test suite.
|
||||
expected = [x.replace("tmp/", "") for x in testcase.output]
|
||||
|
||||
# Verify output
|
||||
actual = normalize_error_messages(out.decode().splitlines())
|
||||
assert_test_output(testcase, actual, "Invalid output", expected=expected)
|
||||
69
venv/lib/python3.12/site-packages/mypyc/test/test_emit.py
Normal file
69
venv/lib/python3.12/site-packages/mypyc/test/test_emit.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.codegen.emit import Emitter, EmitterContext
|
||||
from mypyc.ir.ops import BasicBlock, Register, Value
|
||||
from mypyc.ir.rtypes import RTuple, bool_rprimitive, int_rprimitive, str_rprimitive
|
||||
from mypyc.namegen import NameGenerator
|
||||
|
||||
|
||||
class TestEmitter(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.n = Register(int_rprimitive, "n")
|
||||
self.context = EmitterContext(NameGenerator([["mod"]]))
|
||||
|
||||
def test_label(self) -> None:
|
||||
emitter = Emitter(self.context, {})
|
||||
assert emitter.label(BasicBlock(4)) == "CPyL4"
|
||||
|
||||
def test_reg(self) -> None:
|
||||
names: dict[Value, str] = {self.n: "n"}
|
||||
emitter = Emitter(self.context, names)
|
||||
assert emitter.reg(self.n) == "cpy_r_n"
|
||||
|
||||
def test_object_annotation(self) -> None:
|
||||
emitter = Emitter(self.context, {})
|
||||
assert emitter.object_annotation("hello, world", "line;") == " /* 'hello, world' */"
|
||||
assert (
|
||||
emitter.object_annotation(list(range(30)), "line;")
|
||||
== """\
|
||||
/* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 24, 25, 26, 27, 28, 29] */"""
|
||||
)
|
||||
|
||||
def test_emit_line(self) -> None:
|
||||
emitter = Emitter(self.context, {})
|
||||
emitter.emit_line("line;")
|
||||
emitter.emit_line("a {")
|
||||
emitter.emit_line("f();")
|
||||
emitter.emit_line("}")
|
||||
assert emitter.fragments == ["line;\n", "a {\n", " f();\n", "}\n"]
|
||||
emitter = Emitter(self.context, {})
|
||||
emitter.emit_line("CPyStatics[0];", ann="hello, world")
|
||||
emitter.emit_line("CPyStatics[1];", ann=list(range(30)))
|
||||
assert emitter.fragments[0] == "CPyStatics[0]; /* 'hello, world' */\n"
|
||||
assert (
|
||||
emitter.fragments[1]
|
||||
== """\
|
||||
CPyStatics[1]; /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 25, 26, 27, 28, 29] */\n"""
|
||||
)
|
||||
|
||||
def test_emit_undefined_value_for_simple_type(self) -> None:
|
||||
emitter = Emitter(self.context, {})
|
||||
assert emitter.c_undefined_value(int_rprimitive) == "CPY_INT_TAG"
|
||||
assert emitter.c_undefined_value(str_rprimitive) == "NULL"
|
||||
assert emitter.c_undefined_value(bool_rprimitive) == "2"
|
||||
|
||||
def test_emit_undefined_value_for_tuple(self) -> None:
|
||||
emitter = Emitter(self.context, {})
|
||||
assert (
|
||||
emitter.c_undefined_value(RTuple([str_rprimitive, int_rprimitive, bool_rprimitive]))
|
||||
== "(tuple_T3OIC) { NULL, CPY_INT_TAG, 2 }"
|
||||
)
|
||||
assert emitter.c_undefined_value(RTuple([str_rprimitive])) == "(tuple_T1O) { NULL }"
|
||||
assert (
|
||||
emitter.c_undefined_value(RTuple([RTuple([str_rprimitive]), bool_rprimitive]))
|
||||
== "(tuple_T2T1OC) { { NULL }, 2 }"
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.codegen.emitclass import getter_name, setter_name, slot_key
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.namegen import NameGenerator
|
||||
|
||||
|
||||
class TestEmitClass(unittest.TestCase):
|
||||
def test_slot_key(self) -> None:
|
||||
attrs = ["__add__", "__radd__", "__rshift__", "__rrshift__", "__setitem__", "__delitem__"]
|
||||
s = sorted(attrs, key=lambda x: slot_key(x))
|
||||
# __delitem__ and reverse methods should come last.
|
||||
assert s == [
|
||||
"__add__",
|
||||
"__rshift__",
|
||||
"__setitem__",
|
||||
"__delitem__",
|
||||
"__radd__",
|
||||
"__rrshift__",
|
||||
]
|
||||
|
||||
def test_setter_name(self) -> None:
|
||||
cls = ClassIR(module_name="testing", name="SomeClass")
|
||||
generator = NameGenerator([["mod"]])
|
||||
|
||||
# This should never be `setup`, as it will conflict with the class `setup`
|
||||
assert setter_name(cls, "up", generator) == "testing___SomeClass_set_up"
|
||||
|
||||
def test_getter_name(self) -> None:
|
||||
cls = ClassIR(module_name="testing", name="SomeClass")
|
||||
generator = NameGenerator([["mod"]])
|
||||
|
||||
assert getter_name(cls, "down", generator) == "testing___SomeClass_get_down"
|
||||
926
venv/lib/python3.12/site-packages/mypyc/test/test_emitfunc.py
Normal file
926
venv/lib/python3.12/site-packages/mypyc/test/test_emitfunc.py
Normal file
@@ -0,0 +1,926 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypy.test.helpers import assert_string_arrays_equal
|
||||
from mypyc.codegen.emit import Emitter, EmitterContext
|
||||
from mypyc.codegen.emitfunc import FunctionEmitterVisitor, generate_native_function
|
||||
from mypyc.common import PLATFORM_SIZE
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature, RuntimeArg
|
||||
from mypyc.ir.ops import (
|
||||
ERR_NEVER,
|
||||
Assign,
|
||||
AssignMulti,
|
||||
BasicBlock,
|
||||
Box,
|
||||
Branch,
|
||||
Call,
|
||||
CallC,
|
||||
Cast,
|
||||
ComparisonOp,
|
||||
DecRef,
|
||||
Extend,
|
||||
GetAttr,
|
||||
GetElementPtr,
|
||||
Goto,
|
||||
IncRef,
|
||||
Integer,
|
||||
IntOp,
|
||||
LoadAddress,
|
||||
LoadMem,
|
||||
Op,
|
||||
Register,
|
||||
Return,
|
||||
SetAttr,
|
||||
SetMem,
|
||||
TupleGet,
|
||||
Unbox,
|
||||
Unreachable,
|
||||
Value,
|
||||
)
|
||||
from mypyc.ir.pprint import generate_names_for_ir
|
||||
from mypyc.ir.rtypes import (
|
||||
RArray,
|
||||
RInstance,
|
||||
RStruct,
|
||||
RTuple,
|
||||
RType,
|
||||
bool_rprimitive,
|
||||
c_int_rprimitive,
|
||||
dict_rprimitive,
|
||||
int32_rprimitive,
|
||||
int64_rprimitive,
|
||||
int_rprimitive,
|
||||
list_rprimitive,
|
||||
object_rprimitive,
|
||||
pointer_rprimitive,
|
||||
short_int_rprimitive,
|
||||
)
|
||||
from mypyc.irbuild.vtable import compute_vtable
|
||||
from mypyc.namegen import NameGenerator
|
||||
from mypyc.primitives.dict_ops import (
|
||||
dict_get_item_op,
|
||||
dict_new_op,
|
||||
dict_set_item_op,
|
||||
dict_update_op,
|
||||
)
|
||||
from mypyc.primitives.int_ops import int_neg_op
|
||||
from mypyc.primitives.list_ops import list_append_op, list_get_item_op, list_set_item_op
|
||||
from mypyc.primitives.misc_ops import none_object_op
|
||||
from mypyc.primitives.registry import binary_ops
|
||||
from mypyc.subtype import is_subtype
|
||||
|
||||
|
||||
class TestFunctionEmitterVisitor(unittest.TestCase):
|
||||
"""Test generation of fragments of C from individual IR ops."""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.registers: list[Register] = []
|
||||
|
||||
def add_local(name: str, rtype: RType) -> Register:
|
||||
reg = Register(rtype, name)
|
||||
self.registers.append(reg)
|
||||
return reg
|
||||
|
||||
self.n = add_local("n", int_rprimitive)
|
||||
self.m = add_local("m", int_rprimitive)
|
||||
self.k = add_local("k", int_rprimitive)
|
||||
self.l = add_local("l", list_rprimitive)
|
||||
self.ll = add_local("ll", list_rprimitive)
|
||||
self.o = add_local("o", object_rprimitive)
|
||||
self.o2 = add_local("o2", object_rprimitive)
|
||||
self.d = add_local("d", dict_rprimitive)
|
||||
self.b = add_local("b", bool_rprimitive)
|
||||
self.s1 = add_local("s1", short_int_rprimitive)
|
||||
self.s2 = add_local("s2", short_int_rprimitive)
|
||||
self.i32 = add_local("i32", int32_rprimitive)
|
||||
self.i32_1 = add_local("i32_1", int32_rprimitive)
|
||||
self.i64 = add_local("i64", int64_rprimitive)
|
||||
self.i64_1 = add_local("i64_1", int64_rprimitive)
|
||||
self.ptr = add_local("ptr", pointer_rprimitive)
|
||||
self.t = add_local("t", RTuple([int_rprimitive, bool_rprimitive]))
|
||||
self.tt = add_local(
|
||||
"tt", RTuple([RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive])
|
||||
)
|
||||
ir = ClassIR("A", "mod")
|
||||
ir.attributes = {
|
||||
"x": bool_rprimitive,
|
||||
"y": int_rprimitive,
|
||||
"i1": int64_rprimitive,
|
||||
"i2": int32_rprimitive,
|
||||
}
|
||||
ir.bitmap_attrs = ["i1", "i2"]
|
||||
compute_vtable(ir)
|
||||
ir.mro = [ir]
|
||||
self.r = add_local("r", RInstance(ir))
|
||||
|
||||
self.context = EmitterContext(NameGenerator([["mod"]]))
|
||||
|
||||
def test_goto(self) -> None:
|
||||
self.assert_emit(Goto(BasicBlock(2)), "goto CPyL2;")
|
||||
|
||||
def test_goto_next_block(self) -> None:
|
||||
next_block = BasicBlock(2)
|
||||
self.assert_emit(Goto(next_block), "", next_block=next_block)
|
||||
|
||||
def test_return(self) -> None:
|
||||
self.assert_emit(Return(self.m), "return cpy_r_m;")
|
||||
|
||||
def test_integer(self) -> None:
|
||||
self.assert_emit(Assign(self.n, Integer(5)), "cpy_r_n = 10;")
|
||||
self.assert_emit(Assign(self.i32, Integer(5, c_int_rprimitive)), "cpy_r_i32 = 5;")
|
||||
|
||||
def test_tuple_get(self) -> None:
|
||||
self.assert_emit(TupleGet(self.t, 1, 0), "cpy_r_r0 = cpy_r_t.f1;")
|
||||
|
||||
def test_load_None(self) -> None:
|
||||
self.assert_emit(
|
||||
LoadAddress(none_object_op.type, none_object_op.src, 0),
|
||||
"cpy_r_r0 = (PyObject *)&_Py_NoneStruct;",
|
||||
)
|
||||
|
||||
def test_assign_int(self) -> None:
|
||||
self.assert_emit(Assign(self.m, self.n), "cpy_r_m = cpy_r_n;")
|
||||
|
||||
def test_int_add(self) -> None:
|
||||
self.assert_emit_binary_op(
|
||||
"+", self.n, self.m, self.k, "cpy_r_r0 = CPyTagged_Add(cpy_r_m, cpy_r_k);"
|
||||
)
|
||||
|
||||
def test_int_sub(self) -> None:
|
||||
self.assert_emit_binary_op(
|
||||
"-", self.n, self.m, self.k, "cpy_r_r0 = CPyTagged_Subtract(cpy_r_m, cpy_r_k);"
|
||||
)
|
||||
|
||||
def test_int_neg(self) -> None:
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
int_neg_op.c_function_name,
|
||||
[self.m],
|
||||
int_neg_op.return_type,
|
||||
int_neg_op.steals,
|
||||
int_neg_op.is_borrowed,
|
||||
int_neg_op.is_borrowed,
|
||||
int_neg_op.error_kind,
|
||||
55,
|
||||
),
|
||||
"cpy_r_r0 = CPyTagged_Negate(cpy_r_m);",
|
||||
)
|
||||
|
||||
def test_branch(self) -> None:
|
||||
self.assert_emit(
|
||||
Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL),
|
||||
"""if (cpy_r_b) {
|
||||
goto CPyL8;
|
||||
} else
|
||||
goto CPyL9;
|
||||
""",
|
||||
)
|
||||
b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL)
|
||||
b.negated = True
|
||||
self.assert_emit(
|
||||
b,
|
||||
"""if (!cpy_r_b) {
|
||||
goto CPyL8;
|
||||
} else
|
||||
goto CPyL9;
|
||||
""",
|
||||
)
|
||||
|
||||
def test_branch_no_else(self) -> None:
|
||||
next_block = BasicBlock(9)
|
||||
b = Branch(self.b, BasicBlock(8), next_block, Branch.BOOL)
|
||||
self.assert_emit(b, """if (cpy_r_b) goto CPyL8;""", next_block=next_block)
|
||||
next_block = BasicBlock(9)
|
||||
b = Branch(self.b, BasicBlock(8), next_block, Branch.BOOL)
|
||||
b.negated = True
|
||||
self.assert_emit(b, """if (!cpy_r_b) goto CPyL8;""", next_block=next_block)
|
||||
|
||||
def test_branch_no_else_negated(self) -> None:
|
||||
next_block = BasicBlock(1)
|
||||
b = Branch(self.b, next_block, BasicBlock(2), Branch.BOOL)
|
||||
self.assert_emit(b, """if (!cpy_r_b) goto CPyL2;""", next_block=next_block)
|
||||
next_block = BasicBlock(1)
|
||||
b = Branch(self.b, next_block, BasicBlock(2), Branch.BOOL)
|
||||
b.negated = True
|
||||
self.assert_emit(b, """if (cpy_r_b) goto CPyL2;""", next_block=next_block)
|
||||
|
||||
def test_branch_is_error(self) -> None:
|
||||
b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR)
|
||||
self.assert_emit(
|
||||
b,
|
||||
"""if (cpy_r_b == 2) {
|
||||
goto CPyL8;
|
||||
} else
|
||||
goto CPyL9;
|
||||
""",
|
||||
)
|
||||
b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR)
|
||||
b.negated = True
|
||||
self.assert_emit(
|
||||
b,
|
||||
"""if (cpy_r_b != 2) {
|
||||
goto CPyL8;
|
||||
} else
|
||||
goto CPyL9;
|
||||
""",
|
||||
)
|
||||
|
||||
def test_branch_is_error_next_block(self) -> None:
|
||||
next_block = BasicBlock(8)
|
||||
b = Branch(self.b, next_block, BasicBlock(9), Branch.IS_ERROR)
|
||||
self.assert_emit(b, """if (cpy_r_b != 2) goto CPyL9;""", next_block=next_block)
|
||||
b = Branch(self.b, next_block, BasicBlock(9), Branch.IS_ERROR)
|
||||
b.negated = True
|
||||
self.assert_emit(b, """if (cpy_r_b == 2) goto CPyL9;""", next_block=next_block)
|
||||
|
||||
def test_branch_rare(self) -> None:
|
||||
self.assert_emit(
|
||||
Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL, rare=True),
|
||||
"""if (unlikely(cpy_r_b)) {
|
||||
goto CPyL8;
|
||||
} else
|
||||
goto CPyL9;
|
||||
""",
|
||||
)
|
||||
next_block = BasicBlock(9)
|
||||
self.assert_emit(
|
||||
Branch(self.b, BasicBlock(8), next_block, Branch.BOOL, rare=True),
|
||||
"""if (unlikely(cpy_r_b)) goto CPyL8;""",
|
||||
next_block=next_block,
|
||||
)
|
||||
next_block = BasicBlock(8)
|
||||
b = Branch(self.b, next_block, BasicBlock(9), Branch.BOOL, rare=True)
|
||||
self.assert_emit(b, """if (likely(!cpy_r_b)) goto CPyL9;""", next_block=next_block)
|
||||
next_block = BasicBlock(8)
|
||||
b = Branch(self.b, next_block, BasicBlock(9), Branch.BOOL, rare=True)
|
||||
b.negated = True
|
||||
self.assert_emit(b, """if (likely(cpy_r_b)) goto CPyL9;""", next_block=next_block)
|
||||
|
||||
def test_call(self) -> None:
|
||||
decl = FuncDecl(
|
||||
"myfn", None, "mod", FuncSignature([RuntimeArg("m", int_rprimitive)], int_rprimitive)
|
||||
)
|
||||
self.assert_emit(Call(decl, [self.m], 55), "cpy_r_r0 = CPyDef_myfn(cpy_r_m);")
|
||||
|
||||
def test_call_two_args(self) -> None:
|
||||
decl = FuncDecl(
|
||||
"myfn",
|
||||
None,
|
||||
"mod",
|
||||
FuncSignature(
|
||||
[RuntimeArg("m", int_rprimitive), RuntimeArg("n", int_rprimitive)], int_rprimitive
|
||||
),
|
||||
)
|
||||
self.assert_emit(
|
||||
Call(decl, [self.m, self.k], 55), "cpy_r_r0 = CPyDef_myfn(cpy_r_m, cpy_r_k);"
|
||||
)
|
||||
|
||||
def test_inc_ref(self) -> None:
|
||||
self.assert_emit(IncRef(self.o), "CPy_INCREF(cpy_r_o);")
|
||||
self.assert_emit(IncRef(self.o), "CPy_INCREF(cpy_r_o);", rare=True)
|
||||
|
||||
def test_dec_ref(self) -> None:
|
||||
self.assert_emit(DecRef(self.o), "CPy_DECREF(cpy_r_o);")
|
||||
self.assert_emit(DecRef(self.o), "CPy_DecRef(cpy_r_o);", rare=True)
|
||||
|
||||
def test_inc_ref_int(self) -> None:
|
||||
self.assert_emit(IncRef(self.m), "CPyTagged_INCREF(cpy_r_m);")
|
||||
self.assert_emit(IncRef(self.m), "CPyTagged_IncRef(cpy_r_m);", rare=True)
|
||||
|
||||
def test_dec_ref_int(self) -> None:
|
||||
self.assert_emit(DecRef(self.m), "CPyTagged_DECREF(cpy_r_m);")
|
||||
self.assert_emit(DecRef(self.m), "CPyTagged_DecRef(cpy_r_m);", rare=True)
|
||||
|
||||
def test_dec_ref_tuple(self) -> None:
|
||||
self.assert_emit(DecRef(self.t), "CPyTagged_DECREF(cpy_r_t.f0);")
|
||||
|
||||
def test_dec_ref_tuple_nested(self) -> None:
|
||||
self.assert_emit(DecRef(self.tt), "CPyTagged_DECREF(cpy_r_tt.f0.f0);")
|
||||
|
||||
def test_list_get_item(self) -> None:
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
list_get_item_op.c_function_name,
|
||||
[self.m, self.k],
|
||||
list_get_item_op.return_type,
|
||||
list_get_item_op.steals,
|
||||
list_get_item_op.is_borrowed,
|
||||
list_get_item_op.error_kind,
|
||||
55,
|
||||
),
|
||||
"""cpy_r_r0 = CPyList_GetItem(cpy_r_m, cpy_r_k);""",
|
||||
)
|
||||
|
||||
def test_list_set_item(self) -> None:
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
list_set_item_op.c_function_name,
|
||||
[self.l, self.n, self.o],
|
||||
list_set_item_op.return_type,
|
||||
list_set_item_op.steals,
|
||||
list_set_item_op.is_borrowed,
|
||||
list_set_item_op.error_kind,
|
||||
55,
|
||||
),
|
||||
"""cpy_r_r0 = CPyList_SetItem(cpy_r_l, cpy_r_n, cpy_r_o);""",
|
||||
)
|
||||
|
||||
def test_box_int(self) -> None:
|
||||
self.assert_emit(Box(self.n), """cpy_r_r0 = CPyTagged_StealAsObject(cpy_r_n);""")
|
||||
|
||||
def test_unbox_int(self) -> None:
|
||||
self.assert_emit(
|
||||
Unbox(self.m, int_rprimitive, 55),
|
||||
"""if (likely(PyLong_Check(cpy_r_m)))
|
||||
cpy_r_r0 = CPyTagged_FromObject(cpy_r_m);
|
||||
else {
|
||||
CPy_TypeError("int", cpy_r_m); cpy_r_r0 = CPY_INT_TAG;
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
def test_box_i64(self) -> None:
|
||||
self.assert_emit(Box(self.i64), """cpy_r_r0 = PyLong_FromLongLong(cpy_r_i64);""")
|
||||
|
||||
def test_unbox_i64(self) -> None:
|
||||
self.assert_emit(
|
||||
Unbox(self.o, int64_rprimitive, 55), """cpy_r_r0 = CPyLong_AsInt64(cpy_r_o);"""
|
||||
)
|
||||
|
||||
def test_list_append(self) -> None:
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
list_append_op.c_function_name,
|
||||
[self.l, self.o],
|
||||
list_append_op.return_type,
|
||||
list_append_op.steals,
|
||||
list_append_op.is_borrowed,
|
||||
list_append_op.error_kind,
|
||||
1,
|
||||
),
|
||||
"""cpy_r_r0 = PyList_Append(cpy_r_l, cpy_r_o);""",
|
||||
)
|
||||
|
||||
def test_get_attr(self) -> None:
|
||||
self.assert_emit(
|
||||
GetAttr(self.r, "y", 1),
|
||||
"""cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_y;
|
||||
if (unlikely(cpy_r_r0 == CPY_INT_TAG)) {
|
||||
PyErr_SetString(PyExc_AttributeError, "attribute 'y' of 'A' undefined");
|
||||
} else {
|
||||
CPyTagged_INCREF(cpy_r_r0);
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
def test_get_attr_non_refcounted(self) -> None:
|
||||
self.assert_emit(
|
||||
GetAttr(self.r, "x", 1),
|
||||
"""cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_x;
|
||||
if (unlikely(cpy_r_r0 == 2)) {
|
||||
PyErr_SetString(PyExc_AttributeError, "attribute 'x' of 'A' undefined");
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
def test_get_attr_merged(self) -> None:
|
||||
op = GetAttr(self.r, "y", 1)
|
||||
branch = Branch(op, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR)
|
||||
branch.traceback_entry = ("foobar", 123)
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""\
|
||||
cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_y;
|
||||
if (unlikely(cpy_r_r0 == CPY_INT_TAG)) {
|
||||
CPy_AttributeError("prog.py", "foobar", "A", "y", 123, CPyStatic_prog___globals);
|
||||
goto CPyL8;
|
||||
}
|
||||
CPyTagged_INCREF(cpy_r_r0);
|
||||
goto CPyL9;
|
||||
""",
|
||||
next_branch=branch,
|
||||
skip_next=True,
|
||||
)
|
||||
|
||||
def test_get_attr_with_bitmap(self) -> None:
|
||||
self.assert_emit(
|
||||
GetAttr(self.r, "i1", 1),
|
||||
"""cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_i1;
|
||||
if (unlikely(cpy_r_r0 == -113) && !(((mod___AObject *)cpy_r_r)->bitmap & 1)) {
|
||||
PyErr_SetString(PyExc_AttributeError, "attribute 'i1' of 'A' undefined");
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
def test_set_attr(self) -> None:
|
||||
self.assert_emit(
|
||||
SetAttr(self.r, "y", self.m, 1),
|
||||
"""if (((mod___AObject *)cpy_r_r)->_y != CPY_INT_TAG) {
|
||||
CPyTagged_DECREF(((mod___AObject *)cpy_r_r)->_y);
|
||||
}
|
||||
((mod___AObject *)cpy_r_r)->_y = cpy_r_m;
|
||||
cpy_r_r0 = 1;
|
||||
""",
|
||||
)
|
||||
|
||||
def test_set_attr_non_refcounted(self) -> None:
|
||||
self.assert_emit(
|
||||
SetAttr(self.r, "x", self.b, 1),
|
||||
"""((mod___AObject *)cpy_r_r)->_x = cpy_r_b;
|
||||
cpy_r_r0 = 1;
|
||||
""",
|
||||
)
|
||||
|
||||
def test_set_attr_no_error(self) -> None:
|
||||
op = SetAttr(self.r, "y", self.m, 1)
|
||||
op.error_kind = ERR_NEVER
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""if (((mod___AObject *)cpy_r_r)->_y != CPY_INT_TAG) {
|
||||
CPyTagged_DECREF(((mod___AObject *)cpy_r_r)->_y);
|
||||
}
|
||||
((mod___AObject *)cpy_r_r)->_y = cpy_r_m;
|
||||
""",
|
||||
)
|
||||
|
||||
def test_set_attr_non_refcounted_no_error(self) -> None:
|
||||
op = SetAttr(self.r, "x", self.b, 1)
|
||||
op.error_kind = ERR_NEVER
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""((mod___AObject *)cpy_r_r)->_x = cpy_r_b;
|
||||
""",
|
||||
)
|
||||
|
||||
def test_set_attr_with_bitmap(self) -> None:
|
||||
# For some rtypes the error value overlaps a valid value, so we need
|
||||
# to use a separate bitmap to track defined attributes.
|
||||
self.assert_emit(
|
||||
SetAttr(self.r, "i1", self.i64, 1),
|
||||
"""if (unlikely(cpy_r_i64 == -113)) {
|
||||
((mod___AObject *)cpy_r_r)->bitmap |= 1;
|
||||
}
|
||||
((mod___AObject *)cpy_r_r)->_i1 = cpy_r_i64;
|
||||
cpy_r_r0 = 1;
|
||||
""",
|
||||
)
|
||||
self.assert_emit(
|
||||
SetAttr(self.r, "i2", self.i32, 1),
|
||||
"""if (unlikely(cpy_r_i32 == -113)) {
|
||||
((mod___AObject *)cpy_r_r)->bitmap |= 2;
|
||||
}
|
||||
((mod___AObject *)cpy_r_r)->_i2 = cpy_r_i32;
|
||||
cpy_r_r0 = 1;
|
||||
""",
|
||||
)
|
||||
|
||||
def test_set_attr_init_with_bitmap(self) -> None:
|
||||
op = SetAttr(self.r, "i1", self.i64, 1)
|
||||
op.is_init = True
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""if (unlikely(cpy_r_i64 == -113)) {
|
||||
((mod___AObject *)cpy_r_r)->bitmap |= 1;
|
||||
}
|
||||
((mod___AObject *)cpy_r_r)->_i1 = cpy_r_i64;
|
||||
cpy_r_r0 = 1;
|
||||
""",
|
||||
)
|
||||
|
||||
def test_dict_get_item(self) -> None:
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
dict_get_item_op.c_function_name,
|
||||
[self.d, self.o2],
|
||||
dict_get_item_op.return_type,
|
||||
dict_get_item_op.steals,
|
||||
dict_get_item_op.is_borrowed,
|
||||
dict_get_item_op.error_kind,
|
||||
1,
|
||||
),
|
||||
"""cpy_r_r0 = CPyDict_GetItem(cpy_r_d, cpy_r_o2);""",
|
||||
)
|
||||
|
||||
def test_dict_set_item(self) -> None:
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
dict_set_item_op.c_function_name,
|
||||
[self.d, self.o, self.o2],
|
||||
dict_set_item_op.return_type,
|
||||
dict_set_item_op.steals,
|
||||
dict_set_item_op.is_borrowed,
|
||||
dict_set_item_op.error_kind,
|
||||
1,
|
||||
),
|
||||
"""cpy_r_r0 = CPyDict_SetItem(cpy_r_d, cpy_r_o, cpy_r_o2);""",
|
||||
)
|
||||
|
||||
def test_dict_update(self) -> None:
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
dict_update_op.c_function_name,
|
||||
[self.d, self.o],
|
||||
dict_update_op.return_type,
|
||||
dict_update_op.steals,
|
||||
dict_update_op.is_borrowed,
|
||||
dict_update_op.error_kind,
|
||||
1,
|
||||
),
|
||||
"""cpy_r_r0 = CPyDict_Update(cpy_r_d, cpy_r_o);""",
|
||||
)
|
||||
|
||||
def test_new_dict(self) -> None:
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
dict_new_op.c_function_name,
|
||||
[],
|
||||
dict_new_op.return_type,
|
||||
dict_new_op.steals,
|
||||
dict_new_op.is_borrowed,
|
||||
dict_new_op.error_kind,
|
||||
1,
|
||||
),
|
||||
"""cpy_r_r0 = PyDict_New();""",
|
||||
)
|
||||
|
||||
def test_dict_contains(self) -> None:
|
||||
self.assert_emit_binary_op(
|
||||
"in", self.b, self.o, self.d, """cpy_r_r0 = PyDict_Contains(cpy_r_d, cpy_r_o);"""
|
||||
)
|
||||
|
||||
def test_int_op(self) -> None:
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.ADD, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 + cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.SUB, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 - cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.MUL, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 * cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.DIV, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 / cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.MOD, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 % cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.AND, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 & cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.OR, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 | cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.XOR, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 ^ cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.LEFT_SHIFT, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 << cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.RIGHT_SHIFT, 1),
|
||||
"""cpy_r_r0 = (Py_ssize_t)cpy_r_s1 >> (Py_ssize_t)cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
IntOp(short_int_rprimitive, self.i64, self.i64_1, IntOp.RIGHT_SHIFT, 1),
|
||||
"""cpy_r_r0 = cpy_r_i64 >> cpy_r_i64_1;""",
|
||||
)
|
||||
|
||||
def test_comparison_op(self) -> None:
|
||||
# signed
|
||||
self.assert_emit(
|
||||
ComparisonOp(self.s1, self.s2, ComparisonOp.SLT, 1),
|
||||
"""cpy_r_r0 = (Py_ssize_t)cpy_r_s1 < (Py_ssize_t)cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
ComparisonOp(self.i32, self.i32_1, ComparisonOp.SLT, 1),
|
||||
"""cpy_r_r0 = cpy_r_i32 < cpy_r_i32_1;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
ComparisonOp(self.i64, self.i64_1, ComparisonOp.SLT, 1),
|
||||
"""cpy_r_r0 = cpy_r_i64 < cpy_r_i64_1;""",
|
||||
)
|
||||
# unsigned
|
||||
self.assert_emit(
|
||||
ComparisonOp(self.s1, self.s2, ComparisonOp.ULT, 1),
|
||||
"""cpy_r_r0 = cpy_r_s1 < cpy_r_s2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
ComparisonOp(self.i32, self.i32_1, ComparisonOp.ULT, 1),
|
||||
"""cpy_r_r0 = (uint32_t)cpy_r_i32 < (uint32_t)cpy_r_i32_1;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
ComparisonOp(self.i64, self.i64_1, ComparisonOp.ULT, 1),
|
||||
"""cpy_r_r0 = (uint64_t)cpy_r_i64 < (uint64_t)cpy_r_i64_1;""",
|
||||
)
|
||||
|
||||
# object type
|
||||
self.assert_emit(
|
||||
ComparisonOp(self.o, self.o2, ComparisonOp.EQ, 1),
|
||||
"""cpy_r_r0 = cpy_r_o == cpy_r_o2;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
ComparisonOp(self.o, self.o2, ComparisonOp.NEQ, 1),
|
||||
"""cpy_r_r0 = cpy_r_o != cpy_r_o2;""",
|
||||
)
|
||||
|
||||
def test_load_mem(self) -> None:
|
||||
self.assert_emit(LoadMem(bool_rprimitive, self.ptr), """cpy_r_r0 = *(char *)cpy_r_ptr;""")
|
||||
|
||||
def test_set_mem(self) -> None:
|
||||
self.assert_emit(
|
||||
SetMem(bool_rprimitive, self.ptr, self.b), """*(char *)cpy_r_ptr = cpy_r_b;"""
|
||||
)
|
||||
|
||||
def test_get_element_ptr(self) -> None:
|
||||
r = RStruct(
|
||||
"Foo", ["b", "i32", "i64"], [bool_rprimitive, int32_rprimitive, int64_rprimitive]
|
||||
)
|
||||
self.assert_emit(
|
||||
GetElementPtr(self.o, r, "b"), """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->b;"""
|
||||
)
|
||||
self.assert_emit(
|
||||
GetElementPtr(self.o, r, "i32"), """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->i32;"""
|
||||
)
|
||||
self.assert_emit(
|
||||
GetElementPtr(self.o, r, "i64"), """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->i64;"""
|
||||
)
|
||||
|
||||
def test_load_address(self) -> None:
|
||||
self.assert_emit(
|
||||
LoadAddress(object_rprimitive, "PyDict_Type"),
|
||||
"""cpy_r_r0 = (PyObject *)&PyDict_Type;""",
|
||||
)
|
||||
|
||||
def test_assign_multi(self) -> None:
|
||||
t = RArray(object_rprimitive, 2)
|
||||
a = Register(t, "a")
|
||||
self.registers.append(a)
|
||||
self.assert_emit(
|
||||
AssignMulti(a, [self.o, self.o2]), """PyObject *cpy_r_a[2] = {cpy_r_o, cpy_r_o2};"""
|
||||
)
|
||||
|
||||
def test_long_unsigned(self) -> None:
|
||||
a = Register(int64_rprimitive, "a")
|
||||
self.assert_emit(
|
||||
Assign(a, Integer(1 << 31, int64_rprimitive)), """cpy_r_a = 2147483648LL;"""
|
||||
)
|
||||
self.assert_emit(
|
||||
Assign(a, Integer((1 << 31) - 1, int64_rprimitive)), """cpy_r_a = 2147483647;"""
|
||||
)
|
||||
|
||||
def test_long_signed(self) -> None:
|
||||
a = Register(int64_rprimitive, "a")
|
||||
self.assert_emit(
|
||||
Assign(a, Integer(-(1 << 31) + 1, int64_rprimitive)), """cpy_r_a = -2147483647;"""
|
||||
)
|
||||
self.assert_emit(
|
||||
Assign(a, Integer(-(1 << 31), int64_rprimitive)), """cpy_r_a = -2147483648LL;"""
|
||||
)
|
||||
|
||||
def test_cast_and_branch_merge(self) -> None:
|
||||
op = Cast(self.r, dict_rprimitive, 1)
|
||||
next_block = BasicBlock(9)
|
||||
branch = Branch(op, BasicBlock(8), next_block, Branch.IS_ERROR)
|
||||
branch.traceback_entry = ("foobar", 123)
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""\
|
||||
if (likely(PyDict_Check(cpy_r_r)))
|
||||
cpy_r_r0 = cpy_r_r;
|
||||
else {
|
||||
CPy_TypeErrorTraceback("prog.py", "foobar", 123, CPyStatic_prog___globals, "dict", cpy_r_r);
|
||||
goto CPyL8;
|
||||
}
|
||||
""",
|
||||
next_block=next_block,
|
||||
next_branch=branch,
|
||||
skip_next=True,
|
||||
)
|
||||
|
||||
def test_cast_and_branch_no_merge_1(self) -> None:
|
||||
op = Cast(self.r, dict_rprimitive, 1)
|
||||
branch = Branch(op, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR)
|
||||
branch.traceback_entry = ("foobar", 123)
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""\
|
||||
if (likely(PyDict_Check(cpy_r_r)))
|
||||
cpy_r_r0 = cpy_r_r;
|
||||
else {
|
||||
CPy_TypeError("dict", cpy_r_r);
|
||||
cpy_r_r0 = NULL;
|
||||
}
|
||||
""",
|
||||
next_block=BasicBlock(10),
|
||||
next_branch=branch,
|
||||
skip_next=False,
|
||||
)
|
||||
|
||||
def test_cast_and_branch_no_merge_2(self) -> None:
|
||||
op = Cast(self.r, dict_rprimitive, 1)
|
||||
next_block = BasicBlock(9)
|
||||
branch = Branch(op, BasicBlock(8), next_block, Branch.IS_ERROR)
|
||||
branch.negated = True
|
||||
branch.traceback_entry = ("foobar", 123)
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""\
|
||||
if (likely(PyDict_Check(cpy_r_r)))
|
||||
cpy_r_r0 = cpy_r_r;
|
||||
else {
|
||||
CPy_TypeError("dict", cpy_r_r);
|
||||
cpy_r_r0 = NULL;
|
||||
}
|
||||
""",
|
||||
next_block=next_block,
|
||||
next_branch=branch,
|
||||
)
|
||||
|
||||
def test_cast_and_branch_no_merge_3(self) -> None:
|
||||
op = Cast(self.r, dict_rprimitive, 1)
|
||||
next_block = BasicBlock(9)
|
||||
branch = Branch(op, BasicBlock(8), next_block, Branch.BOOL)
|
||||
branch.traceback_entry = ("foobar", 123)
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""\
|
||||
if (likely(PyDict_Check(cpy_r_r)))
|
||||
cpy_r_r0 = cpy_r_r;
|
||||
else {
|
||||
CPy_TypeError("dict", cpy_r_r);
|
||||
cpy_r_r0 = NULL;
|
||||
}
|
||||
""",
|
||||
next_block=next_block,
|
||||
next_branch=branch,
|
||||
)
|
||||
|
||||
def test_cast_and_branch_no_merge_4(self) -> None:
|
||||
op = Cast(self.r, dict_rprimitive, 1)
|
||||
next_block = BasicBlock(9)
|
||||
branch = Branch(op, BasicBlock(8), next_block, Branch.IS_ERROR)
|
||||
self.assert_emit(
|
||||
op,
|
||||
"""\
|
||||
if (likely(PyDict_Check(cpy_r_r)))
|
||||
cpy_r_r0 = cpy_r_r;
|
||||
else {
|
||||
CPy_TypeError("dict", cpy_r_r);
|
||||
cpy_r_r0 = NULL;
|
||||
}
|
||||
""",
|
||||
next_block=next_block,
|
||||
next_branch=branch,
|
||||
)
|
||||
|
||||
def test_extend(self) -> None:
|
||||
a = Register(int32_rprimitive, "a")
|
||||
self.assert_emit(Extend(a, int64_rprimitive, signed=True), """cpy_r_r0 = cpy_r_a;""")
|
||||
self.assert_emit(
|
||||
Extend(a, int64_rprimitive, signed=False), """cpy_r_r0 = (uint32_t)cpy_r_a;"""
|
||||
)
|
||||
if PLATFORM_SIZE == 4:
|
||||
self.assert_emit(
|
||||
Extend(self.n, int64_rprimitive, signed=True),
|
||||
"""cpy_r_r0 = (Py_ssize_t)cpy_r_n;""",
|
||||
)
|
||||
self.assert_emit(
|
||||
Extend(self.n, int64_rprimitive, signed=False), """cpy_r_r0 = cpy_r_n;"""
|
||||
)
|
||||
if PLATFORM_SIZE == 8:
|
||||
self.assert_emit(Extend(a, int_rprimitive, signed=True), """cpy_r_r0 = cpy_r_a;""")
|
||||
self.assert_emit(
|
||||
Extend(a, int_rprimitive, signed=False), """cpy_r_r0 = (uint32_t)cpy_r_a;"""
|
||||
)
|
||||
|
||||
def assert_emit(
|
||||
self,
|
||||
op: Op,
|
||||
expected: str,
|
||||
next_block: BasicBlock | None = None,
|
||||
*,
|
||||
rare: bool = False,
|
||||
next_branch: Branch | None = None,
|
||||
skip_next: bool = False,
|
||||
) -> None:
|
||||
block = BasicBlock(0)
|
||||
block.ops.append(op)
|
||||
value_names = generate_names_for_ir(self.registers, [block])
|
||||
emitter = Emitter(self.context, value_names)
|
||||
declarations = Emitter(self.context, value_names)
|
||||
emitter.fragments = []
|
||||
declarations.fragments = []
|
||||
|
||||
visitor = FunctionEmitterVisitor(emitter, declarations, "prog.py", "prog")
|
||||
visitor.next_block = next_block
|
||||
visitor.rare = rare
|
||||
if next_branch:
|
||||
visitor.ops = [op, next_branch]
|
||||
else:
|
||||
visitor.ops = [op]
|
||||
visitor.op_index = 0
|
||||
|
||||
op.accept(visitor)
|
||||
frags = declarations.fragments + emitter.fragments
|
||||
actual_lines = [line.strip(" ") for line in frags]
|
||||
assert all(line.endswith("\n") for line in actual_lines)
|
||||
actual_lines = [line.rstrip("\n") for line in actual_lines]
|
||||
if not expected.strip():
|
||||
expected_lines = []
|
||||
else:
|
||||
expected_lines = expected.rstrip().split("\n")
|
||||
expected_lines = [line.strip(" ") for line in expected_lines]
|
||||
assert_string_arrays_equal(expected_lines, actual_lines, msg="Generated code unexpected")
|
||||
if skip_next:
|
||||
assert visitor.op_index == 1
|
||||
else:
|
||||
assert visitor.op_index == 0
|
||||
|
||||
def assert_emit_binary_op(
|
||||
self, op: str, dest: Value, left: Value, right: Value, expected: str
|
||||
) -> None:
|
||||
if op in binary_ops:
|
||||
ops = binary_ops[op]
|
||||
for desc in ops:
|
||||
if is_subtype(left.type, desc.arg_types[0]) and is_subtype(
|
||||
right.type, desc.arg_types[1]
|
||||
):
|
||||
args = [left, right]
|
||||
if desc.ordering is not None:
|
||||
args = [args[i] for i in desc.ordering]
|
||||
self.assert_emit(
|
||||
CallC(
|
||||
desc.c_function_name,
|
||||
args,
|
||||
desc.return_type,
|
||||
desc.steals,
|
||||
desc.is_borrowed,
|
||||
desc.error_kind,
|
||||
55,
|
||||
),
|
||||
expected,
|
||||
)
|
||||
return
|
||||
else:
|
||||
assert False, "Could not find matching op"
|
||||
|
||||
|
||||
class TestGenerateFunction(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.arg = RuntimeArg("arg", int_rprimitive)
|
||||
self.reg = Register(int_rprimitive, "arg")
|
||||
self.block = BasicBlock(0)
|
||||
|
||||
def test_simple(self) -> None:
|
||||
self.block.ops.append(Return(self.reg))
|
||||
fn = FuncIR(
|
||||
FuncDecl("myfunc", None, "mod", FuncSignature([self.arg], int_rprimitive)),
|
||||
[self.reg],
|
||||
[self.block],
|
||||
)
|
||||
value_names = generate_names_for_ir(fn.arg_regs, fn.blocks)
|
||||
emitter = Emitter(EmitterContext(NameGenerator([["mod"]])), value_names)
|
||||
generate_native_function(fn, emitter, "prog.py", "prog")
|
||||
result = emitter.fragments
|
||||
assert_string_arrays_equal(
|
||||
["CPyTagged CPyDef_myfunc(CPyTagged cpy_r_arg) {\n", " return cpy_r_arg;\n", "}\n"],
|
||||
result,
|
||||
msg="Generated code invalid",
|
||||
)
|
||||
|
||||
def test_register(self) -> None:
|
||||
reg = Register(int_rprimitive)
|
||||
op = Assign(reg, Integer(5))
|
||||
self.block.ops.append(op)
|
||||
self.block.ops.append(Unreachable())
|
||||
fn = FuncIR(
|
||||
FuncDecl("myfunc", None, "mod", FuncSignature([self.arg], list_rprimitive)),
|
||||
[self.reg],
|
||||
[self.block],
|
||||
)
|
||||
value_names = generate_names_for_ir(fn.arg_regs, fn.blocks)
|
||||
emitter = Emitter(EmitterContext(NameGenerator([["mod"]])), value_names)
|
||||
generate_native_function(fn, emitter, "prog.py", "prog")
|
||||
result = emitter.fragments
|
||||
assert_string_arrays_equal(
|
||||
[
|
||||
"PyObject *CPyDef_myfunc(CPyTagged cpy_r_arg) {\n",
|
||||
" CPyTagged cpy_r_r0;\n",
|
||||
" cpy_r_r0 = 10;\n",
|
||||
" CPy_Unreachable();\n",
|
||||
"}\n",
|
||||
],
|
||||
result,
|
||||
msg="Generated code invalid",
|
||||
)
|
||||
@@ -0,0 +1,60 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypy.test.helpers import assert_string_arrays_equal
|
||||
from mypyc.codegen.emit import Emitter, EmitterContext, ReturnHandler
|
||||
from mypyc.codegen.emitwrapper import generate_arg_check
|
||||
from mypyc.ir.rtypes import int_rprimitive, list_rprimitive
|
||||
from mypyc.namegen import NameGenerator
|
||||
|
||||
|
||||
class TestArgCheck(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.context = EmitterContext(NameGenerator([["mod"]]))
|
||||
|
||||
def test_check_list(self) -> None:
|
||||
emitter = Emitter(self.context)
|
||||
generate_arg_check("x", list_rprimitive, emitter, ReturnHandler("NULL"))
|
||||
lines = emitter.fragments
|
||||
self.assert_lines(
|
||||
[
|
||||
"PyObject *arg_x;",
|
||||
"if (likely(PyList_Check(obj_x)))",
|
||||
" arg_x = obj_x;",
|
||||
"else {",
|
||||
' CPy_TypeError("list", obj_x);',
|
||||
" return NULL;",
|
||||
"}",
|
||||
],
|
||||
lines,
|
||||
)
|
||||
|
||||
def test_check_int(self) -> None:
|
||||
emitter = Emitter(self.context)
|
||||
generate_arg_check("x", int_rprimitive, emitter, ReturnHandler("NULL"))
|
||||
generate_arg_check("y", int_rprimitive, emitter, ReturnHandler("NULL"), optional=True)
|
||||
lines = emitter.fragments
|
||||
self.assert_lines(
|
||||
[
|
||||
"CPyTagged arg_x;",
|
||||
"if (likely(PyLong_Check(obj_x)))",
|
||||
" arg_x = CPyTagged_BorrowFromObject(obj_x);",
|
||||
"else {",
|
||||
' CPy_TypeError("int", obj_x); return NULL;',
|
||||
"}",
|
||||
"CPyTagged arg_y;",
|
||||
"if (obj_y == NULL) {",
|
||||
" arg_y = CPY_INT_TAG;",
|
||||
"} else if (likely(PyLong_Check(obj_y)))",
|
||||
" arg_y = CPyTagged_BorrowFromObject(obj_y);",
|
||||
"else {",
|
||||
' CPy_TypeError("int", obj_y); return NULL;',
|
||||
"}",
|
||||
],
|
||||
lines,
|
||||
)
|
||||
|
||||
def assert_lines(self, expected: list[str], actual: list[str]) -> None:
|
||||
actual = [line.rstrip("\n") for line in actual]
|
||||
assert_string_arrays_equal(expected, actual, "Invalid output")
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Test runner for exception handling transform test cases.
|
||||
|
||||
The transform inserts exception handling branch operations to IR.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
|
||||
from mypy.errors import CompileError
|
||||
from mypy.test.config import test_temp_dir
|
||||
from mypy.test.data import DataDrivenTestCase
|
||||
from mypyc.analysis.blockfreq import frequently_executed_blocks
|
||||
from mypyc.common import TOP_LEVEL_NAME
|
||||
from mypyc.ir.pprint import format_func
|
||||
from mypyc.test.testutil import (
|
||||
ICODE_GEN_BUILTINS,
|
||||
MypycDataSuite,
|
||||
assert_test_output,
|
||||
build_ir_for_single_file,
|
||||
remove_comment_lines,
|
||||
use_custom_builtins,
|
||||
)
|
||||
from mypyc.transform.exceptions import insert_exception_handling
|
||||
from mypyc.transform.refcount import insert_ref_count_opcodes
|
||||
from mypyc.transform.uninit import insert_uninit_checks
|
||||
|
||||
files = ["exceptions.test", "exceptions-freq.test"]
|
||||
|
||||
|
||||
class TestExceptionTransform(MypycDataSuite):
|
||||
files = files
|
||||
base_path = test_temp_dir
|
||||
|
||||
def run_case(self, testcase: DataDrivenTestCase) -> None:
|
||||
"""Perform a runtime checking transformation test case."""
|
||||
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
|
||||
expected_output = remove_comment_lines(testcase.output)
|
||||
try:
|
||||
ir = build_ir_for_single_file(testcase.input)
|
||||
except CompileError as e:
|
||||
actual = e.messages
|
||||
else:
|
||||
actual = []
|
||||
for fn in ir:
|
||||
if fn.name == TOP_LEVEL_NAME and not testcase.name.endswith("_toplevel"):
|
||||
continue
|
||||
insert_uninit_checks(fn)
|
||||
insert_exception_handling(fn)
|
||||
insert_ref_count_opcodes(fn)
|
||||
actual.extend(format_func(fn))
|
||||
if testcase.name.endswith("_freq"):
|
||||
common = frequently_executed_blocks(fn.blocks[0])
|
||||
actual.append("hot blocks: %s" % sorted(b.label for b in common))
|
||||
|
||||
assert_test_output(testcase, actual, "Invalid source code output", expected_output)
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Test cases that run tests as subprocesses."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
base_dir = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
|
||||
|
||||
class TestExternal(unittest.TestCase):
|
||||
# TODO: Get this to work on Windows.
|
||||
# (Or don't. It is probably not a good use of time.)
|
||||
@unittest.skipIf(sys.platform.startswith("win"), "rt tests don't work on windows")
|
||||
def test_c_unit_test(self) -> None:
|
||||
"""Run C unit tests in a subprocess."""
|
||||
# Build Google Test, the C++ framework we use for testing C code.
|
||||
# The source code for Google Test is copied to this repository.
|
||||
cppflags: list[str] = []
|
||||
env = os.environ.copy()
|
||||
if sys.platform == "darwin":
|
||||
cppflags += ["-mmacosx-version-min=10.10", "-stdlib=libc++"]
|
||||
env["CPPFLAGS"] = " ".join(cppflags)
|
||||
subprocess.check_call(
|
||||
["make", "libgtest.a"],
|
||||
env=env,
|
||||
cwd=os.path.join(base_dir, "mypyc", "external", "googletest", "make"),
|
||||
)
|
||||
# Build Python wrapper for C unit tests.
|
||||
env = os.environ.copy()
|
||||
env["CPPFLAGS"] = " ".join(cppflags)
|
||||
status = subprocess.check_call(
|
||||
[sys.executable, "setup.py", "build_ext", "--inplace"],
|
||||
env=env,
|
||||
cwd=os.path.join(base_dir, "mypyc", "lib-rt"),
|
||||
)
|
||||
# Run C unit tests.
|
||||
env = os.environ.copy()
|
||||
if "GTEST_COLOR" not in os.environ:
|
||||
env["GTEST_COLOR"] = "yes" # Use fancy colors
|
||||
status = subprocess.call(
|
||||
[sys.executable, "-c", "import sys, test_capi; sys.exit(test_capi.run_tests())"],
|
||||
env=env,
|
||||
cwd=os.path.join(base_dir, "mypyc", "lib-rt"),
|
||||
)
|
||||
if status != 0:
|
||||
raise AssertionError("make test: C unit test failure")
|
||||
87
venv/lib/python3.12/site-packages/mypyc/test/test_irbuild.py
Normal file
87
venv/lib/python3.12/site-packages/mypyc/test/test_irbuild.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Test cases for IR generation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from mypy.errors import CompileError
|
||||
from mypy.test.config import test_temp_dir
|
||||
from mypy.test.data import DataDrivenTestCase
|
||||
from mypyc.common import TOP_LEVEL_NAME
|
||||
from mypyc.ir.pprint import format_func
|
||||
from mypyc.test.testutil import (
|
||||
ICODE_GEN_BUILTINS,
|
||||
MypycDataSuite,
|
||||
assert_test_output,
|
||||
build_ir_for_single_file,
|
||||
infer_ir_build_options_from_test_name,
|
||||
remove_comment_lines,
|
||||
replace_word_size,
|
||||
use_custom_builtins,
|
||||
)
|
||||
|
||||
files = [
|
||||
"irbuild-basic.test",
|
||||
"irbuild-int.test",
|
||||
"irbuild-bool.test",
|
||||
"irbuild-lists.test",
|
||||
"irbuild-tuple.test",
|
||||
"irbuild-dict.test",
|
||||
"irbuild-set.test",
|
||||
"irbuild-str.test",
|
||||
"irbuild-bytes.test",
|
||||
"irbuild-float.test",
|
||||
"irbuild-statements.test",
|
||||
"irbuild-nested.test",
|
||||
"irbuild-classes.test",
|
||||
"irbuild-optional.test",
|
||||
"irbuild-any.test",
|
||||
"irbuild-generics.test",
|
||||
"irbuild-try.test",
|
||||
"irbuild-strip-asserts.test",
|
||||
"irbuild-i64.test",
|
||||
"irbuild-i32.test",
|
||||
"irbuild-i16.test",
|
||||
"irbuild-u8.test",
|
||||
"irbuild-vectorcall.test",
|
||||
"irbuild-unreachable.test",
|
||||
"irbuild-isinstance.test",
|
||||
"irbuild-dunders.test",
|
||||
"irbuild-singledispatch.test",
|
||||
"irbuild-constant-fold.test",
|
||||
"irbuild-glue-methods.test",
|
||||
"irbuild-math.test",
|
||||
]
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
files.append("irbuild-match.test")
|
||||
|
||||
|
||||
class TestGenOps(MypycDataSuite):
|
||||
files = files
|
||||
base_path = test_temp_dir
|
||||
optional_out = True
|
||||
|
||||
def run_case(self, testcase: DataDrivenTestCase) -> None:
|
||||
"""Perform a runtime checking transformation test case."""
|
||||
options = infer_ir_build_options_from_test_name(testcase.name)
|
||||
if options is None:
|
||||
# Skipped test case
|
||||
return
|
||||
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
|
||||
expected_output = remove_comment_lines(testcase.output)
|
||||
expected_output = replace_word_size(expected_output)
|
||||
name = testcase.name
|
||||
try:
|
||||
ir = build_ir_for_single_file(testcase.input, options)
|
||||
except CompileError as e:
|
||||
actual = e.messages
|
||||
else:
|
||||
actual = []
|
||||
for fn in ir:
|
||||
if fn.name == TOP_LEVEL_NAME and not name.endswith("_toplevel"):
|
||||
continue
|
||||
actual.extend(format_func(fn))
|
||||
|
||||
assert_test_output(testcase, actual, "Invalid source code output", expected_output)
|
||||
199
venv/lib/python3.12/site-packages/mypyc/test/test_ircheck.py
Normal file
199
venv/lib/python3.12/site-packages/mypyc/test/test_ircheck.py
Normal file
@@ -0,0 +1,199 @@
|
||||
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)]
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Test code geneneration for literals."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.codegen.literals import (
|
||||
Literals,
|
||||
_encode_bytes_values,
|
||||
_encode_int_values,
|
||||
_encode_str_values,
|
||||
format_str_literal,
|
||||
)
|
||||
|
||||
|
||||
class TestLiterals(unittest.TestCase):
|
||||
def test_format_str_literal(self) -> None:
|
||||
assert format_str_literal("") == b"\x00"
|
||||
assert format_str_literal("xyz") == b"\x03xyz"
|
||||
assert format_str_literal("x" * 127) == b"\x7f" + b"x" * 127
|
||||
assert format_str_literal("x" * 128) == b"\x81\x00" + b"x" * 128
|
||||
assert format_str_literal("x" * 131) == b"\x81\x03" + b"x" * 131
|
||||
|
||||
def test_encode_str_values(self) -> None:
|
||||
assert _encode_str_values({}) == [b""]
|
||||
assert _encode_str_values({"foo": 0}) == [b"\x01\x03foo", b""]
|
||||
assert _encode_str_values({"foo": 0, "b": 1}) == [b"\x02\x03foo\x01b", b""]
|
||||
assert _encode_str_values({"foo": 0, "x" * 70: 1}) == [
|
||||
b"\x01\x03foo",
|
||||
bytes([1, 70]) + b"x" * 70,
|
||||
b"",
|
||||
]
|
||||
assert _encode_str_values({"y" * 100: 0}) == [bytes([1, 100]) + b"y" * 100, b""]
|
||||
|
||||
def test_encode_bytes_values(self) -> None:
|
||||
assert _encode_bytes_values({}) == [b""]
|
||||
assert _encode_bytes_values({b"foo": 0}) == [b"\x01\x03foo", b""]
|
||||
assert _encode_bytes_values({b"foo": 0, b"b": 1}) == [b"\x02\x03foo\x01b", b""]
|
||||
assert _encode_bytes_values({b"foo": 0, b"x" * 70: 1}) == [
|
||||
b"\x01\x03foo",
|
||||
bytes([1, 70]) + b"x" * 70,
|
||||
b"",
|
||||
]
|
||||
assert _encode_bytes_values({b"y" * 100: 0}) == [bytes([1, 100]) + b"y" * 100, b""]
|
||||
|
||||
def test_encode_int_values(self) -> None:
|
||||
assert _encode_int_values({}) == [b""]
|
||||
assert _encode_int_values({123: 0}) == [b"\x01123", b""]
|
||||
assert _encode_int_values({123: 0, 9: 1}) == [b"\x02123\x009", b""]
|
||||
assert _encode_int_values({123: 0, 45: 1, 5 * 10**70: 2}) == [
|
||||
b"\x02123\x0045",
|
||||
b"\x015" + b"0" * 70,
|
||||
b"",
|
||||
]
|
||||
assert _encode_int_values({6 * 10**100: 0}) == [b"\x016" + b"0" * 100, b""]
|
||||
|
||||
def test_simple_literal_index(self) -> None:
|
||||
lit = Literals()
|
||||
lit.record_literal(1)
|
||||
lit.record_literal("y")
|
||||
lit.record_literal(True)
|
||||
lit.record_literal(None)
|
||||
lit.record_literal(False)
|
||||
assert lit.literal_index(None) == 0
|
||||
assert lit.literal_index(False) == 1
|
||||
assert lit.literal_index(True) == 2
|
||||
assert lit.literal_index("y") == 3
|
||||
assert lit.literal_index(1) == 4
|
||||
|
||||
def test_tuple_literal(self) -> None:
|
||||
lit = Literals()
|
||||
lit.record_literal((1, "y", None, (b"a", "b")))
|
||||
lit.record_literal((b"a", "b"))
|
||||
lit.record_literal(())
|
||||
assert lit.literal_index((b"a", "b")) == 7
|
||||
assert lit.literal_index((1, "y", None, (b"a", "b"))) == 8
|
||||
assert lit.literal_index(()) == 9
|
||||
print(lit.encoded_tuple_values())
|
||||
assert lit.encoded_tuple_values() == [
|
||||
"3", # Number of tuples
|
||||
"2",
|
||||
"5",
|
||||
"4", # First tuple (length=2)
|
||||
"4",
|
||||
"6",
|
||||
"3",
|
||||
"0",
|
||||
"7", # Second tuple (length=4)
|
||||
"0", # Third tuple (length=0)
|
||||
]
|
||||
48
venv/lib/python3.12/site-packages/mypyc/test/test_namegen.py
Normal file
48
venv/lib/python3.12/site-packages/mypyc/test/test_namegen.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.namegen import (
|
||||
NameGenerator,
|
||||
candidate_suffixes,
|
||||
exported_name,
|
||||
make_module_translation_map,
|
||||
)
|
||||
|
||||
|
||||
class TestNameGen(unittest.TestCase):
|
||||
def test_candidate_suffixes(self) -> None:
|
||||
assert candidate_suffixes("foo") == ["", "foo."]
|
||||
assert candidate_suffixes("foo.bar") == ["", "bar.", "foo.bar."]
|
||||
|
||||
def test_exported_name(self) -> None:
|
||||
assert exported_name("foo") == "foo"
|
||||
assert exported_name("foo.bar") == "foo___bar"
|
||||
|
||||
def test_make_module_translation_map(self) -> None:
|
||||
assert make_module_translation_map(["foo", "bar"]) == {"foo": "foo.", "bar": "bar."}
|
||||
assert make_module_translation_map(["foo.bar", "foo.baz"]) == {
|
||||
"foo.bar": "bar.",
|
||||
"foo.baz": "baz.",
|
||||
}
|
||||
assert make_module_translation_map(["zar", "foo.bar", "foo.baz"]) == {
|
||||
"foo.bar": "bar.",
|
||||
"foo.baz": "baz.",
|
||||
"zar": "zar.",
|
||||
}
|
||||
assert make_module_translation_map(["foo.bar", "fu.bar", "foo.baz"]) == {
|
||||
"foo.bar": "foo.bar.",
|
||||
"fu.bar": "fu.bar.",
|
||||
"foo.baz": "baz.",
|
||||
}
|
||||
|
||||
def test_name_generator(self) -> None:
|
||||
g = NameGenerator([["foo", "foo.zar"]])
|
||||
assert g.private_name("foo", "f") == "foo___f"
|
||||
assert g.private_name("foo", "C.x.y") == "foo___C___x___y"
|
||||
assert g.private_name("foo", "C.x.y") == "foo___C___x___y"
|
||||
assert g.private_name("foo.zar", "C.x.y") == "zar___C___x___y"
|
||||
assert g.private_name("foo", "C.x_y") == "foo___C___x_y"
|
||||
assert g.private_name("foo", "C_x_y") == "foo___C_x_y"
|
||||
assert g.private_name("foo", "C_x_y") == "foo___C_x_y"
|
||||
assert g.private_name("foo", "___") == "foo______3_"
|
||||
42
venv/lib/python3.12/site-packages/mypyc/test/test_pprint.py
Normal file
42
venv/lib/python3.12/site-packages/mypyc/test/test_pprint.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.ir.ops import Assign, BasicBlock, Integer, IntOp, Op, Register, Unreachable
|
||||
from mypyc.ir.pprint import generate_names_for_ir
|
||||
from mypyc.ir.rtypes import int_rprimitive
|
||||
|
||||
|
||||
def register(name: str) -> Register:
|
||||
return Register(int_rprimitive, "foo", is_arg=True)
|
||||
|
||||
|
||||
def make_block(ops: list[Op]) -> BasicBlock:
|
||||
block = BasicBlock()
|
||||
block.ops.extend(ops)
|
||||
return block
|
||||
|
||||
|
||||
class TestGenerateNames(unittest.TestCase):
|
||||
def test_empty(self) -> None:
|
||||
assert generate_names_for_ir([], []) == {}
|
||||
|
||||
def test_arg(self) -> None:
|
||||
reg = register("foo")
|
||||
assert generate_names_for_ir([reg], []) == {reg: "foo"}
|
||||
|
||||
def test_int_op(self) -> None:
|
||||
n1 = Integer(2)
|
||||
n2 = Integer(4)
|
||||
op1 = IntOp(int_rprimitive, n1, n2, IntOp.ADD)
|
||||
op2 = IntOp(int_rprimitive, op1, n2, IntOp.ADD)
|
||||
block = make_block([op1, op2, Unreachable()])
|
||||
assert generate_names_for_ir([], [block]) == {op1: "r0", op2: "r1"}
|
||||
|
||||
def test_assign(self) -> None:
|
||||
reg = register("foo")
|
||||
n = Integer(2)
|
||||
op1 = Assign(reg, n)
|
||||
op2 = Assign(reg, n)
|
||||
block = make_block([op1, op2])
|
||||
assert generate_names_for_ir([reg], [block]) == {reg: "foo"}
|
||||
48
venv/lib/python3.12/site-packages/mypyc/test/test_rarray.py
Normal file
48
venv/lib/python3.12/site-packages/mypyc/test/test_rarray.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""Unit tests for RArray types."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.common import PLATFORM_SIZE
|
||||
from mypyc.ir.rtypes import (
|
||||
RArray,
|
||||
bool_rprimitive,
|
||||
compute_rtype_alignment,
|
||||
compute_rtype_size,
|
||||
int_rprimitive,
|
||||
)
|
||||
|
||||
|
||||
class TestRArray(unittest.TestCase):
|
||||
def test_basics(self) -> None:
|
||||
a = RArray(int_rprimitive, 10)
|
||||
assert a.item_type == int_rprimitive
|
||||
assert a.length == 10
|
||||
|
||||
def test_str_conversion(self) -> None:
|
||||
a = RArray(int_rprimitive, 10)
|
||||
assert str(a) == "int[10]"
|
||||
assert repr(a) == "<RArray <RPrimitive builtins.int>[10]>"
|
||||
|
||||
def test_eq(self) -> None:
|
||||
a = RArray(int_rprimitive, 10)
|
||||
assert a == RArray(int_rprimitive, 10)
|
||||
assert a != RArray(bool_rprimitive, 10)
|
||||
assert a != RArray(int_rprimitive, 9)
|
||||
|
||||
def test_hash(self) -> None:
|
||||
assert hash(RArray(int_rprimitive, 10)) == hash(RArray(int_rprimitive, 10))
|
||||
assert hash(RArray(bool_rprimitive, 5)) == hash(RArray(bool_rprimitive, 5))
|
||||
|
||||
def test_alignment(self) -> None:
|
||||
a = RArray(int_rprimitive, 10)
|
||||
assert compute_rtype_alignment(a) == PLATFORM_SIZE
|
||||
b = RArray(bool_rprimitive, 55)
|
||||
assert compute_rtype_alignment(b) == 1
|
||||
|
||||
def test_size(self) -> None:
|
||||
a = RArray(int_rprimitive, 9)
|
||||
assert compute_rtype_size(a) == 9 * PLATFORM_SIZE
|
||||
b = RArray(bool_rprimitive, 3)
|
||||
assert compute_rtype_size(b) == 3
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Test runner for reference count opcode insertion transform test cases.
|
||||
|
||||
The transform inserts needed reference count increment/decrement
|
||||
operations to IR.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
|
||||
from mypy.errors import CompileError
|
||||
from mypy.test.config import test_temp_dir
|
||||
from mypy.test.data import DataDrivenTestCase
|
||||
from mypyc.common import TOP_LEVEL_NAME
|
||||
from mypyc.ir.pprint import format_func
|
||||
from mypyc.test.testutil import (
|
||||
ICODE_GEN_BUILTINS,
|
||||
MypycDataSuite,
|
||||
assert_test_output,
|
||||
build_ir_for_single_file,
|
||||
infer_ir_build_options_from_test_name,
|
||||
remove_comment_lines,
|
||||
replace_word_size,
|
||||
use_custom_builtins,
|
||||
)
|
||||
from mypyc.transform.refcount import insert_ref_count_opcodes
|
||||
from mypyc.transform.uninit import insert_uninit_checks
|
||||
|
||||
files = ["refcount.test"]
|
||||
|
||||
|
||||
class TestRefCountTransform(MypycDataSuite):
|
||||
files = files
|
||||
base_path = test_temp_dir
|
||||
optional_out = True
|
||||
|
||||
def run_case(self, testcase: DataDrivenTestCase) -> None:
|
||||
"""Perform a runtime checking transformation test case."""
|
||||
options = infer_ir_build_options_from_test_name(testcase.name)
|
||||
if options is None:
|
||||
# Skipped test case
|
||||
return
|
||||
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
|
||||
expected_output = remove_comment_lines(testcase.output)
|
||||
expected_output = replace_word_size(expected_output)
|
||||
try:
|
||||
ir = build_ir_for_single_file(testcase.input, options)
|
||||
except CompileError as e:
|
||||
actual = e.messages
|
||||
else:
|
||||
actual = []
|
||||
for fn in ir:
|
||||
if fn.name == TOP_LEVEL_NAME and not testcase.name.endswith("_toplevel"):
|
||||
continue
|
||||
insert_uninit_checks(fn)
|
||||
insert_ref_count_opcodes(fn)
|
||||
actual.extend(format_func(fn))
|
||||
|
||||
assert_test_output(testcase, actual, "Invalid source code output", expected_output)
|
||||
429
venv/lib/python3.12/site-packages/mypyc/test/test_run.py
Normal file
429
venv/lib/python3.12/site-packages/mypyc/test/test_run.py
Normal file
@@ -0,0 +1,429 @@
|
||||
"""Test cases for building an C extension and running it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
import glob
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Iterator
|
||||
|
||||
from mypy import build
|
||||
from mypy.errors import CompileError
|
||||
from mypy.options import TYPE_VAR_TUPLE, UNPACK, Options
|
||||
from mypy.test.config import test_temp_dir
|
||||
from mypy.test.data import DataDrivenTestCase
|
||||
from mypy.test.helpers import assert_module_equivalence, perform_file_operations
|
||||
from mypyc.build import construct_groups
|
||||
from mypyc.codegen import emitmodule
|
||||
from mypyc.errors import Errors
|
||||
from mypyc.options import CompilerOptions
|
||||
from mypyc.test.test_serialization import check_serialization_roundtrip
|
||||
from mypyc.test.testutil import (
|
||||
ICODE_GEN_BUILTINS,
|
||||
TESTUTIL_PATH,
|
||||
MypycDataSuite,
|
||||
assert_test_output,
|
||||
fudge_dir_mtimes,
|
||||
show_c,
|
||||
use_custom_builtins,
|
||||
)
|
||||
|
||||
files = [
|
||||
"run-async.test",
|
||||
"run-misc.test",
|
||||
"run-functions.test",
|
||||
"run-integers.test",
|
||||
"run-i64.test",
|
||||
"run-i32.test",
|
||||
"run-i16.test",
|
||||
"run-u8.test",
|
||||
"run-floats.test",
|
||||
"run-math.test",
|
||||
"run-bools.test",
|
||||
"run-strings.test",
|
||||
"run-bytes.test",
|
||||
"run-tuples.test",
|
||||
"run-lists.test",
|
||||
"run-dicts.test",
|
||||
"run-sets.test",
|
||||
"run-primitives.test",
|
||||
"run-loops.test",
|
||||
"run-exceptions.test",
|
||||
"run-imports.test",
|
||||
"run-classes.test",
|
||||
"run-traits.test",
|
||||
"run-generators.test",
|
||||
"run-multimodule.test",
|
||||
"run-bench.test",
|
||||
"run-mypy-sim.test",
|
||||
"run-dunders.test",
|
||||
"run-singledispatch.test",
|
||||
"run-attrs.test",
|
||||
"run-python37.test",
|
||||
"run-python38.test",
|
||||
]
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
files.append("run-match.test")
|
||||
|
||||
setup_format = """\
|
||||
from setuptools import setup
|
||||
from mypyc.build import mypycify
|
||||
|
||||
setup(name='test_run_output',
|
||||
ext_modules=mypycify({}, separate={}, skip_cgen_input={!r}, strip_asserts=False,
|
||||
multi_file={}, opt_level='{}'),
|
||||
)
|
||||
"""
|
||||
|
||||
WORKDIR = "build"
|
||||
|
||||
|
||||
def run_setup(script_name: str, script_args: list[str]) -> bool:
|
||||
"""Run a setup script in a somewhat controlled environment.
|
||||
|
||||
This is adapted from code in distutils and our goal here is that is
|
||||
faster to not need to spin up a python interpreter to run it.
|
||||
|
||||
We had to fork it because the real run_setup swallows errors
|
||||
and KeyboardInterrupt with no way to recover them (!).
|
||||
The real version has some extra features that we removed since
|
||||
we weren't using them.
|
||||
|
||||
Returns whether the setup succeeded.
|
||||
"""
|
||||
save_argv = sys.argv.copy()
|
||||
g = {"__file__": script_name}
|
||||
try:
|
||||
try:
|
||||
sys.argv[0] = script_name
|
||||
sys.argv[1:] = script_args
|
||||
with open(script_name, "rb") as f:
|
||||
exec(f.read(), g)
|
||||
finally:
|
||||
sys.argv = save_argv
|
||||
except SystemExit as e:
|
||||
# distutils converts KeyboardInterrupt into a SystemExit with
|
||||
# "interrupted" as the argument. Convert it back so that
|
||||
# pytest will exit instead of just failing the test.
|
||||
if e.code == "interrupted":
|
||||
raise KeyboardInterrupt from e
|
||||
|
||||
return e.code == 0 or e.code is None
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def chdir_manager(target: str) -> Iterator[None]:
|
||||
dir = os.getcwd()
|
||||
os.chdir(target)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(dir)
|
||||
|
||||
|
||||
class TestRun(MypycDataSuite):
|
||||
"""Test cases that build a C extension and run code."""
|
||||
|
||||
files = files
|
||||
base_path = test_temp_dir
|
||||
optional_out = True
|
||||
multi_file = False
|
||||
separate = False # If True, using separate (incremental) compilation
|
||||
|
||||
def run_case(self, testcase: DataDrivenTestCase) -> None:
|
||||
# setup.py wants to be run from the root directory of the package, which we accommodate
|
||||
# by chdiring into tmp/
|
||||
with use_custom_builtins(
|
||||
os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase
|
||||
), chdir_manager("tmp"):
|
||||
self.run_case_inner(testcase)
|
||||
|
||||
def run_case_inner(self, testcase: DataDrivenTestCase) -> None:
|
||||
if not os.path.isdir(WORKDIR): # (one test puts something in build...)
|
||||
os.mkdir(WORKDIR)
|
||||
|
||||
text = "\n".join(testcase.input)
|
||||
|
||||
with open("native.py", "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
with open("interpreted.py", "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
|
||||
shutil.copyfile(TESTUTIL_PATH, "testutil.py")
|
||||
|
||||
step = 1
|
||||
self.run_case_step(testcase, step)
|
||||
|
||||
steps = testcase.find_steps()
|
||||
if steps == [[]]:
|
||||
steps = []
|
||||
|
||||
for operations in steps:
|
||||
# To make sure that any new changes get picked up as being
|
||||
# new by distutils, shift the mtime of all of the
|
||||
# generated artifacts back by a second.
|
||||
fudge_dir_mtimes(WORKDIR, -1)
|
||||
# On Ubuntu, changing the mtime doesn't work reliably. As
|
||||
# a workaround, sleep.
|
||||
#
|
||||
# TODO: Figure out a better approach, since this slows down tests.
|
||||
if sys.platform == "linux":
|
||||
time.sleep(1.0)
|
||||
|
||||
step += 1
|
||||
with chdir_manager(".."):
|
||||
perform_file_operations(operations)
|
||||
self.run_case_step(testcase, step)
|
||||
|
||||
def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> None:
|
||||
bench = testcase.config.getoption("--bench", False) and "Benchmark" in testcase.name
|
||||
|
||||
options = Options()
|
||||
options.use_builtins_fixtures = True
|
||||
options.show_traceback = True
|
||||
options.strict_optional = True
|
||||
options.python_version = sys.version_info[:2]
|
||||
options.export_types = True
|
||||
options.preserve_asts = True
|
||||
options.allow_empty_bodies = True
|
||||
options.incremental = self.separate
|
||||
options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK]
|
||||
|
||||
# Avoid checking modules/packages named 'unchecked', to provide a way
|
||||
# to test interacting with code we don't have types for.
|
||||
options.per_module_options["unchecked.*"] = {"follow_imports": "error"}
|
||||
|
||||
source = build.BuildSource("native.py", "native", None)
|
||||
sources = [source]
|
||||
module_names = ["native"]
|
||||
module_paths = ["native.py"]
|
||||
|
||||
# Hard code another module name to compile in the same compilation unit.
|
||||
to_delete = []
|
||||
for fn, text in testcase.files:
|
||||
fn = os.path.relpath(fn, test_temp_dir)
|
||||
|
||||
if os.path.basename(fn).startswith("other") and fn.endswith(".py"):
|
||||
name = fn.split(".")[0].replace(os.sep, ".")
|
||||
module_names.append(name)
|
||||
sources.append(build.BuildSource(fn, name, None))
|
||||
to_delete.append(fn)
|
||||
module_paths.append(fn)
|
||||
|
||||
shutil.copyfile(fn, os.path.join(os.path.dirname(fn), name + "_interpreted.py"))
|
||||
|
||||
for source in sources:
|
||||
options.per_module_options.setdefault(source.module, {})["mypyc"] = True
|
||||
|
||||
separate = (
|
||||
self.get_separate("\n".join(testcase.input), incremental_step)
|
||||
if self.separate
|
||||
else False
|
||||
)
|
||||
|
||||
groups = construct_groups(sources, separate, len(module_names) > 1)
|
||||
|
||||
try:
|
||||
compiler_options = CompilerOptions(multi_file=self.multi_file, separate=self.separate)
|
||||
result = emitmodule.parse_and_typecheck(
|
||||
sources=sources,
|
||||
options=options,
|
||||
compiler_options=compiler_options,
|
||||
groups=groups,
|
||||
alt_lib_path=".",
|
||||
)
|
||||
errors = Errors(options)
|
||||
ir, cfiles = emitmodule.compile_modules_to_c(
|
||||
result, compiler_options=compiler_options, errors=errors, groups=groups
|
||||
)
|
||||
if errors.num_errors:
|
||||
errors.flush_errors()
|
||||
assert False, "Compile error"
|
||||
except CompileError as e:
|
||||
for line in e.messages:
|
||||
print(fix_native_line_number(line, testcase.file, testcase.line))
|
||||
assert False, "Compile error"
|
||||
|
||||
# Check that serialization works on this IR. (Only on the first
|
||||
# step because the returned ir only includes updated code.)
|
||||
if incremental_step == 1:
|
||||
check_serialization_roundtrip(ir)
|
||||
|
||||
opt_level = int(os.environ.get("MYPYC_OPT_LEVEL", 0))
|
||||
debug_level = int(os.environ.get("MYPYC_DEBUG_LEVEL", 0))
|
||||
|
||||
setup_file = os.path.abspath(os.path.join(WORKDIR, "setup.py"))
|
||||
# We pass the C file information to the build script via setup.py unfortunately
|
||||
with open(setup_file, "w", encoding="utf-8") as f:
|
||||
f.write(
|
||||
setup_format.format(
|
||||
module_paths, separate, cfiles, self.multi_file, opt_level, debug_level
|
||||
)
|
||||
)
|
||||
|
||||
if not run_setup(setup_file, ["build_ext", "--inplace"]):
|
||||
if testcase.config.getoption("--mypyc-showc"):
|
||||
show_c(cfiles)
|
||||
assert False, "Compilation failed"
|
||||
|
||||
# Assert that an output file got created
|
||||
suffix = "pyd" if sys.platform == "win32" else "so"
|
||||
assert glob.glob(f"native.*.{suffix}") or glob.glob(f"native.{suffix}")
|
||||
|
||||
driver_path = "driver.py"
|
||||
if not os.path.isfile(driver_path):
|
||||
# No driver.py provided by test case. Use the default one
|
||||
# (mypyc/test-data/driver/driver.py) that calls each
|
||||
# function named test_*.
|
||||
default_driver = os.path.join(
|
||||
os.path.dirname(__file__), "..", "test-data", "driver", "driver.py"
|
||||
)
|
||||
shutil.copy(default_driver, driver_path)
|
||||
env = os.environ.copy()
|
||||
env["MYPYC_RUN_BENCH"] = "1" if bench else "0"
|
||||
|
||||
debugger = testcase.config.getoption("debugger")
|
||||
if debugger:
|
||||
if debugger == "lldb":
|
||||
subprocess.check_call(["lldb", "--", sys.executable, driver_path], env=env)
|
||||
elif debugger == "gdb":
|
||||
subprocess.check_call(["gdb", "--args", sys.executable, driver_path], env=env)
|
||||
else:
|
||||
assert False, "Unsupported debugger"
|
||||
# TODO: find a way to automatically disable capturing
|
||||
# stdin/stdout when in debugging mode
|
||||
assert False, (
|
||||
"Test can't pass in debugging mode. "
|
||||
"(Make sure to pass -s to pytest to interact with the debugger)"
|
||||
)
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, driver_path],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env,
|
||||
)
|
||||
if sys.version_info >= (3, 12):
|
||||
# TODO: testDecorators1 hangs on 3.12, remove this once fixed
|
||||
proc.wait(timeout=30)
|
||||
output = proc.communicate()[0].decode("utf8")
|
||||
outlines = output.splitlines()
|
||||
|
||||
if testcase.config.getoption("--mypyc-showc"):
|
||||
show_c(cfiles)
|
||||
if proc.returncode != 0:
|
||||
print()
|
||||
print("*** Exit status: %d" % proc.returncode)
|
||||
|
||||
# Verify output.
|
||||
if bench:
|
||||
print("Test output:")
|
||||
print(output)
|
||||
else:
|
||||
if incremental_step == 1:
|
||||
msg = "Invalid output"
|
||||
expected = testcase.output
|
||||
else:
|
||||
msg = f"Invalid output (step {incremental_step})"
|
||||
expected = testcase.output2.get(incremental_step, [])
|
||||
|
||||
if not expected:
|
||||
# Tweak some line numbers, but only if the expected output is empty,
|
||||
# as tweaked output might not match expected output.
|
||||
outlines = [
|
||||
fix_native_line_number(line, testcase.file, testcase.line) for line in outlines
|
||||
]
|
||||
assert_test_output(testcase, outlines, msg, expected)
|
||||
|
||||
if incremental_step > 1 and options.incremental:
|
||||
suffix = "" if incremental_step == 2 else str(incremental_step - 1)
|
||||
expected_rechecked = testcase.expected_rechecked_modules.get(incremental_step - 1)
|
||||
if expected_rechecked is not None:
|
||||
assert_module_equivalence(
|
||||
"rechecked" + suffix, expected_rechecked, result.manager.rechecked_modules
|
||||
)
|
||||
expected_stale = testcase.expected_stale_modules.get(incremental_step - 1)
|
||||
if expected_stale is not None:
|
||||
assert_module_equivalence(
|
||||
"stale" + suffix, expected_stale, result.manager.stale_modules
|
||||
)
|
||||
|
||||
assert proc.returncode == 0
|
||||
|
||||
def get_separate(self, program_text: str, incremental_step: int) -> Any:
|
||||
template = r"# separate{}: (\[.*\])$"
|
||||
m = re.search(template.format(incremental_step), program_text, flags=re.MULTILINE)
|
||||
if not m:
|
||||
m = re.search(template.format(""), program_text, flags=re.MULTILINE)
|
||||
if m:
|
||||
return ast.literal_eval(m.group(1))
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class TestRunMultiFile(TestRun):
|
||||
"""Run the main multi-module tests in multi-file compilation mode.
|
||||
|
||||
In multi-file mode each module gets compiled into a separate C file,
|
||||
but all modules (C files) are compiled together.
|
||||
"""
|
||||
|
||||
multi_file = True
|
||||
test_name_suffix = "_multi"
|
||||
files = ["run-multimodule.test", "run-mypy-sim.test"]
|
||||
|
||||
|
||||
class TestRunSeparate(TestRun):
|
||||
"""Run the main multi-module tests in separate compilation mode.
|
||||
|
||||
In this mode there are multiple compilation groups, which are compiled
|
||||
incrementally. Each group is compiled to a separate C file, and these C
|
||||
files are compiled separately.
|
||||
|
||||
Each compiled module is placed into a separate compilation group, unless
|
||||
overridden by a special comment. Consider this example:
|
||||
|
||||
# separate: [(["other.py", "other_b.py"], "stuff")]
|
||||
|
||||
This puts other.py and other_b.py into a compilation group named "stuff".
|
||||
Any files not mentioned in the comment will get single-file groups.
|
||||
"""
|
||||
|
||||
separate = True
|
||||
test_name_suffix = "_separate"
|
||||
files = ["run-multimodule.test", "run-mypy-sim.test"]
|
||||
|
||||
|
||||
def fix_native_line_number(message: str, fnam: str, delta: int) -> str:
|
||||
"""Update code locations in test case output to point to the .test file.
|
||||
|
||||
The description of the test case is written to native.py, and line numbers
|
||||
in test case output often are relative to native.py. This translates the
|
||||
line numbers to be relative to the .test file that contains the test case
|
||||
description, and also updates the file name to the .test file name.
|
||||
|
||||
Args:
|
||||
message: message to update
|
||||
fnam: path of the .test file
|
||||
delta: line number of the beginning of the test case in the .test file
|
||||
|
||||
Returns updated message (or original message if we couldn't find anything).
|
||||
"""
|
||||
fnam = os.path.basename(fnam)
|
||||
message = re.sub(
|
||||
r"native\.py:([0-9]+):", lambda m: "%s:%d:" % (fnam, int(m.group(1)) + delta), message
|
||||
)
|
||||
message = re.sub(
|
||||
r'"native.py", line ([0-9]+),',
|
||||
lambda m: '"%s", line %d,' % (fnam, int(m.group(1)) + delta),
|
||||
message,
|
||||
)
|
||||
return message
|
||||
@@ -0,0 +1,108 @@
|
||||
"""Functions to check that serialization round-tripped properly."""
|
||||
|
||||
# This file is named test_serialization.py even though it doesn't
|
||||
# contain its own tests so that pytest will rewrite the asserts...
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import Any
|
||||
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature
|
||||
from mypyc.ir.module_ir import ModuleIR, deserialize_modules
|
||||
from mypyc.ir.ops import DeserMaps
|
||||
from mypyc.ir.rtypes import RType
|
||||
from mypyc.sametype import is_same_signature, is_same_type
|
||||
|
||||
|
||||
def get_dict(x: Any) -> dict[str, Any]:
|
||||
if hasattr(x, "__mypyc_attrs__"):
|
||||
return {k: getattr(x, k) for k in x.__mypyc_attrs__ if hasattr(x, k)}
|
||||
else:
|
||||
return dict(x.__dict__)
|
||||
|
||||
|
||||
def get_function_dict(x: FuncIR) -> dict[str, Any]:
|
||||
"""Get a dict of function attributes safe to compare across serialization"""
|
||||
d = get_dict(x)
|
||||
d.pop("blocks", None)
|
||||
d.pop("env", None)
|
||||
return d
|
||||
|
||||
|
||||
def assert_blobs_same(x: Any, y: Any, trail: tuple[Any, ...]) -> None:
|
||||
"""Compare two blobs of IR as best we can.
|
||||
|
||||
FuncDecls, FuncIRs, and ClassIRs are compared by fullname to avoid
|
||||
infinite recursion.
|
||||
(More detailed comparisons should be done manually.)
|
||||
|
||||
Types and signatures are compared using mypyc.sametype.
|
||||
|
||||
Containers are compared recursively.
|
||||
|
||||
Anything else is compared with ==.
|
||||
|
||||
The `trail` argument is used in error messages.
|
||||
"""
|
||||
|
||||
assert type(x) is type(y), (f"Type mismatch at {trail}", type(x), type(y))
|
||||
if isinstance(x, (FuncDecl, FuncIR, ClassIR)):
|
||||
assert x.fullname == y.fullname, f"Name mismatch at {trail}"
|
||||
elif isinstance(x, dict):
|
||||
assert len(x.keys()) == len(y.keys()), f"Keys mismatch at {trail}"
|
||||
for (xk, xv), (yk, yv) in zip(x.items(), y.items()):
|
||||
assert_blobs_same(xk, yk, trail + ("keys",))
|
||||
assert_blobs_same(xv, yv, trail + (xk,))
|
||||
elif isinstance(x, dict):
|
||||
assert x.keys() == y.keys(), f"Keys mismatch at {trail}"
|
||||
for k in x.keys():
|
||||
assert_blobs_same(x[k], y[k], trail + (k,))
|
||||
elif isinstance(x, Iterable) and not isinstance(x, (str, set)):
|
||||
# Special case iterables to generate better assert error messages.
|
||||
# We can't use this for sets since the ordering is unpredictable,
|
||||
# and strings should be treated as atomic values.
|
||||
for i, (xv, yv) in enumerate(zip(x, y)):
|
||||
assert_blobs_same(xv, yv, trail + (i,))
|
||||
elif isinstance(x, RType):
|
||||
assert is_same_type(x, y), f"RType mismatch at {trail}"
|
||||
elif isinstance(x, FuncSignature):
|
||||
assert is_same_signature(x, y), f"Signature mismatch at {trail}"
|
||||
else:
|
||||
assert x == y, f"Value mismatch at {trail}"
|
||||
|
||||
|
||||
def assert_modules_same(ir1: ModuleIR, ir2: ModuleIR) -> None:
|
||||
"""Assert that two module IRs are the same (*).
|
||||
|
||||
* Or rather, as much as we care about preserving across
|
||||
serialization. We drop the actual IR bodies of functions but try
|
||||
to preserve everything else.
|
||||
"""
|
||||
assert ir1.fullname == ir2.fullname
|
||||
|
||||
assert ir1.imports == ir2.imports
|
||||
|
||||
for cls1, cls2 in zip(ir1.classes, ir2.classes):
|
||||
assert_blobs_same(get_dict(cls1), get_dict(cls2), (ir1.fullname, cls1.fullname))
|
||||
|
||||
for fn1, fn2 in zip(ir1.functions, ir2.functions):
|
||||
assert_blobs_same(
|
||||
get_function_dict(fn1), get_function_dict(fn2), (ir1.fullname, fn1.fullname)
|
||||
)
|
||||
assert_blobs_same(get_dict(fn1.decl), get_dict(fn2.decl), (ir1.fullname, fn1.fullname))
|
||||
|
||||
assert_blobs_same(ir1.final_names, ir2.final_names, (ir1.fullname, "final_names"))
|
||||
|
||||
|
||||
def check_serialization_roundtrip(irs: dict[str, ModuleIR]) -> None:
|
||||
"""Check that we can serialize modules out and deserialize them to the same thing."""
|
||||
serialized = {k: ir.serialize() for k, ir in irs.items()}
|
||||
|
||||
ctx = DeserMaps({}, {})
|
||||
irs2 = deserialize_modules(serialized, ctx)
|
||||
assert irs.keys() == irs2.keys()
|
||||
|
||||
for k in irs:
|
||||
assert_modules_same(irs[k], irs2[k])
|
||||
112
venv/lib/python3.12/site-packages/mypyc/test/test_struct.py
Normal file
112
venv/lib/python3.12/site-packages/mypyc/test/test_struct.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.ir.rtypes import (
|
||||
RStruct,
|
||||
bool_rprimitive,
|
||||
int32_rprimitive,
|
||||
int64_rprimitive,
|
||||
int_rprimitive,
|
||||
object_rprimitive,
|
||||
)
|
||||
from mypyc.rt_subtype import is_runtime_subtype
|
||||
|
||||
|
||||
class TestStruct(unittest.TestCase):
|
||||
def test_struct_offsets(self) -> None:
|
||||
# test per-member alignment
|
||||
r = RStruct("", [], [bool_rprimitive, int32_rprimitive, int64_rprimitive])
|
||||
assert r.size == 16
|
||||
assert r.offsets == [0, 4, 8]
|
||||
|
||||
# test final alignment
|
||||
r1 = RStruct("", [], [bool_rprimitive, bool_rprimitive])
|
||||
assert r1.size == 2
|
||||
assert r1.offsets == [0, 1]
|
||||
r2 = RStruct("", [], [int32_rprimitive, bool_rprimitive])
|
||||
r3 = RStruct("", [], [int64_rprimitive, bool_rprimitive])
|
||||
assert r2.offsets == [0, 4]
|
||||
assert r3.offsets == [0, 8]
|
||||
assert r2.size == 8
|
||||
assert r3.size == 16
|
||||
|
||||
r4 = RStruct("", [], [bool_rprimitive, bool_rprimitive, bool_rprimitive, int32_rprimitive])
|
||||
assert r4.size == 8
|
||||
assert r4.offsets == [0, 1, 2, 4]
|
||||
|
||||
# test nested struct
|
||||
r5 = RStruct("", [], [bool_rprimitive, r])
|
||||
assert r5.offsets == [0, 8]
|
||||
assert r5.size == 24
|
||||
r6 = RStruct("", [], [int32_rprimitive, r5])
|
||||
assert r6.offsets == [0, 8]
|
||||
assert r6.size == 32
|
||||
# test nested struct with alignment less than 8
|
||||
r7 = RStruct("", [], [bool_rprimitive, r4])
|
||||
assert r7.offsets == [0, 4]
|
||||
assert r7.size == 12
|
||||
|
||||
def test_struct_str(self) -> None:
|
||||
r = RStruct("Foo", ["a", "b"], [bool_rprimitive, object_rprimitive])
|
||||
assert str(r) == "Foo{a:bool, b:object}"
|
||||
assert (
|
||||
repr(r) == "<RStruct Foo{a:<RPrimitive builtins.bool>, "
|
||||
"b:<RPrimitive builtins.object>}>"
|
||||
)
|
||||
r1 = RStruct("Bar", ["c"], [int32_rprimitive])
|
||||
assert str(r1) == "Bar{c:i32}"
|
||||
assert repr(r1) == "<RStruct Bar{c:<RPrimitive i32>}>"
|
||||
r2 = RStruct("Baz", [], [])
|
||||
assert str(r2) == "Baz{}"
|
||||
assert repr(r2) == "<RStruct Baz{}>"
|
||||
|
||||
def test_runtime_subtype(self) -> None:
|
||||
# right type to check with
|
||||
r = RStruct("Foo", ["a", "b"], [bool_rprimitive, int_rprimitive])
|
||||
|
||||
# using the exact same fields
|
||||
r1 = RStruct("Foo", ["a", "b"], [bool_rprimitive, int_rprimitive])
|
||||
|
||||
# names different
|
||||
r2 = RStruct("Bar", ["c", "b"], [bool_rprimitive, int_rprimitive])
|
||||
|
||||
# name different
|
||||
r3 = RStruct("Baz", ["a", "b"], [bool_rprimitive, int_rprimitive])
|
||||
|
||||
# type different
|
||||
r4 = RStruct("FooBar", ["a", "b"], [bool_rprimitive, int32_rprimitive])
|
||||
|
||||
# number of types different
|
||||
r5 = RStruct(
|
||||
"FooBarBaz", ["a", "b", "c"], [bool_rprimitive, int_rprimitive, bool_rprimitive]
|
||||
)
|
||||
|
||||
assert is_runtime_subtype(r1, r) is True
|
||||
assert is_runtime_subtype(r2, r) is False
|
||||
assert is_runtime_subtype(r3, r) is False
|
||||
assert is_runtime_subtype(r4, r) is False
|
||||
assert is_runtime_subtype(r5, r) is False
|
||||
|
||||
def test_eq_and_hash(self) -> None:
|
||||
r = RStruct("Foo", ["a", "b"], [bool_rprimitive, int_rprimitive])
|
||||
|
||||
# using the exact same fields
|
||||
r1 = RStruct("Foo", ["a", "b"], [bool_rprimitive, int_rprimitive])
|
||||
assert hash(r) == hash(r1)
|
||||
assert r == r1
|
||||
|
||||
# different name
|
||||
r2 = RStruct("Foq", ["a", "b"], [bool_rprimitive, int_rprimitive])
|
||||
assert hash(r) != hash(r2)
|
||||
assert r != r2
|
||||
|
||||
# different names
|
||||
r3 = RStruct("Foo", ["a", "c"], [bool_rprimitive, int_rprimitive])
|
||||
assert hash(r) != hash(r3)
|
||||
assert r != r3
|
||||
|
||||
# different type
|
||||
r4 = RStruct("Foo", ["a", "b"], [bool_rprimitive, int_rprimitive, bool_rprimitive])
|
||||
assert hash(r) != hash(r4)
|
||||
assert r != r4
|
||||
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.ir.class_ir import ClassIR
|
||||
from mypyc.ir.rtypes import (
|
||||
RInstance,
|
||||
RTuple,
|
||||
RUnion,
|
||||
bool_rprimitive,
|
||||
int_rprimitive,
|
||||
list_rprimitive,
|
||||
object_rprimitive,
|
||||
)
|
||||
|
||||
|
||||
class TestTupleNames(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.inst_a = RInstance(ClassIR("A", "__main__"))
|
||||
self.inst_b = RInstance(ClassIR("B", "__main__"))
|
||||
|
||||
def test_names(self) -> None:
|
||||
assert RTuple([int_rprimitive, int_rprimitive]).unique_id == "T2II"
|
||||
assert RTuple([list_rprimitive, object_rprimitive, self.inst_a]).unique_id == "T3OOO"
|
||||
assert RTuple([list_rprimitive, object_rprimitive, self.inst_b]).unique_id == "T3OOO"
|
||||
assert RTuple([]).unique_id == "T0"
|
||||
assert (
|
||||
RTuple([RTuple([]), RTuple([int_rprimitive, int_rprimitive])]).unique_id == "T2T0T2II"
|
||||
)
|
||||
assert (
|
||||
RTuple([bool_rprimitive, RUnion([bool_rprimitive, int_rprimitive])]).unique_id
|
||||
== "T2CO"
|
||||
)
|
||||
97
venv/lib/python3.12/site-packages/mypyc/test/test_typeops.py
Normal file
97
venv/lib/python3.12/site-packages/mypyc/test/test_typeops.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Test cases for various RType operations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from mypyc.ir.rtypes import (
|
||||
RUnion,
|
||||
bit_rprimitive,
|
||||
bool_rprimitive,
|
||||
int16_rprimitive,
|
||||
int32_rprimitive,
|
||||
int64_rprimitive,
|
||||
int_rprimitive,
|
||||
object_rprimitive,
|
||||
short_int_rprimitive,
|
||||
str_rprimitive,
|
||||
)
|
||||
from mypyc.rt_subtype import is_runtime_subtype
|
||||
from mypyc.subtype import is_subtype
|
||||
|
||||
native_int_types = [int64_rprimitive, int32_rprimitive, int16_rprimitive]
|
||||
|
||||
|
||||
class TestSubtype(unittest.TestCase):
|
||||
def test_bit(self) -> None:
|
||||
assert is_subtype(bit_rprimitive, bool_rprimitive)
|
||||
assert is_subtype(bit_rprimitive, int_rprimitive)
|
||||
assert is_subtype(bit_rprimitive, short_int_rprimitive)
|
||||
for rt in native_int_types:
|
||||
assert is_subtype(bit_rprimitive, rt)
|
||||
|
||||
def test_bool(self) -> None:
|
||||
assert not is_subtype(bool_rprimitive, bit_rprimitive)
|
||||
assert is_subtype(bool_rprimitive, int_rprimitive)
|
||||
assert is_subtype(bool_rprimitive, short_int_rprimitive)
|
||||
for rt in native_int_types:
|
||||
assert is_subtype(bool_rprimitive, rt)
|
||||
|
||||
def test_int64(self) -> None:
|
||||
assert is_subtype(int64_rprimitive, int64_rprimitive)
|
||||
assert is_subtype(int64_rprimitive, int_rprimitive)
|
||||
assert not is_subtype(int64_rprimitive, short_int_rprimitive)
|
||||
assert not is_subtype(int64_rprimitive, int32_rprimitive)
|
||||
assert not is_subtype(int64_rprimitive, int16_rprimitive)
|
||||
|
||||
def test_int32(self) -> None:
|
||||
assert is_subtype(int32_rprimitive, int32_rprimitive)
|
||||
assert is_subtype(int32_rprimitive, int_rprimitive)
|
||||
assert not is_subtype(int32_rprimitive, short_int_rprimitive)
|
||||
assert not is_subtype(int32_rprimitive, int64_rprimitive)
|
||||
assert not is_subtype(int32_rprimitive, int16_rprimitive)
|
||||
|
||||
def test_int16(self) -> None:
|
||||
assert is_subtype(int16_rprimitive, int16_rprimitive)
|
||||
assert is_subtype(int16_rprimitive, int_rprimitive)
|
||||
assert not is_subtype(int16_rprimitive, short_int_rprimitive)
|
||||
assert not is_subtype(int16_rprimitive, int64_rprimitive)
|
||||
assert not is_subtype(int16_rprimitive, int32_rprimitive)
|
||||
|
||||
|
||||
class TestRuntimeSubtype(unittest.TestCase):
|
||||
def test_bit(self) -> None:
|
||||
assert is_runtime_subtype(bit_rprimitive, bool_rprimitive)
|
||||
assert not is_runtime_subtype(bit_rprimitive, int_rprimitive)
|
||||
|
||||
def test_bool(self) -> None:
|
||||
assert not is_runtime_subtype(bool_rprimitive, bit_rprimitive)
|
||||
assert not is_runtime_subtype(bool_rprimitive, int_rprimitive)
|
||||
|
||||
def test_union(self) -> None:
|
||||
bool_int_mix = RUnion([bool_rprimitive, int_rprimitive])
|
||||
assert not is_runtime_subtype(bool_int_mix, short_int_rprimitive)
|
||||
assert not is_runtime_subtype(bool_int_mix, int_rprimitive)
|
||||
assert not is_runtime_subtype(short_int_rprimitive, bool_int_mix)
|
||||
assert not is_runtime_subtype(int_rprimitive, bool_int_mix)
|
||||
|
||||
|
||||
class TestUnionSimplification(unittest.TestCase):
|
||||
def test_simple_type_result(self) -> None:
|
||||
assert RUnion.make_simplified_union([int_rprimitive]) == int_rprimitive
|
||||
|
||||
def test_remove_duplicate(self) -> None:
|
||||
assert RUnion.make_simplified_union([int_rprimitive, int_rprimitive]) == int_rprimitive
|
||||
|
||||
def test_cannot_simplify(self) -> None:
|
||||
assert RUnion.make_simplified_union(
|
||||
[int_rprimitive, str_rprimitive, object_rprimitive]
|
||||
) == RUnion([int_rprimitive, str_rprimitive, object_rprimitive])
|
||||
|
||||
def test_nested(self) -> None:
|
||||
assert RUnion.make_simplified_union(
|
||||
[int_rprimitive, RUnion([str_rprimitive, int_rprimitive])]
|
||||
) == RUnion([int_rprimitive, str_rprimitive])
|
||||
assert RUnion.make_simplified_union(
|
||||
[int_rprimitive, RUnion([str_rprimitive, RUnion([int_rprimitive])])]
|
||||
) == RUnion([int_rprimitive, str_rprimitive])
|
||||
283
venv/lib/python3.12/site-packages/mypyc/test/testutil.py
Normal file
283
venv/lib/python3.12/site-packages/mypyc/test/testutil.py
Normal file
@@ -0,0 +1,283 @@
|
||||
"""Helpers for writing tests"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
from typing import Callable, Iterator
|
||||
|
||||
from mypy import build
|
||||
from mypy.errors import CompileError
|
||||
from mypy.options import Options
|
||||
from mypy.test.config import test_temp_dir
|
||||
from mypy.test.data import DataDrivenTestCase, DataSuite
|
||||
from mypy.test.helpers import assert_string_arrays_equal
|
||||
from mypyc.analysis.ircheck import assert_func_ir_valid
|
||||
from mypyc.common import IS_32_BIT_PLATFORM, PLATFORM_SIZE
|
||||
from mypyc.errors import Errors
|
||||
from mypyc.ir.func_ir import FuncIR
|
||||
from mypyc.ir.module_ir import ModuleIR
|
||||
from mypyc.irbuild.main import build_ir
|
||||
from mypyc.irbuild.mapper import Mapper
|
||||
from mypyc.options import CompilerOptions
|
||||
from mypyc.test.config import test_data_prefix
|
||||
|
||||
# The builtins stub used during icode generation test cases.
|
||||
ICODE_GEN_BUILTINS = os.path.join(test_data_prefix, "fixtures/ir.py")
|
||||
# The testutil support library
|
||||
TESTUTIL_PATH = os.path.join(test_data_prefix, "fixtures/testutil.py")
|
||||
|
||||
|
||||
class MypycDataSuite(DataSuite):
|
||||
# Need to list no files, since this will be picked up as a suite of tests
|
||||
files: list[str] = []
|
||||
data_prefix = test_data_prefix
|
||||
|
||||
|
||||
def builtins_wrapper(
|
||||
func: Callable[[DataDrivenTestCase], None], path: str
|
||||
) -> Callable[[DataDrivenTestCase], None]:
|
||||
"""Decorate a function that implements a data-driven test case to copy an
|
||||
alternative builtins module implementation in place before performing the
|
||||
test case. Clean up after executing the test case.
|
||||
"""
|
||||
return lambda testcase: perform_test(func, path, testcase)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def use_custom_builtins(builtins_path: str, testcase: DataDrivenTestCase) -> Iterator[None]:
|
||||
for path, _ in testcase.files:
|
||||
if os.path.basename(path) == "builtins.pyi":
|
||||
default_builtins = False
|
||||
break
|
||||
else:
|
||||
# Use default builtins.
|
||||
builtins = os.path.abspath(os.path.join(test_temp_dir, "builtins.pyi"))
|
||||
shutil.copyfile(builtins_path, builtins)
|
||||
default_builtins = True
|
||||
|
||||
# Actually perform the test case.
|
||||
try:
|
||||
yield None
|
||||
finally:
|
||||
if default_builtins:
|
||||
# Clean up.
|
||||
os.remove(builtins)
|
||||
|
||||
|
||||
def perform_test(
|
||||
func: Callable[[DataDrivenTestCase], None], builtins_path: str, testcase: DataDrivenTestCase
|
||||
) -> None:
|
||||
for path, _ in testcase.files:
|
||||
if os.path.basename(path) == "builtins.py":
|
||||
default_builtins = False
|
||||
break
|
||||
else:
|
||||
# Use default builtins.
|
||||
builtins = os.path.join(test_temp_dir, "builtins.py")
|
||||
shutil.copyfile(builtins_path, builtins)
|
||||
default_builtins = True
|
||||
|
||||
# Actually perform the test case.
|
||||
func(testcase)
|
||||
|
||||
if default_builtins:
|
||||
# Clean up.
|
||||
os.remove(builtins)
|
||||
|
||||
|
||||
def build_ir_for_single_file(
|
||||
input_lines: list[str], compiler_options: CompilerOptions | None = None
|
||||
) -> list[FuncIR]:
|
||||
return build_ir_for_single_file2(input_lines, compiler_options).functions
|
||||
|
||||
|
||||
def build_ir_for_single_file2(
|
||||
input_lines: list[str], compiler_options: CompilerOptions | None = None
|
||||
) -> ModuleIR:
|
||||
program_text = "\n".join(input_lines)
|
||||
|
||||
# By default generate IR compatible with the earliest supported Python C API.
|
||||
# If a test needs more recent API features, this should be overridden.
|
||||
compiler_options = compiler_options or CompilerOptions(capi_version=(3, 7))
|
||||
options = Options()
|
||||
options.show_traceback = True
|
||||
options.hide_error_codes = True
|
||||
options.use_builtins_fixtures = True
|
||||
options.strict_optional = True
|
||||
options.python_version = compiler_options.python_version or (3, 6)
|
||||
options.export_types = True
|
||||
options.preserve_asts = True
|
||||
options.allow_empty_bodies = True
|
||||
options.per_module_options["__main__"] = {"mypyc": True}
|
||||
|
||||
source = build.BuildSource("main", "__main__", program_text)
|
||||
# Construct input as a single single.
|
||||
# Parse and type check the input program.
|
||||
result = build.build(sources=[source], options=options, alt_lib_path=test_temp_dir)
|
||||
if result.errors:
|
||||
raise CompileError(result.errors)
|
||||
|
||||
errors = Errors(options)
|
||||
modules = build_ir(
|
||||
[result.files["__main__"]],
|
||||
result.graph,
|
||||
result.types,
|
||||
Mapper({"__main__": None}),
|
||||
compiler_options,
|
||||
errors,
|
||||
)
|
||||
if errors.num_errors:
|
||||
raise CompileError(errors.new_messages())
|
||||
|
||||
module = list(modules.values())[0]
|
||||
for fn in module.functions:
|
||||
assert_func_ir_valid(fn)
|
||||
return module
|
||||
|
||||
|
||||
def update_testcase_output(testcase: DataDrivenTestCase, output: list[str]) -> None:
|
||||
# TODO: backport this to mypy
|
||||
assert testcase.old_cwd is not None, "test was not properly set up"
|
||||
testcase_path = os.path.join(testcase.old_cwd, testcase.file)
|
||||
with open(testcase_path) as f:
|
||||
data_lines = f.read().splitlines()
|
||||
|
||||
# We can't rely on the test line numbers to *find* the test, since
|
||||
# we might fix multiple tests in a run. So find it by the case
|
||||
# header. Give up if there are multiple tests with the same name.
|
||||
test_slug = f"[case {testcase.name}]"
|
||||
if data_lines.count(test_slug) != 1:
|
||||
return
|
||||
start_idx = data_lines.index(test_slug)
|
||||
stop_idx = start_idx + 11
|
||||
while stop_idx < len(data_lines) and not data_lines[stop_idx].startswith("[case "):
|
||||
stop_idx += 1
|
||||
|
||||
test = data_lines[start_idx:stop_idx]
|
||||
out_start = test.index("[out]")
|
||||
test[out_start + 1 :] = output
|
||||
data_lines[start_idx:stop_idx] = test + [""]
|
||||
data = "\n".join(data_lines)
|
||||
|
||||
with open(testcase_path, "w") as f:
|
||||
print(data, file=f)
|
||||
|
||||
|
||||
def assert_test_output(
|
||||
testcase: DataDrivenTestCase,
|
||||
actual: list[str],
|
||||
message: str,
|
||||
expected: list[str] | None = None,
|
||||
formatted: list[str] | None = None,
|
||||
) -> None:
|
||||
__tracebackhide__ = True
|
||||
|
||||
expected_output = expected if expected is not None else testcase.output
|
||||
if expected_output != actual and testcase.config.getoption("--update-data", False):
|
||||
update_testcase_output(testcase, actual)
|
||||
|
||||
assert_string_arrays_equal(
|
||||
expected_output, actual, f"{message} ({testcase.file}, line {testcase.line})"
|
||||
)
|
||||
|
||||
|
||||
def get_func_names(expected: list[str]) -> list[str]:
|
||||
res = []
|
||||
for s in expected:
|
||||
m = re.match(r"def ([_a-zA-Z0-9.*$]+)\(", s)
|
||||
if m:
|
||||
res.append(m.group(1))
|
||||
return res
|
||||
|
||||
|
||||
def remove_comment_lines(a: list[str]) -> list[str]:
|
||||
"""Return a copy of array with comments removed.
|
||||
|
||||
Lines starting with '--' (but not with '---') are removed.
|
||||
"""
|
||||
r = []
|
||||
for s in a:
|
||||
if s.strip().startswith("--") and not s.strip().startswith("---"):
|
||||
pass
|
||||
else:
|
||||
r.append(s)
|
||||
return r
|
||||
|
||||
|
||||
def print_with_line_numbers(s: str) -> None:
|
||||
lines = s.splitlines()
|
||||
for i, line in enumerate(lines):
|
||||
print("%-4d %s" % (i + 1, line))
|
||||
|
||||
|
||||
def heading(text: str) -> None:
|
||||
print("=" * 20 + " " + text + " " + "=" * 20)
|
||||
|
||||
|
||||
def show_c(cfiles: list[list[tuple[str, str]]]) -> None:
|
||||
heading("Generated C")
|
||||
for group in cfiles:
|
||||
for cfile, ctext in group:
|
||||
print(f"== {cfile} ==")
|
||||
print_with_line_numbers(ctext)
|
||||
heading("End C")
|
||||
|
||||
|
||||
def fudge_dir_mtimes(dir: str, delta: int) -> None:
|
||||
for dirpath, _, filenames in os.walk(dir):
|
||||
for name in filenames:
|
||||
path = os.path.join(dirpath, name)
|
||||
new_mtime = os.stat(path).st_mtime + delta
|
||||
os.utime(path, times=(new_mtime, new_mtime))
|
||||
|
||||
|
||||
def replace_word_size(text: list[str]) -> list[str]:
|
||||
"""Replace WORDSIZE with platform specific word sizes"""
|
||||
result = []
|
||||
for line in text:
|
||||
index = line.find("WORD_SIZE")
|
||||
if index != -1:
|
||||
# get 'WORDSIZE*n' token
|
||||
word_size_token = line[index:].split()[0]
|
||||
n = int(word_size_token[10:])
|
||||
replace_str = str(PLATFORM_SIZE * n)
|
||||
result.append(line.replace(word_size_token, replace_str))
|
||||
else:
|
||||
result.append(line)
|
||||
return result
|
||||
|
||||
|
||||
def infer_ir_build_options_from_test_name(name: str) -> CompilerOptions | None:
|
||||
"""Look for magic substrings in test case name to set compiler options.
|
||||
|
||||
Return None if the test case should be skipped (always pass).
|
||||
|
||||
Supported naming conventions:
|
||||
|
||||
*_64bit*:
|
||||
Run test case only on 64-bit platforms
|
||||
*_32bit*:
|
||||
Run test caseonly on 32-bit platforms
|
||||
*_python3_8* (or for any Python version):
|
||||
Use Python 3.8+ C API features (default: lowest supported version)
|
||||
*StripAssert*:
|
||||
Don't generate code for assert statements
|
||||
"""
|
||||
# If this is specific to some bit width, always pass if platform doesn't match.
|
||||
if "_64bit" in name and IS_32_BIT_PLATFORM:
|
||||
return None
|
||||
if "_32bit" in name and not IS_32_BIT_PLATFORM:
|
||||
return None
|
||||
options = CompilerOptions(strip_asserts="StripAssert" in name, capi_version=(3, 7))
|
||||
# A suffix like _python3.8 is used to set the target C API version.
|
||||
m = re.search(r"_python([3-9]+)_([0-9]+)(_|\b)", name)
|
||||
if m:
|
||||
options.capi_version = (int(m.group(1)), int(m.group(2)))
|
||||
options.python_version = options.capi_version
|
||||
elif "_py" in name or "_Python" in name:
|
||||
assert False, f"Invalid _py* suffix (should be _pythonX_Y): {name}"
|
||||
return options
|
||||
Reference in New Issue
Block a user