Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mypyc/codegen/emitfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ def visit_set_attr(self, op: SetAttr) -> None:
rtype.setter_index(op.attr),
src,
rtype.struct_name(self.names),
self.ctype(rtype.attr_type(op.attr)),
self.ctype(op.propset.sig.args[1].type),
c_ret_type,
op.attr,
)
Expand Down
9 changes: 9 additions & 0 deletions mypyc/irbuild/targets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from mypyc.common import PROPSET_PREFIX
from mypyc.ir.ops import Register, Value
from mypyc.ir.rtypes import RInstance, RType, object_rprimitive

Expand Down Expand Up @@ -49,6 +50,14 @@ def __init__(self, obj: Value, attr: str, can_borrow: bool = False) -> None:
# Native attribute reference
self.obj_type: RType = obj.type
self.type = obj.type.attr_type(attr)
# A property setter may accept a wider type than its getter returns,
# so values assigned through it are coerced to the setter's argument type
for ir in obj.type.class_ir.mro:
setter = ir.method_decls.get(PROPSET_PREFIX + attr)
if setter is not None:
if not setter.implicit:
self.type = setter.sig.args[1].type
break
else:
# Python attribute reference
self.obj_type = object_rprimitive
Expand Down
70 changes: 70 additions & 0 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -2317,6 +2317,76 @@ import other_interpreted

[out]

[case testPropertySetterWiderThanGetter]
from typing import Union
from mypy_extensions import trait

class Wrapped:
def __init__(self, s: str) -> None:
self.s = s

@trait
class T:
@property
def value(self) -> Wrapped:
raise NotImplementedError

@value.setter
def value(self, v: Union[Wrapped, str]) -> None:
raise NotImplementedError

class C(T):
def __init__(self) -> None:
self._value = Wrapped("init")
self._n = 0

@property
def value(self) -> Wrapped:
return self._value

@value.setter
def value(self, v: Union[Wrapped, str]) -> None:
self._value = Wrapped(v) if isinstance(v, str) else v

@property
def n(self) -> int:
return self._n

@n.setter
def n(self, v: Union[int, str]) -> None:
self._n = int(v)

def set_via_class(c: C, v: Union[Wrapped, str]) -> str:
c.value = v
return c.value.s

def set_via_trait(t: T, v: Union[Wrapped, str]) -> str:
t.value = v
return t.value.s

def set_unboxed(c: C, v: Union[int, str]) -> int:
c.n = v
return c.n

def test_setter_wider_than_getter() -> None:
c = C()
assert set_via_class(c, "a") == "a"
assert set_via_class(c, Wrapped("b")) == "b"
assert set_via_trait(c, "c") == "c"
assert set_via_trait(c, Wrapped("d")) == "d"
assert set_unboxed(c, "5") == 5
assert set_unboxed(c, 6) == 6

[file driver.py]
from native import C, test_setter_wider_than_getter
test_setter_wider_than_getter()

c = C()
c.value = "py"
assert c.value.s == "py"
c.n = "7"
assert c.n == 7

[case testAttributeOverridesProperty]
from typing import Any
from mypy_extensions import trait
Expand Down
Loading