Skip to content

Commit 67caabb

Browse files
committed
Add tests for str and repr
1 parent 7e8a71b commit 67caabb

File tree

5 files changed

+102
-8
lines changed

5 files changed

+102
-8
lines changed

mypyc/codegen/emitclass.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
generate_len_wrapper,
1818
generate_richcompare_wrapper,
1919
generate_set_del_item_wrapper,
20+
generate_str_wrapper,
2021
)
2122
from mypyc.common import BITMAP_BITS, BITMAP_TYPE, NATIVE_PREFIX, PREFIX, REG_PREFIX
2223
from mypyc.ir.class_ir import ClassIR, VTableEntries
@@ -44,8 +45,8 @@ def wrapper_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
4445
SLOT_DEFS: SlotTable = {
4546
"__init__": ("tp_init", lambda c, t, e: generate_init_for_class(c, t, e)),
4647
"__call__": ("tp_call", lambda c, t, e: generate_call_wrapper(c, t, e)),
47-
"__str__": ("tp_str", native_slot),
48-
"__repr__": ("tp_repr", native_slot),
48+
"__str__": ("tp_str", generate_str_wrapper),
49+
"__repr__": ("tp_repr", generate_str_wrapper),
4950
"__next__": ("tp_iternext", native_slot),
5051
"__iter__": ("tp_iter", native_slot),
5152
"__hash__": ("tp_hash", generate_hash_wrapper),

mypyc/codegen/emitwrapper.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,20 @@ def generate_get_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
554554
return name
555555

556556

557+
def generate_str_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
558+
"""Generates a wrapper for native __str__ and __repr__ methods."""
559+
name = f"{DUNDER_PREFIX}{fn.name}{cl.name_prefix(emitter.names)}"
560+
emitter.emit_line(f"static PyObject* {name}(PyObject *self) {{")
561+
unbox_value_if_required("self", "obj_self", fn.args[0].type, emitter)
562+
emitter.emit_line(
563+
"return {}{}{}(obj_self);".format(
564+
emitter.get_group_prefix(fn.decl), NATIVE_PREFIX, fn.cname(emitter.names)
565+
)
566+
)
567+
emitter.emit_line("}")
568+
return name
569+
570+
557571
def generate_hash_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
558572
"""Generates a wrapper for native __hash__ methods."""
559573
name = f"{DUNDER_PREFIX}{fn.name}{cl.name_prefix(emitter.names)}"

mypyc/ir/class_ir.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,6 @@ def struct_name(self, names: NameGenerator) -> str:
300300
def struct_name2(self) -> str:
301301
return f"{exported_name(self.fullname)}Object"
302302

303-
def struct_data_name(self) -> str:
304-
return f"{exported_name(self.fullname)}Data"
305-
306303
def get_method_and_class(
307304
self, name: str, *, prefer_method: bool = False
308305
) -> tuple[FuncIR, ClassIR] | None:

mypyc/irbuild/prepare.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ def build_type_map(
7979
module_classes = [node for node in module.defs if isinstance(node, ClassDef)]
8080
classes.extend([(module, cdef) for cdef in module_classes])
8181

82+
module_by_fullname = {module.fullname: module for module in modules}
83+
8284
# Collect all class mappings so that we can bind arbitrary class name
8385
# references even if there are import cycles.
8486
for module, cdef in classes:
@@ -92,9 +94,8 @@ def build_type_map(
9294
is_value_type=is_value_type(cdef),
9395
)
9496

95-
if class_ir.is_value_type and (not is_immutable(cdef) or not cdef.info.is_final):
96-
# Because the value type have semantic differences we can not just ignore it
97-
errors.error("Value types must be immutable and final", module.path, cdef.line)
97+
if class_ir.is_value_type:
98+
check_value_type(cdef, errors, module_by_fullname)
9899

99100
if class_ir.is_ext_class:
100101
class_ir.deletable = cdef.info.deletable_attributes.copy()
@@ -144,6 +145,33 @@ def build_type_map(
144145
)
145146

146147

148+
def check_value_type(
149+
cdef: ClassDef, errors: Errors, module_by_fullname: dict[str, MypyFile]
150+
) -> None:
151+
if not is_immutable(cdef) or not cdef.info.is_final:
152+
module = module_by_fullname.get(cdef.info.module_name)
153+
# Because the value type have semantic differences we can not just ignore it
154+
errors.error("Value types must be immutable and final", module.path, cdef.line)
155+
156+
for mtd_name in (
157+
"__iter__",
158+
"__next__",
159+
"__enter__",
160+
"__exit__",
161+
"__getitem__",
162+
"__setitem__",
163+
"__delitem__",
164+
):
165+
mtd = cdef.info.get_method(mtd_name)
166+
if mtd is not None:
167+
module = module_by_fullname.get(mtd.info.module_name)
168+
errors.error(
169+
f"Value types must not define method '{mtd_name}'",
170+
module.path if module else "",
171+
mtd.line,
172+
)
173+
174+
147175
def is_from_module(node: SymbolNode, module: MypyFile) -> bool:
148176
return node.fullname == module.fullname + "." + node.name
149177

mypyc/test-data/run-valuetype.test

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,15 @@ class A:
237237
def __contains__(self, item: object) -> bool:
238238
return isinstance(item, i32) and item == self.x
239239

240+
def __bool__(self) -> bool:
241+
return self.x != 0
242+
243+
def __str__(self) -> str:
244+
return f"A({self.x})"
245+
246+
def __repr__(self) -> str:
247+
return f"A({self.x})"
248+
240249
def test_value_type_in_key() -> None:
241250
a = A(1, 2)
242251
d = {a: 1}
@@ -246,10 +255,34 @@ def test_value_type_in_val() -> None:
246255
a = A(1, 2)
247256
d = {1: a}
248257
assert d[1].x == 1
258+
assert d[1] == A(1, 2)
259+
260+
def test_value_type_in_set() -> None:
261+
a = A(1, 2)
262+
s = {a}
263+
assert A(1, 2) in s
264+
265+
def test_hash() -> None:
266+
a = A(1, 2)
267+
assert hash(a) == hash(1)
268+
269+
def test_eq() -> None:
270+
assert A(1, 2) == A(1, 2)
271+
assert A(1, 2) != A(2, 3)
272+
assert A(1, 2) != 1
273+
assert 1 != A(1, 2)
274+
275+
def test_bool() -> None:
276+
assert bool(A(1, 2))
277+
assert not bool(A(0, 2))
249278

250279
def test_len() -> None:
251280
assert len(A(1, 2)) == 1
252281

282+
def test_str() -> None:
283+
assert str(A(1, 2)) == "A(1)"
284+
assert repr(A(1, 2)) == "A(1)"
285+
253286
def test_cmp() -> None:
254287
a = A(1, 2)
255288
b = A(2, 3)
@@ -263,3 +296,24 @@ def test_cmp() -> None:
263296
assert not b <= a
264297
assert 1 in a
265298
assert 2 not in a
299+
300+
[case testValueTypeLifetime]
301+
from typing import final, Final, NamedTuple
302+
from mypy_extensions import mypyc_attr
303+
304+
class A(NamedTuple):
305+
n: str
306+
307+
@final
308+
@mypyc_attr(value_type=True)
309+
class B:
310+
def __init__(self, a: A) -> None:
311+
self.a: Final = a
312+
313+
def f() -> B:
314+
# A instance is created in the function scope
315+
# should be keep alive after the function returns
316+
return B(A(n="test"))
317+
318+
def test_value_type_lifetime() -> None:
319+
assert f().a.n == "test"

0 commit comments

Comments
 (0)