## Summary
Resolve https://github.com/astral-sh/ty/issues/2226
We need to add a special case in `apply_type_mapping` instead of
directly in `promote_literals_impl` because we do not reach this with
non generic non tuple nominal instances. We still ensure we apply the
normal mapping if we do not see `float` or `complex` instances.
## Test Plan
Update existing mdtest and add a new case to `literal_promotion.md`
## Summary
Other type checkers allow you to access all `FunctionType` attributes on
any object with a `Callable` type. ty does not, because this is
demonstrably unsound, but this is often a source of confusion for users.
And there were lots of diagnostics in the ecosystem report for
https://github.com/astral-sh/ruff/pull/22145 that were complaining that
"Object of type `(...) -> Unknown` has no attribute `__name__`", for
example.
The discrepancy between what ty does here and what other type checkers
do is discussed a bit in https://github.com/astral-sh/ty/issues/1495.
You can see that there have been lots of issues closed as duplicates of
that issue; we should probably also add an FAQ entry for it.
Anyway, this PR adds a subdiagnostic to help users out when they hit
this diagnostic. Unfortunately something I did meant that rustfmt
increased the indentation of the whole of this huge closure, so this PR
is best reviewed with the "No whitespace" option selected for viewing
the diff.
## Test Plan
Snapshot added
## Summary
Provides a message like:
```
error[invalid-argument-type]: Cannot delete required key "name" from TypedDict `Movie`
--> test.py:15:7
|
15 | del m["name"]
| ^^^^^^
|
info: Field defined here
--> test.py:4:5
|
4 | name: str
| --------- `name` declared as required here; consider making it `NotRequired`
|
info: Only keys marked as `NotRequired` (or in a TypedDict with `total=False`) can be deleted
```
## Summary
TypedDict now synthesizes a proper `__delitem__` method that...
- ...allows deletion of `NotRequired` keys and keys in `total=False`
TypedDicts.
- ...rejects deletion of required keys (synthesizes `__delitem__(k:
Never)`).
## Summary
We already had `CallableTypeKind::ClassMethodLike` to track callables
that behave like `classmethods` (always bind the first argument). This
PR adds the symmetric `CallableTypeKind::StaticMethodLike` for callables
that behave like `staticmethods` (never bind `self`).
Closes https://github.com/astral-sh/ty/issues/2114.
## Summary
Previously, `del x[k]` incorrectly required the object to have a
`__getitem__` method. This was wrong because deletion only needs
`__delitem__`, which is independent of `__getitem__`.
Closes https://github.com/astral-sh/ty/issues/1799.
## Summary
This PR fixes https://github.com/astral-sh/ty/issues/1848.
```python
T = tuple[int, 'U']
class C(set['U']):
pass
type U = T | C
```
The reason why the fixed point iteration did not converge was because
the types stored in the implicit tuple type alias `Specialization`
changed each time.
```
1st: <class 'tuple[int, C]'>
2nd: <class 'tuple[int, tuple[int, C] | C]'>
3rd: <class 'tuple[int, tuple[int, tuple[int, C] | C] | C]'>
...
```
And this was because `UnionType::from_elements` was used when creating
union types for tuple operations, which causes type aliases inside to be
expanded.
This PR replaces these with `UnionType::from_elements_leave_aliases`.
## Test Plan
New corpus test
Identify and narrow cases like this:
```py
class Foo(TypedDict):
tag: Literal["foo"]
class Bar(TypedDict):
tag: Literal["bar"]
def _(union: Foo | Bar):
if union["tag"] == "foo":
reveal_type(union) # Foo
```
Fixes part of https://github.com/astral-sh/ty/issues/1479.
---------
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
## Summary
This PR fixes https://github.com/astral-sh/ty/issues/2085.
Based on the reported code, the panicking MRE is:
```python
class Test:
def __init__(self, x: int):
self.left = x
self.right = x
def method(self):
self.left, self.right = self.right, self.left
if self.right:
self.right = self.right
```
The type inference (`implicit_attribute_inner`) for `self.right`
proceeds as follows:
```
0: Divergent(Id(6c07))
1: Unknown | int | (Divergent(Id(1c00)) & ~AlwaysFalsy)
2: Unknown | int | (Divergent(Id(6c07)) & ~AlwaysFalsy) | (Divergent(Id(1c00)) & ~AlwaysFalsy)
3: Unknown | int | (Divergent(Id(1c00)) & ~AlwaysFalsy) | (Divergent(Id(6c07)) & ~AlwaysFalsy)
4: Unknown | int | (Divergent(Id(6c07)) & ~AlwaysFalsy) | (Divergent(Id(1c00)) & ~AlwaysFalsy)
...
```
The problem is that the order of union types is not stable between
cycles. To solve this, when unioning the previous union type with the
current union type, we should use the previous type as the base and add
only the new elements in this cycle (In the current implementation, this
unioning order was reversed).
## Test Plan
New corpus test
## Summary
IIUC, tuples with a known structure (`tuple_spec`) use the standard
tuple `__eq__` which only returns `True` for other tuples, so they can
be safely excluded when disjoint from string literals or other non-tuple
types.
Closes https://github.com/astral-sh/ty/issues/2140.
## Summary
I missed this in https://github.com/astral-sh/ruff/pull/22062. This
avoids exponential runtime in the following snippet:
```py
class X1: ...
class X2: ...
class X3: ...
class X4: ...
class X5: ...
class X6: ...
...
def f(
x:
list[X1 | None]
| list[X2 | None]
| list[X3 | None]
| list[X4 | None]
| list[X5 | None]
| list[X6 | None]
...
):
...
def g[T](x: T) -> list[T]:
return [x]
def id[T](x: T) -> T:
return x
f(id(id(id(id(g(X64()))))))
```
Eventually I want to refactor our multi-inference infrastructure (which
is currently very brittle) to handle this implicitly, but this is a
temporary performance fix until that happens.
## Summary
The following snippet currently errors because we widen the inferred
type, even though `X` is covariant over `T`. If `T` was contravariant or
invariant, this would be fine, as it would lead to an assignability
error anyways.
```python
class X[T]:
def __init__(self: X[None]): ...
def pop(self) -> T:
raise NotImplementedError
# error: Argument to bound method `__init__` is incorrect: Expected `X[None]`, found `X[int | None]`
x: X[int | None] = X()
```
There are some cases where it is still helpful to prefer covariant
declared types, but this error seems hard to fix otherwise, and makes
our heuristics more consistent overall.
## Summary
This PR implements the strategy described in
https://github.com/astral-sh/ty/issues/1871: we iterate over the
positive types, resolve them, then intersect the results.
## Summary
This should make revealed types a little nicer, as well as avoid
confusing the constraint solver in some cases (which were showing up in
https://github.com/astral-sh/ruff/pull/21930).