-
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] add Type::is_disjoint_from
and intersection simplifications
#13775
Conversation
|
cd903c3
to
e94ba83
Compare
e94ba83
to
2bd1789
Compare
215ef0c
to
bf4af40
Compare
FWIW, I'm quite open to the idea that we discard the positive/negative approach in favor of a The downside is we then have to add handling for |
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 great! I suspect that this approach will carry us a long way; I'm not feeling any particular need to explore more radical approaches. (I'm not 100% sure, but I suspect that the lazy-BDD approach I linked you to may just not be workable anyway for the complexity of Python's type system; particularly inheritance and multiple inheritance.)
crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals_is.md
Show resolved
Hide resolved
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.
Looks like this is still in-process, so didn't do a full re-review of the code, just a few responses to inline comments.
(ty @ (Type::Function(..) | Type::Module(..) | Type::Class(..)), other) | ||
| (other, ty @ (Type::Function(..) | Type::Module(..) | Type::Class(..))) => { | ||
ty != other | ||
} |
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.
It just occurred to me now on second read-through that this case is incorrect if it comes before the union case, because the union of two Type::Function
is not disjoint from one of those same Type::Function
.
I think this case is really the same as the None
and Literal
types case below, and could probably just be merged with it?
Would be good to add a test case that would have caught this (to create a union of function or class types, define them in if/else branches.)
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.
It just occurred to me now on second read-through that this case is incorrect if it comes before the union case, because the union of two
Type::Function
is not disjoint from one of those sameType::Function
.
Ah, you're right.
I think this case is really the same as the
None
andLiteral
types case below, and could probably just be merged with it?
👍
Would be good to add a test case that would have caught this (to create a union of function or class types, define them in if/else branches.)
Yes. I added a test that failed with the previous version.
c2930da
to
34b0383
Compare
(Type::BooleanLiteral(..), Type::Instance(class_type)) | ||
| (Type::Instance(class_type), Type::BooleanLiteral(..)) => !matches!( | ||
class_type.known(db), | ||
Some(KnownClass::Bool | KnownClass::Object) |
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.
Because bool
is a subtype of int
in Python (sadly), we must also include KnownClass::Int
here.
There was already a test for this case, but it asserted wrongly that int
and a BooleanLiteral
were disjoint; I switched it to assert the opposite.
There was also an IntersectionBuilder
test relying on this assumption of bool/int disjointness; I fixed it by using a StringLiteral
instead of a BooleanLiteral
.
e5af7c9
to
b3b0ffb
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 excellent work, thank you!!
Summary
Type::is_disjoint_from
as a way to test whether two types overlapS & T = S
forS <: T
S & ~T = Never
forS <: T
~S & ~T = ~T
forS <: T
A & ~B = A
forA
disjoint fromB
A & B = Never
forA
disjoint fromB
bool & ~Literal[bool] = Literal[!bool]
resolves one item in #12694
Open questions:
positive
andnegative
contributions? I could imagine that there would be a way if we hadType::Not(type)
/Type::Negative(type)
, but with thepositive
/negative
architecture, I'm not sure. Note that there is a certain duplication in theadd_positive
/add_negative
functions (e.g.S & ~T = Never
is implemented twice), but other rules are actually not perfectly symmetric:S & T = S
vs~S & ~T = ~T
.add_positive
/add_negative
turned out. They are long imperative-style functions with some mutability mixed in (to_remove
). I'm happy to look into ways to improve this code if we decide to go with this approach of implementing a set of ad-hoc rules for simplification.Is it useful to perform simplifications eagerly inThis is what I did for now.add_positive
/add_negative
? (@carljm)Test Plan
Type::is_disjoint_from
IntersectionBuilder::build()