Skip to content

Commit 5f7e2c6

Browse files
committed
Re-widen custom properties after narrowing
1 parent 5081c59 commit 5f7e2c6

File tree

2 files changed

+27
-10
lines changed

2 files changed

+27
-10
lines changed

mypy/checker.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4580,7 +4580,7 @@ def check_member_assignment(
45804580
self,
45814581
lvalue: MemberExpr,
45824582
instance_type: Type,
4583-
attribute_type: Type,
4583+
set_lvalue_type: Type,
45844584
rvalue: Expression,
45854585
context: Context,
45864586
) -> tuple[Type, Type, bool]:
@@ -4597,23 +4597,21 @@ def check_member_assignment(
45974597
if (isinstance(instance_type, FunctionLike) and instance_type.is_type_obj()) or isinstance(
45984598
instance_type, TypeType
45994599
):
4600-
rvalue_type, _ = self.check_simple_assignment(attribute_type, rvalue, context)
4601-
return rvalue_type, attribute_type, True
4600+
rvalue_type, _ = self.check_simple_assignment(set_lvalue_type, rvalue, context)
4601+
return rvalue_type, set_lvalue_type, True
46024602

46034603
with self.msg.filter_errors(filter_deprecated=True):
46044604
get_lvalue_type = self.expr_checker.analyze_ordinary_member_access(
46054605
lvalue, is_lvalue=False
46064606
)
46074607

4608-
# Special case: if the rvalue_type is a subtype of both '__get__' and '__set__' types,
4609-
# and '__get__' type is narrower than '__set__', then we invoke the binder to narrow type
4608+
# Special case: if the rvalue_type is a subtype of both '__get__' type, and
4609+
# '__get__' type is narrower than '__set__', then we invoke the binder to narrow type
46104610
# by this assignment. Technically, this is not safe, but in practice this is
46114611
# what a user expects.
4612-
rvalue_type, _ = self.check_simple_assignment(attribute_type, rvalue, context)
4613-
infer = is_subtype(rvalue_type, get_lvalue_type) and is_subtype(
4614-
get_lvalue_type, attribute_type
4615-
)
4616-
return rvalue_type if infer else attribute_type, attribute_type, infer
4612+
rvalue_type, _ = self.check_simple_assignment(set_lvalue_type, rvalue, context)
4613+
rvalue_type = rvalue_type if is_subtype(rvalue_type, get_lvalue_type) else get_lvalue_type
4614+
return rvalue_type, set_lvalue_type, is_subtype(get_lvalue_type, set_lvalue_type)
46174615

46184616
def check_indexed_assignment(
46194617
self, lvalue: IndexExpr, rvalue: Expression, context: Context

test-data/unit/check-narrowing.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2593,3 +2593,22 @@ def baz(item: Base) -> None:
25932593
reveal_type(item) # N: Revealed type is "__main__.<subclass of "__main__.Base" and "__main__.BarMixin">"
25942594
item.bar()
25952595
[builtins fixtures/isinstance.pyi]
2596+
2597+
[case testCustomSetterNarrowingReWidened]
2598+
class B: ...
2599+
class C(B): ...
2600+
class C1(B): ...
2601+
class D(C): ...
2602+
2603+
class Test:
2604+
@property
2605+
def foo(self) -> C: ...
2606+
@foo.setter
2607+
def foo(self, val: B) -> None: ...
2608+
2609+
t: Test
2610+
t.foo = D()
2611+
reveal_type(t.foo) # N: Revealed type is "__main__.D"
2612+
t.foo = C1()
2613+
reveal_type(t.foo) # N: Revealed type is "__main__.C"
2614+
[builtins fixtures/property.pyi]

0 commit comments

Comments
 (0)