## 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
This appears to have been a copy/paste error from the list example, as
the subscript is not present in the original next/iter example only in
the case where the error case is shown. While in the specific example
code the subscript actually has no effect, it does make the example
slightly confusing.
Consider the following variations, first the example from the docs
unchanged and second the same code but not hitting the intended error
case (due to using a non-empty collection):
```console
$ python3 -c 'next(iter(range(0)))[0]'
Traceback (most recent call last):
File "<string>", line 1, in <module>
StopIteration
$ python3 -c 'next(iter(range(1)))[0]'
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: 'int' object is not subscriptable
```
## Test Plan
Not directly tested, however see inline snippets above.
## 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.
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
requests.)
- Does this pull request include references to any relevant issues?
-->
## Summary
Was looking over that PR and this looked wrong.
panic introduced in #22076
## Test Plan
Before running:
```bash
cargo run -p ty check test.py --force-exclude --no-progress
```
would result in a panic
```text
thread 'main' (162713) panicked at crates/ty/src/args.rs:459:17:
internal error: entered unreachable code: Clap should make this impossible
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
Now it does not.
## 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
Fixes https://github.com/astral-sh/ty/issues/2090. Quoting my rationale
from that issue:
> A PR that only touches code in [one of these crates] should never have
any impact on memory usage or diagnostics produced. And the comments
from the bot just lead to additional notifications which is annoying.
I _think_ I've got the syntax right here. The
[docs](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)
say:
> The order that you define paths patterns matters:
>
> - A matching negative pattern (prefixed with !) after a positive match
will exclude the path.
> - A matching positive pattern after a negative match will include the
path again.
## Test Plan
No idea? Merge it and see?
## Summary
Fixes https://github.com/astral-sh/ty/issues/1885.
It wasn't obvious to me that there was a deliberate order to the way
these rules were listed in our reference docs -- it looked like it was
_nearly_ alphabetical, but not quite. I think it's simpler if we just
list them in alphabetical order.
## Test Plan
The output from running `cargo dev generate-all` (committed as part of
this PR) looks correct!
## Summary
Currently, the proposed fix for https://docs.astral.sh/ruff/rules/print/
violates https://docs.astral.sh/ruff/rules/root-logger-call/. Thus,
let's change the proposal to make LOG015 happy as well.
## Test Plan
Test manually in a project that has both T201 and LOG015 enabled and run
them over the previous and proposed code. Is there continuous testing of
the code snippets from the docs?
## Summary
* Related to, but does not handle
https://github.com/astral-sh/ty/issues/2021
## Test Plan
I also added some snapshot tests for future work on non-standard
attribute docstrings (didn't want to highlight them if we don't
recognize them elsewhere).
## 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).
## Summary
Add lockfiles for all mdtests which make use of external dependencies.
When running tests normally, we use this lockfile when creating the
temporary venv using `uv sync --locked`. A new
`MDTEST_UPGRADE_LOCKFILES` environment variable is used to switch to a
mode in which those lockfiles can be updated or regenerated. When using
the Python mdtest runner, this environment variable is automatically set
(because we use this command while developing, not to simulate exactly
what happens in CI). A command-line flag is provided to opt out of this.
## Test Plan
### Using the mdtest runner
#### Adding a new test (no lockfile yet)
* Removed `attrs.lock` to simulate this
* Ran `uv run crates/ty_python_semantic/mdtest.py -e external/`. The
lockfile is generated and the test succeeds.
#### Upgrading/downgrading a dependency
* Changed pydantic requirement from `pydantic==2.12.2` to
`pydantic==2.12.5` (also tested with `2.12.0`)
* Ran `uv run crates/ty_python_semantic/mdtest.py -e external/`. The
lockfile is updated and the test succeeds.
### Using cargo
#### Adding a new test (no lockfile yet)
* Removed `attrs.lock` to simulate this
* Ran `MDTEST_EXTERNAL=1 cargo test -p ty_python_semantic --test mdtest
mdtest__external` "naively", which outputs:
> Failed to setup in-memory virtual environment with dependencies:
Lockfile not found at
'/home/shark/ruff/crates/ty_python_semantic/resources/mdtest/external/attrs.lock'.
Run with `MDTEST_UPGRADE_LOCKFILES=1` to generate it.
* Ran `MDTEST_UPGRADE_LOCKFILES=1 MDTEST_EXTERNAL=1 cargo test -p
ty_python_semantic --test mdtest mdtest__external`. The lockfile is
updated and the test succeeds.
#### Upgrading/downgrading a dependency
* Changed pydantic requirement from `pydantic==2.12.2` to
`pydantic==2.12.5` (also tested with `2.12.0`)
* Ran `MDTEST_EXTERNAL=1 cargo test -p ty_python_semantic --test mdtest
mdtest__external` "naively", which outputs a similar error message as
above.
* Ran the command suggested in the error message (`MDTEST_EXTERNAL=1
MDTEST_UPGRADE_LOCKFILES=1 cargo test -p ty_python_semantic --test
mdtest mdtest__external`). The lockfile is updated and the test
succeeds.