## 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).
We're seeing a lot of nondeterminism in the ecosystem tests at the
moment, which started (or at least got worse) once `Callable` inference
landed.
This PR attempts to remove this nondeterminism. We recently
(https://github.com/astral-sh/ruff/pull/21983) added a `source_order`
field to BDD nodes, which tracks when their constraint was added to the
BDD. Since we build up constraints based on the order that they appear
in the underlying source, that gives us a stable ordering even though we
use an arbitrary salsa-derived ordering for the BDD variables.
The issue (at least for some of the flakiness) is that we add "derived"
constraints when walking a BDD tree, and those derived constraints
inherit or borrow the `source_order` of the "real" constraint that
implied them. That means we can get multiple constraints in our
specialization that all have the same `source_order`. If we're not
careful, those "tied" constraints can be ordered arbitrarily.
The fix requires ~three~ ~four~ several steps:
- When starting to construct a sequent map (the data structure that
stores the derived constraints), we first sort all of the "real"
constraints by their `source_order`. That ensures that we insert things
into the sequent map in a stable order.
- During sequent map construction, derived facts are discovered by a
deterministic process applied to constraints in a (now) stable order. So
derived facts are now also inserted in a stable order.
- We update the fields of `SequentMap` to use `FxOrderSet` instead of
`FxHashSet`, so that we retain that stable insertion order.
- When walking BDD paths when constructing a specialization, we were
already sorting the constraints by their `source_order`. However, we
were not considering that we might get derived constraints, and
therefore constraints with "ties". Because of that, we need to make sure
to use a _stable_ sort, that retains the insertion order for those ties.
All together, this...should...fix the nondeterminism. (Unfortunately, I
haven't been able to effectively test this, since I haven't been able to
coerce local tests to flop into the other order that we sometimes see in
CI.)
@carljm put forth a reasonably compelling argument that just disabling
this lint might be advisable. If we agree, here's the implementation.
* Fixes https://github.com/astral-sh/ty/issues/309
---------
Co-authored-by: Carl Meyer <carl@astral.sh>
This refactor is intended to give more structure to how we generate
completions. There's now a `Context` for "how do we figure out what kind
of completions to offer" and also a `CollectionContext` for "how do we
figure out which completions are appropriate or not." We double down on
`Completions` as a collector and a single point of truth for this. It
now handles adding information to `Completion` (based on the context)
and also skipping completions that are inappropriate (instead of
filtering them after-the-fact).
We also bundle a bunch of state into a new `ContextCursor` type, and
then define a bunch of predicates/accessors on that type that were
previously free functions with loads of parameters.
Finally, we introduce more structure to ranking. Instead of an anonymous
tuple, we define an explicit type with some helper types to hopefully
make the influence on ranking from each constituent piece a bit clearer.
This does seem to fix one bug around detecting the target for non-import
completions, but otherwise should not have any changes in behavior.
This is meant to be a precursor to improving completion ranking.
## Summary
I should have factored this better but this includes a drive-by move of
find_node to ruff_python_ast so ty_python_semantic can use it too.
* Fixes https://github.com/astral-sh/ty/issues/2017
## Test Plan
Snapshots galore