-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Re-widen custom properties after narrowing #19296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2593,3 +2593,22 @@ def baz(item: Base) -> None: | |
| reveal_type(item) # N: Revealed type is "__main__.<subclass of "__main__.Base" and "__main__.BarMixin">" | ||
| item.bar() | ||
| [builtins fixtures/isinstance.pyi] | ||
|
|
||
| [case testCustomSetterNarrowingReWidened] | ||
| class B: ... | ||
| class C(B): ... | ||
| class C1(B): ... | ||
| class D(C): ... | ||
|
|
||
| class Test: | ||
| @property | ||
| def foo(self) -> C: ... | ||
| @foo.setter | ||
| def foo(self, val: B) -> None: ... | ||
|
|
||
| t: Test | ||
| t.foo = D() | ||
| reveal_type(t.foo) # N: Revealed type is "__main__.D" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only now I finally realized that
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe that the vast majority of setters probably just assign the value as is (with some additional operations, such as checking validity), and the getter will return it. There's no guarantee that this is the case -- but narrowing types of regular attributes is already generally unsafe but still supported, since not having it would be quite tedious.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JukkaL I agree that's true iff the property is symmetric. If it is not, narrowing is too optimistic IMO, I'd prefer to see no narrowing by assignment at all for properties with different getter and setter types. I can easily envision the setter implementation (omitted in this test) creating a I think this is quite different from regular attribute narrowing: there is some common sense expectation that attributes are "good citizens", but it does not apply to properties and especially asymmetric ones.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be focused, I think the following implementation is idiomatic, and narrowing by assignment should not happen here: from collections.abc import Iterable
class SuperList(list[int]):
def non_list_method(self): ...
class Demo:
_foo: list[int]
@property
def foo(self) -> list[int]:
return self._foo
@foo.setter
def foo(self, val: Iterable[int]) -> None:
self._foo = list(val)
d = Demo()
reveal_type(d.foo) # N: Revealed type is "builtins.list[builtins.int]"
d.foo = SuperList([])
# Huh? Looks like an easy mistake to make, and `mypy` now supports it!
reveal_type(d.foo) # N: Revealed type is "__main__.SuperList"
d.foo.non_list_method() # AttributeError, of course
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, I am writing subclasses of
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you're asking... I dunno whether this was considered and intentional - sometimes things like this may really get missed. Sometimes decisions made in past are challenged and eventually reconsidered. Since such decisions are almost always undocumented (or difficult to trace at least), I can't say for sure. And I know you were working on these parts, so you're the right person to raise my concerns to:) And I don't think 6-7 years timeline applies to properties - you only finished asymmetric properties support last month or so, the implementation is essentially newborn. To me doing such narrowing for asymmetric properties is a mistake - I don't think this would cause a lot of false positives in real code, and this is exactly the kind of possible bug I want mypy to catch early. It's not the hill I'm going to die on, but a significant annoyance. (though I should've probably opened a ticket to discuss this outside of a semi-related PR)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It does, before I implemented the proper support, the setter type was automatically the getter type, so mypy happily narrowed it if the type of the value assigned was a subtype of the getter type.
But what if a person actually implements your example correctly? I.e. with @foo.setter
def foo(self, val: Iterable[int]) -> None:
if isinstance(val, list):
self._foo = val
else:
self._foo = list(val)Also what if the getter/setter types are unions (especially with
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But that's not necessarily "correctly"! If that class modifies
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, the least surprising behavior is when consequences of A bit more of educational content for you: should we prohibit overriding regular attributes with such properties? I mean we always narrow regular attributes, but |
||
| t.foo = C1() | ||
| reveal_type(t.foo) # N: Revealed type is "__main__.C" | ||
| [builtins fixtures/property.pyi] | ||
Uh oh!
There was an error while loading. Please reload this page.