[red knot] add Type::is_disjoint_from and intersection simplifications#13775
[red knot] add Type::is_disjoint_from and intersection simplifications#13775
Type::is_disjoint_from and intersection simplifications#13775Conversation
|
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 |
carljm
left a comment
There was a problem hiding this comment.
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
carljm
left a comment
There was a problem hiding this comment.
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.
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.
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::Functionis not disjoint from one of those sameType::Function.
Ah, you're right.
I think this case is really the same as the
NoneandLiteraltypes 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.
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
carljm
left a comment
There was a problem hiding this comment.
This is excellent work, thank you!!
Summary
Type::is_disjoint_fromas a way to test whether two types overlapS & T = SforS <: TS & ~T = NeverforS <: T~S & ~T = ~TforS <: TA & ~B = AforAdisjoint fromBA & B = NeverforAdisjoint fromBbool & ~Literal[bool] = Literal[!bool]resolves one item in astral-sh/ty#245
Open questions:
positiveandnegativecontributions? I could imagine that there would be a way if we hadType::Not(type)/Type::Negative(type), but with thepositive/negativearchitecture, I'm not sure. Note that there is a certain duplication in theadd_positive/add_negativefunctions (e.g.S & ~T = Neveris implemented twice), but other rules are actually not perfectly symmetric:S & T = Svs~S & ~T = ~T.add_positive/add_negativeturned 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_fromIntersectionBuilder::build()