-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[ty] Emit diagnostic on tuple subclasses that override __eq__, __ne__ or __bool__ methods
#22150
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
base: main
Are you sure you want to change the base?
[ty] Emit diagnostic on tuple subclasses that override __eq__, __ne__ or __bool__ methods
#22150
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
| let mut diagnostic = | ||
| builder.into_diagnostic(format_args!("Unsafe override of method `{member}`")); | ||
|
|
||
| diagnostic.set_primary_message(format_args!( | ||
| "Unsafely overriding method `{member}` in a subclass of `tuple`" | ||
| )); | ||
|
|
||
| diagnostic.help(format!( | ||
| "Overriding `{member}` can cause unexpected behavior" | ||
| )); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how much we should explain why it is unsafe to override certain methods.
AlexWaygood
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
This looks like it's a bit too trigger-happy right now:
- I don't think we ever need to issue
unsafe-tuple-subclassfor a__len__override. Our existing Liskov Substitution Principle check takes care of that for us - For other methods such as
__bool__,__eq__, etc., I think we should avoid emitting this lint if the override would anyway cause us to emit a Liskov diagnostic. For something like this, we don't yet emit a Liskov diagnostic, but we should, so I don't think it should be covered by this check:class Foo(tuple[int]): __len__ = None
- I don't think we need to issue
unsafe-tuple-subclassfor a__bool__override unless we would infer the tuple superclass as either always-truthy or always-falsy. It's fine to override__bool__on a tuple subclass if the superclass has ambiguous truthiness; that won't violate any of our type-inference or narrowing assumptions elsewhere
| } | ||
| } | ||
|
|
||
| const PROHIBITED_TUPLE_SUBCLASS_METHODS: &[&str] = &["__eq__", "__len__", "__bool__"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
other methods where we have special-cased behaviour for tuples are __lt__, __le__, __gt__, __ge__, __ne__, and __contains__. We probably need to ban overriding all of those too, unfortunately.
But as I said elsewhere, I think we can get rid of __len__ from this list (it's already covered by our Liskov checks). And there are many situations where overriding __bool__ on a tuple subclass should be fine.
We have very special-cased behaviour for __getitem__, but that should also be taken care of by our Liskov checks once we've finished implementing them, so I don't think you need to worry about __getitem__.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have very special-cased behaviour for
__getitem__, but that should also be taken care of by our Liskov checks once we've finished implementing them, so I don't think you need to worry about__getitem__.
Ah, no, we do need to ban __getitem__ overrides too, I think, because our special casing for slices of tuples is not reflected in our synthesized __getitem__ overloads for specialized tuples.
And there is maybe one specific kind of __len__ override that we need to ban, which is __len__ being overridden on subclasses of "mixed" tuples. tuple[int, *tuple[int, ...]] means "a tuple of length at least one, but of potentially arbitrary length". Either of these overrides would not be caught by our Liskov checks, but both are clearly inconsistent with the tuple spec of the superclass:
from typing import Literal
class Foo(tuple[int, *tuple[int, ...]]):
def __len__(self) -> int:
return 2
class Bar(tuple[int, *tuple[int, ...]):
def __len__(self) -> Literal[2]:
return 2|
Perhaps this can be repurposed to just cover |
Sure! |
|
I maybe need some advice on diagnostic messages |
__eq__, __ne__ or __bool__ methods
Summary
Resolves astral-sh/ty#2171
Test Plan
Add
unsafe_tuple_subclass.mdmdtest file.