Skip to content

Commit a2187de

Browse files
committed
More clenup, fixes, and tests
1 parent 2cb9117 commit a2187de

File tree

4 files changed

+119
-16
lines changed

4 files changed

+119
-16
lines changed

mypy/checker.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,14 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
651651
self.visit_func_def(defn.items[1].func)
652652
setter_type = self.function_type(defn.items[1].func)
653653
assert isinstance(setter_type, CallableType)
654+
if len(setter_type.arg_types) != 2:
655+
self.fail("Invalid property setter signature", defn.items[1].func)
656+
any_type = AnyType(TypeOfAny.from_error)
657+
setter_type = setter_type.copy_modified(
658+
arg_types=[any_type, any_type],
659+
arg_kinds=[ARG_POS, ARG_POS],
660+
arg_names=[None, None],
661+
)
654662
defn.items[0].var.setter_type = setter_type
655663
for fdef in defn.items:
656664
assert isinstance(fdef, Decorator)
@@ -2058,22 +2066,29 @@ def check_setter_type_override(
20582066
"""
20592067
base_node = base_attr.node
20602068
assert isinstance(base_node, (OverloadedFuncDef, Var))
2061-
original_type = get_raw_setter_type(base_node)
2069+
original_type, is_original_setter = get_raw_setter_type(base_node)
20622070
if isinstance(base_node, Var):
20632071
expanded_type = map_type_from_supertype(original_type, defn.info, base)
20642072
original_type = get_proper_type(
20652073
expand_self_type(base_node, expanded_type, fill_typevars(defn.info))
20662074
)
20672075
else:
2076+
assert isinstance(original_type, ProperType)
20682077
assert isinstance(original_type, CallableType)
20692078
original_type = self.bind_and_map_method(base_attr, original_type, defn.info, base)
20702079
assert isinstance(original_type, CallableType)
2071-
original_type = get_proper_type(original_type.arg_types[0])
2080+
if is_original_setter:
2081+
original_type = original_type.arg_types[0]
2082+
else:
2083+
original_type = original_type.ret_type
20722084

2073-
typ = get_raw_setter_type(defn)
2074-
assert isinstance(typ, CallableType)
2085+
typ, is_setter = get_raw_setter_type(defn)
2086+
assert isinstance(typ, ProperType) and isinstance(typ, CallableType)
20752087
typ = bind_self(typ, self.scope.active_self_type())
2076-
typ = get_proper_type(typ.arg_types[0])
2088+
if is_setter:
2089+
typ = typ.arg_types[0]
2090+
else:
2091+
typ = typ.ret_type
20772092

20782093
if not is_subtype(original_type, typ):
20792094
self.msg.incompatible_setter_override(defn.items[1], typ, original_type, base)
@@ -3422,7 +3437,8 @@ def check_compatibility_all_supers(
34223437
base_type, _ = self.lvalue_type_from_base(
34233438
lvalue_node, base, setter_type=True
34243439
)
3425-
# Setter type must be ready if the getter type is ready.
3440+
# Setter type for a custom property must be ready if
3441+
# the getter type is ready.
34263442
assert base_type is not None
34273443
if not is_subtype(base_type, lvalue_type):
34283444
self.msg.incompatible_setter_override(
@@ -3519,8 +3535,14 @@ def check_compatibility_super(
35193535
def lvalue_type_from_base(
35203536
self, expr_node: Var, base: TypeInfo, setter_type: bool = False
35213537
) -> tuple[Type | None, SymbolNode | None]:
3522-
"""For a NameExpr that is part of a class, walk all base classes and try
3523-
to find the first class that defines a Type for the same name."""
3538+
"""Find a type for a variable name in base class.
3539+
3540+
Return the type found and the corresponding node defining the name or None
3541+
for both if the name is not defined in base or the node type is not known (yet).
3542+
The type returned is already properly mapped/bound to the subclass.
3543+
If setter_type is True, return setter types for settable properties (otherwise the
3544+
getter type is returned).
3545+
"""
35243546
expr_name = expr_node.name
35253547
base_var = base.names.get(expr_name)
35263548

@@ -3558,7 +3580,8 @@ def lvalue_type_from_base(
35583580
if setter_type:
35593581
assert isinstance(base_node.items[0], Decorator)
35603582
base_type = base_node.items[0].var.setter_type
3561-
assert isinstance(base_type, CallableType)
3583+
# This flag is True only for custom properties, so it is safe to assert.
3584+
assert base_type is not None
35623585
base_type = self.bind_and_map_method(base_var, base_type, expr_node.info, base)
35633586
assert isinstance(base_type, CallableType)
35643587
base_type = get_proper_type(base_type.arg_types[0])
@@ -8778,6 +8801,11 @@ def is_settable_property(defn: SymbolNode | None) -> TypeGuard[OverloadedFuncDef
87788801

87798802

87808803
def is_custom_settable_property(defn: SymbolNode | None) -> bool:
8804+
"""Check if a node is a settable property with a non-trivial setter type.
8805+
8806+
By non-trivial here we mean that it is known (i.e. definition was already type
8807+
checked), it is not Any, and it is different from the property getter type.
8808+
"""
87818809
if defn is None:
87828810
return False
87838811
if not is_settable_property(defn):
@@ -8796,17 +8824,27 @@ def is_custom_settable_property(defn: SymbolNode | None) -> bool:
87968824
return not is_same_type(get_property_type(get_proper_type(var.type)), setter_type)
87978825

87988826

8799-
def get_raw_setter_type(defn: OverloadedFuncDef | Var) -> ProperType:
8827+
def get_raw_setter_type(defn: OverloadedFuncDef | Var) -> tuple[Type, bool]:
8828+
"""Get an effective original setter type for a node.
8829+
8830+
For a variable it is simply its type. For a property it is the type
8831+
of the setter method (if not None), or the getter method (used as fallback
8832+
for the plugin generated properties).
8833+
Return the type and a flag indicating that we didn't fall back to getter.
8834+
"""
88008835
if isinstance(defn, Var):
88018836
# This function should not be called if the var is not ready.
88028837
assert defn.type is not None
8803-
return get_proper_type(defn.type)
8838+
return defn.type, True
88048839
first_item = defn.items[0]
88058840
assert isinstance(first_item, Decorator)
88068841
var = first_item.var
8807-
# TODO: handle synthetic properties here and elsewhere.
8808-
assert var.setter_type is not None
8809-
return var.setter_type
8842+
# This function may be called on non-custom properties, so we need
8843+
# to handle the situation when it is synthetic (plugin generated).
8844+
if var.setter_type is not None:
8845+
return var.setter_type, True
8846+
assert var.type is not None
8847+
return var.type, False
88108848

88118849

88128850
def get_property_type(t: ProperType) -> ProperType:

mypy/messages.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3061,12 +3061,12 @@ def get_conflict_protocol_types(
30613061
different_setter = False
30623062
if IS_EXPLICIT_SETTER in superflags:
30633063
set_supertype = find_member(member, right, left, is_lvalue=True)
3064-
if set_supertype != supertype:
3064+
if set_supertype and not is_same_type(set_supertype, supertype):
30653065
different_setter = True
30663066
supertype = set_supertype
30673067
if IS_EXPLICIT_SETTER in get_member_flags(member, left):
30683068
set_subtype = mypy.typeops.get_protocol_member(left, member, class_obj, is_lvalue=True)
3069-
if set_subtype != subtype:
3069+
if set_subtype and not is_same_type(set_subtype, subtype):
30703070
different_setter = True
30713071
subtype = set_subtype
30723072
if not is_compat and not different_setter:

mypy/nodes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,7 @@ def __init__(self, name: str, type: mypy.types.Type | None = None) -> None:
10121012
# TODO: Should be Optional[TypeInfo]
10131013
self.info = VAR_NO_INFO
10141014
self.type: mypy.types.Type | None = type # Declared or inferred type, or None
1015+
# The setter type for settable properties.
10151016
self.setter_type: mypy.types.CallableType | None = None
10161017
# Is this the first argument to an ordinary method (usually "self")?
10171018
self.is_self = False

test-data/unit/check-classes.test

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8211,6 +8211,70 @@ class C3(B3):
82118211
# N: Setter types should behave contravariantly
82128212
[builtins fixtures/property.pyi]
82138213

8214+
[case testOverridePropertyInvalidSetter]
8215+
class B1:
8216+
@property
8217+
def foo(self) -> int: ...
8218+
@foo.setter
8219+
def foo(self, x: str) -> None: ...
8220+
class C1(B1):
8221+
@property
8222+
def foo(self) -> int: ...
8223+
@foo.setter
8224+
def foo(self) -> None: ... # E: Invalid property setter signature
8225+
8226+
class B2:
8227+
@property
8228+
def foo(self) -> int: ...
8229+
@foo.setter
8230+
def foo(self) -> None: ... # E: Invalid property setter signature
8231+
class C2(B2):
8232+
@property
8233+
def foo(self) -> int: ...
8234+
@foo.setter
8235+
def foo(self, x: str) -> None: ...
8236+
8237+
class B3:
8238+
@property
8239+
def foo(self) -> int: ...
8240+
@foo.setter
8241+
def foo(self) -> None: ... # E: Invalid property setter signature
8242+
class C3(B3):
8243+
foo: int
8244+
[builtins fixtures/property.pyi]
8245+
8246+
[case testOverridePropertyGeneric]
8247+
from typing import TypeVar, Generic
8248+
8249+
T = TypeVar("T")
8250+
8251+
class B1(Generic[T]):
8252+
@property
8253+
def foo(self) -> int: ...
8254+
@foo.setter
8255+
def foo(self, x: T) -> None: ...
8256+
class C1(B1[str]):
8257+
@property
8258+
def foo(self) -> int: ...
8259+
@foo.setter # E: Incompatible override of a setter type \
8260+
# N: (base class "B1" defined the type as "str", \
8261+
# N: override has type "int")
8262+
def foo(self, x: int) -> None: ...
8263+
8264+
class B2:
8265+
@property
8266+
def foo(self) -> int: ...
8267+
@foo.setter
8268+
def foo(self: T, x: T) -> None: ...
8269+
class C2(B2):
8270+
@property
8271+
def foo(self) -> int: ...
8272+
@foo.setter # E: Incompatible override of a setter type \
8273+
# N: (base class "B2" defined the type as "C2", \
8274+
# N: override has type "int")
8275+
def foo(self, x: int) -> None: ...
8276+
[builtins fixtures/property.pyi]
8277+
82148278
[case testOverrideMethodProperty]
82158279
class B:
82168280
def foo(self) -> int:

0 commit comments

Comments
 (0)