-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[red-knot] Gradual forms do not participate in equivalence/subtyping #14758
Conversation
|
66fb795
to
45f5b5f
Compare
45f5b5f
to
2f28eb0
Compare
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.
This is fantastic. Super clear and correct. Thank you.
| Type::ClassLiteral(..) | ||
| Type::SubclassOf(_) | ||
| Type::Instance(_) |
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 think for these three, we also need to check the MRO for an Any/Unknown base? A class that inherits Any/Unknown is not a fully static type.
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.
Yes, thanks!
For Type::Instance(_)
, this is obviously correct.
For Type::ClassLiteral(_)
it took me some time to understand whether we want that or not. I had a good discussion with @AlexWaygood and I believe I now understand why we want that. This also seems to match mypy's and pyright's interpretation:
from typing import Any
class FullyStatic: ...
#instance1: int = FullyStatic() # obviously doesn't work
#class1: type[int] = FullyStatic # same
class Gradual(Any): ...
instance2: int = Gradual() # okay, `Gradual` is a gradual type
class2: type[int] = Gradual # also okay
I then went on to implement this, which seemed fairly trivial at first. But it turns out that this causes all sorts of problems. One of these problems is that we don't fully understand even the most basic types like str
. In typeshed, str
inherits from Sequence[str]
, which is a complex generic protocol. This currently manifests in the following MRO for str
, which causes us to believe it would be a non-fully-static type.
tuple[Literal[str], Unknown, Literal[object]]
This can be patched by matching on a set of known classes. But that is far from ideal. If someone unknowingly pulls in another type from the standard library (outside the set of known classes; see #14769 as an example) with a similar problem, they would see all sorts of weird behavior without a clear indication of what's wrong.
And then there is also another problem where merely iterating over the MRO of a class here (without drawing any conclusions from it) causes us to eagerly infer some types as Unknown
.
If someone has a good idea on how to resolve this (right now, without waiting for generics), please let me know. Otherwise, I would suggest we move forward with the TODO comment and revisit this when we understand generics.
For reference: here is what I tried: ec64bd8
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.
Fully agree with this analysis and I think the conclusion is right; we need to defer this handling until we can understand most/all typeshed classes that aren't supposed to inherit Any/Unknown, as not inheriting Any/Unknown. TODO comment (and maybe an issue on the backlog, too) seems the right move.
## Summary Minor change that uses two plain classes `A` and `B` instead of `typing.Sized` and `typing.Hashable`. The motivation is twofold: I remember that I was confused when I first saw this test. Was there anything specific to `Sized` and `Hashable` that was relevant here? (there is, these classes are not overlapping; and you can build a proper intersection from them; but that's true for almost all non-builtin classes). I now ran into another problem while working on #14758: `Sized` and `Hashable` are protocols that we don't fully understand yet. This causing some trouble when trying to infer whether these are fully-static types or not.
This changeset contains various improvements concerning non-fully-static types and their relationships: - Make sure that non-fully-static types do not participate in equivalence or subtyping. - Clarify what `Type::is_equivalent_to` actually implements. - Introduce `Type::is_fully_static` - New tests making sure that multiple `Any`/`Unknown`s inside unions and intersections are collapsed.
2f28eb0
to
96d594d
Compare
// Another problem is that we run into problems if we eagerly query the | ||
// MRO of class literals here. I have not fully investigated this, but | ||
// iterating over the MRO alone, without even acting on it, causes us to | ||
// infer `Unknown` for many classes. |
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.
This is a bit scary :/ It suggests we frequently need to ask about the fully-static-ness of a type too soon. I guess we'll cross this bridge when we come to it; hopefully we can find a way to defer the need for checking this so early.
Summary
This changeset contains various improvements concerning non-fully-static types and their relationships:
Type::is_equivalent_to
actually implements.Type::is_fully_static
Any
/Unknown
s inside unions and intersections are collapsed.closes #14524
Test Plan
Type::is_equivalent_to
Type::is_subtype_of