-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Don't special-case class instances in binary expression inference #15161
Conversation
CodSpeed Performance ReportMerging #15161 will degrade performances by 4.5%Comparing Summary
Benchmarks breakdown
|
|
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 great, thank you! Just one substantive comment.
class A: ... | ||
class B: ... | ||
|
||
reveal_type(A | B) # revealed: Literal[A, B] |
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 don't think Literal[A, B]
is the right type to assign to the value expression A | B
.
In a type expression, A | B
spells the type Type::Union[Type::Instance(A), Type::Instance(B)]
-- that is, the type consisting of all instances of A
(or any subclass) and all instances of B
(or any subclass).
The value expression A if flag else B
would correctly be assigned the type Literal[A, B]
, since its runtime value must either be the class object A
or the class object B
.
But the runtime value of the value expression A | B
is not either of those, so its type can't be Literal[A, B]
. Its runtime value is an instance of types.UnionType
, which can't be treated as if it were a class object at all. So the type we should assign to it is Type::Instance(types.UnionType)
.
reveal_type(A | B) # revealed: Literal[A, B] | |
reveal_type(A | B) # revealed: UnionType |
If we did any special-casing here, the correct special casing would be to create a custom type representation for types.UnionType
so we can record the fact that its __args__
attribute (in this case) has the type tuple[Literal[A], Literal[B]]
. But I don't think this is worth doing.
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.
Reverted the special case, this is now returning UnionType
again as before
reveal_type(f // f) # revealed: Unknown | ||
``` | ||
|
||
## Subclass |
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 feels very closely related to the ## Classes
section above -- maybe group them together instead of having the intervening function literals section?
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.
Done
(Type::ClassLiteral(_), Type::ClassLiteral(_), ast::Operator::BitOr) | ||
if Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY310 => | ||
{ | ||
Some(UnionType::from_elements(self.db(), [left_ty, right_ty])) | ||
} | ||
|
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 the clause that I think we don't want; I expect typeshed will natively give us the correct type of types.UnionType
?
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.
Removed by the revert mentioned above
It's odd that this PR would cause a regression. The profiles on CodSpeed suggest that we are fetching some ingredient more often. I suspect that the cause is that improving completeness here means we now return a more complex type in lots of cases that would previously have just returned |
* main: Add all PEP-585 names to UP006 rule (#5454) [`flake8-simplify`] More precise inference for dictionaries (`SIM300`) (#15164) `@no_type_check` support (#15122) Visit PEP 764 inline `TypedDict`s' keys as non-type-expressions (#15073) [red-knot] Add diagnostic for invalid unpacking (#15086) [`flake8-use-pathlib`] Catch redundant joins in `PTH201` and avoid syntax errors (#15177) Update Rust crate glob to v0.3.2 (#15185) Update astral-sh/setup-uv action to v5 (#15193) Update dependency mdformat-mkdocs to v4.1.1 (#15192) Update Rust crate serde_with to v3.12.0 (#15191) Update NPM Development dependencies (#15190) Update pre-commit hook rhysd/actionlint to v1.7.5 (#15189) Update Rust crate syn to v2.0.93 (#15188) Update Rust crate serde to v1.0.217 (#15187) Update Rust crate quote to v1.0.38 (#15186) Update Rust crate compact_str to v0.8.1 (#15184) [`flake8-type-checking`] Disable TC006 & TC007 in stub files (#15179) Test explicit shadowing involving `def`s (#15174) Fix typo in `NameImport.qualified_name` docstring (#15170) [airflow]: extend names moved from core to provider (AIR303) (#15159)
This reverts commit a52486f.
* main: (60 commits) [`ruff`] Dataclass enums (`RUF049`) (#15299) Better error message when `--config` is given a table key and a non-inline-table value (#15266) Update pre-commit dependencies (#15289) Don't fix in ecosystem check (#15267) Update Rust crate itertools to 0.14.0 (#15287) Remove accidental empty block at the bottom of `split-static-string (SIM905)` doc (#15290) Update Rust crate clearscreen to v4 (#15288) Update Rust crate insta to v1.42.0 (#15286) Update NPM Development dependencies (#15285) Update dependency uuid to v11.0.4 (#15284) Update dependency ruff to v0.8.6 (#15283) Update Rust crate syn to v2.0.95 (#15282) Update Rust crate matchit to v0.8.6 (#15281) Update Rust crate bstr to v1.11.3 (#15280) [red-knot] Future-proof `Type::is_disjoint_from()` (#15262) [red-knot] Improve `Type::is_disjoint_from()` for `KnownInstanceType`s (#15261) [red-knot] Minor simplifications and improvements to constraint narrowing logic (#15270) Allow assigning ellipsis literal as parameter default value (#14982) [red-knot] fix control flow for assignment expressions in elif tests (#15274) [`refurb`] Mark fix as unsafe when the right-hand side is a string (`FURB171`) (#15273) ...
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 great, thanks for the thorough tests!
* main: Use uv consistently throughout the documentation (#15302) [red-knot] Eagerly normalize `type[]` types (#15272) [`pyupgrade`] Split `UP007` to two individual rules for `Union` and `Optional` (`UP007`, `UP045`) (#15313) [red-knot] Improve symbol-lookup tracing (#14907) [red-knot] improve type shrinking coverage in red-knot property tests (#15297) [`flake8-return`] Recognize functions returning `Never` as non-returning (`RET503`) (#15298) [`flake8-bugbear`] Implement `class-as-data-structure` (`B903`) (#9601) Avoid treating newline-separated sections as sub-sections (#15311) Remove call when removing final argument from `format` (#15309) Don't enforce `object-without-hash-method` in stubs (#15310) Don't special-case class instances in binary expression inference (#15161) Upgrade zizmor to the latest version in CI (#15300)
Just like in #15045 for unary expressions: In binary expressions, we were only looking for dunder expressions for
Type::Instance
types. We had some special cases for coercing the variousLiteral
types into their correspondingInstance
types before doing the lookup. But we can side-step all of that by using the existingType::to_meta_type
andType::to_instance
methods.Closes #14549