Commit Graph

13094 Commits

Author SHA1 Message Date
Aria Desires 3d4b0559f1
[ty] remove erroneous canonicalize (#21405)
Alternative implementation to
https://github.com/astral-sh/ruff/pull/21052
2025-11-12 15:47:33 -05:00
David Peter 2f6f3e1042
[ty] Faster subscript assignment checks for (unions of) `TypedDict`s (#21378)
## Summary

We synthesize a (potentially large) set of `__setitem__` overloads for
every item in a `TypedDict`. Previously, validation of subscript
assignments on `TypedDict`s relied on actually calling `__setitem__`
with the provided key and value types, which implied that we needed to
do the full overload call evaluation for this large set of overloads.
This PR improves the performance of subscript assignment checks on
`TypedDict`s by validating the assignment directly instead of calling
`__setitem__`.

This PR also adds better handling for assignments to subscripts on union
and intersection types (but does not attempt to make it perfect). It
achieves this by distributing the check over unions and intersections,
instead of calling `__setitem__` on the union/intersection directly. We
already do something similar when validating *attribute* assignments.

## Ecosystem impact

* A lot of diagnostics change their rule type, and/or split into
multiple diagnostics. The new version is more verbose, but easier to
understand, in my opinion
* Almost all of the invalid-key diagnostics come from pydantic, and they
should all go away (including many more) when we implement
https://github.com/astral-sh/ty/issues/1479
* Everything else looks correct to me. There may be some new diagnostics
due to the fact that we now check intersections.

## Test Plan

New Markdown tests.
2025-11-12 20:16:38 +01:00
Shunsuke Shibayama 9dd666d677
[ty] fix global symbol lookup from eager scopes (#21317)
## Summary

cf. https://github.com/astral-sh/ruff/pull/20962

In the following code, `foo` in the comprehension was not reported as
unresolved:

```python
# error: [unresolved-reference] "Name `foo` used when not defined"
foo
foo = [
    # no error!
    # revealed: Divergent
    reveal_type(x) for _ in () for x in [foo]
]

baz = [
    # error: [unresolved-reference] "Name `baz` used when not defined"
    # revealed: Unknown
    reveal_type(x) for _ in () for x in [baz]
]
```

In fact, this is a more serious bug than it looks: for `foo`,
[`explicit_global_symbol` is
called](6cc3393ccd/crates/ty_python_semantic/src/types/infer/builder.rs (L8052)),
causing a symbol that should actually be `Undefined` to be reported as
being of type `Divergent`.

This PR fixes this bug. As a result, the code in
`mdtest/regression/pr_20962_comprehension_panics.md` no longer panics.

## Test Plan

`corpus\cyclic_symbol_in_comprehension.py` is added.
New tests are added in `mdtest/comprehensions/basic.md`.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-11-12 10:15:51 -08:00
pyscripter a1d9cb5830
Added the PyScripter IDE to the list of "Who is using Ruff?" (#21402)
## Summary

Added the PyScripter IDE to the list of "Who is using Ruff?".

PyScripter is a popular python IDE that is using ruff for code
diagnostics, fixes and code formatting.
2025-11-12 18:10:08 +00:00
Nikolas Hearp 8a85a2961e
[`flake8-simplify`] Apply `SIM113` when index variable is of type `int` (#21395)
## Summary

Fixes #21393

Now the rule checks if the index variable is initialized as an `int`
type rather than only flagging if the index variable is initialized to
`0`. I used `ResolvedPythonType` to check if the index variable is an
`int` type.

## Test Plan

Updated snapshot test for `SIM113`.

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-11-12 17:54:39 +00:00
Micha Reiser 43427abb61
[ty] Improve semantic token classification for names (#21399) 2025-11-12 16:34:26 +00:00
David Peter 84c3cecad6
[ty] Baseline for subscript assignment diagnostics (#21404)
## Summary

Add (snapshot) tests for subscript assignment diagnostics. This is
mainly intended to establish a baseline before I hope to improve some of
these messages.
2025-11-12 15:29:26 +01:00
David Peter e8e8180888
[ty] Implicit type aliases: Add support for `typing.Union` (#21363)
## Summary

Add support for `typing.Union` in implicit type aliases / in value
position.

## Typing conformance tests

Two new tests are passing

## Ecosystem impact

* The 2k new `invalid-key` diagnostics on pydantic are caused by
https://github.com/astral-sh/ty/issues/1479#issuecomment-3513854645.
* Everything else I've checked is either a known limitation (often
related to type narrowing, because union types are often narrowed down
to a subset of options), or a true positive.

## Test Plan

New Markdown tests
2025-11-12 12:59:14 +01:00
David Peter f5cf672ed4
[ty] Reorganize walltime benchmarks (#21400) 2025-11-12 12:41:34 +01:00
David Peter 6322f37015
[ty] Better assertion message for benchmark diagnostics check (#21398)
I don't know why, but it always takes me an eternity to find the failing
project name a few lines below in the output. So I'm suggesting we just
add the project name to the assertion message.
2025-11-12 11:02:29 +01:00
Micha Reiser d272a623d3
[ty] Fix goto for `float` and `complex` in type annotation positions (#21388) 2025-11-12 07:54:25 +00:00
Micha Reiser 19c7994e90
[ty] Fix Escape handler in playground (#21397) 2025-11-12 08:54:14 +01:00
Dan Parizher 725ae69773
[`pydoclint`] Support NumPy-style comma-separated parameters (`DOC102`) (#20972) 2025-11-12 08:29:23 +01:00
Bhuminjay Soni d2c3996f4e
`UP035`: Consistently set the deprecated tag (#21396) 2025-11-12 08:17:29 +01:00
Shaygan Hooshyari 988c38c013
[ty] Skip eagerly evaluated scopes for attribute storing (#20856)
## Summary

Fix https://github.com/astral-sh/ty/issues/664

This PR adds support for storing attributes in comprehension scopes (any
eager scope.)

For example in the following code we infer type of `z` correctly:

```py
class C:
    def __init__(self):
        [None for self.z in range(1)]
reveal_type(C().z) # previously [unresolved-attribute] but now shows Unknown | int
```

The fix works by adjusting the following logics:

To identify if an attriute is an assignment to self or cls we need to
check the scope is a method. To allow comprehension scopes here we skip
any eager scope in the check.
Also at this stage the code checks if self or the first method argument
is shadowed by another binding that eager scope to prevent this:

```py
class D:
    g: int

class C:
    def __init__(self):
        [[None for self.g in range(1)] for self in [D()]]
reveal_type(C().g) # [unresolved-attribute]
```

When determining scopes that attributes might be defined after
collecting all the methods of the class the code also returns any
decendant scope that is eager and only has eager parents until the
method scope.

When checking reachability of a attribute definition if the attribute is
defined in an eager scope we use the reachability of the first non eager
scope which must be a method. This allows attributes to be marked as
reachable and be seen.


There are also which I didn't add support for:

```py
class C:
    def __init__(self):
        def f():
            [None for self.z in range(1)]
        f()

reveal_type(C().z) # [unresolved-attribute]
```

In the above example we will not even return the comprehension scope as
an attribute scope because there is a non eager scope (`f` function)
between the comprehension and the `__init__` method

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-11-11 14:45:34 -08:00
Andrew Gallant 164c2a6cc6 [ty] Sort keyword completions above everything else
It looks like VS Code does this forcefully. As in, I don't think we can
override it. It also seems like a plausibly good idea. But by us doing
it too, it makes our completion evaluation framework match real world
conditions. (To the extent that "VS Code" and "real world conditions"
are the same. Which... they aren't. But it's close, since VS Code is so
popular.)
2025-11-11 17:20:55 -05:00
Andrew Gallant 1bbe4f0d5e [ty] Add more keyword completions to scope completions
This should round out the rest of the set. I think I had hesitated doing
this before because some of these don't make sense in every context. But
I think identifying the correct context for every keyword could be quite
difficult. And at the very least, I think offering these at least as a
choice---even if they aren't always correct---is better than not doing
it at all.
2025-11-11 17:20:55 -05:00
Andrew Gallant cd7354a5c6 [ty] Add completion evaluation task for general keyword completions 2025-11-11 17:20:55 -05:00
Andrew Gallant ec48a47a88 [ty] Add `from <module> im<CURSOR>` completion evaluation task
Ideally this would have been added as part of #21291, but I forgot.
2025-11-11 17:20:55 -05:00
Alex Waygood 43297d3455
[ty] Support `isinstance()` and `issubclass()` narrowing when the second argument is a `typing.py` stdlib alias (#21391)
## Summary

A followup to https://github.com/astral-sh/ruff/pull/21386

## Test Plan

New mdtests added
2025-11-11 21:09:24 +00:00
Mahmoud Saada 4373974dd9
[ty] Fix false positive for Final attribute assignment in __init__ (#21158)
## Summary

Fixes https://github.com/astral-sh/ty/issues/1409

This PR allows `Final` instance attributes to be initialized in
`__init__` methods, as mandated by the Python typing specification (PEP
591). Previously, ty incorrectly prevented this initialization, causing
false positive errors.

The fix checks if we're inside an `__init__` method before rejecting
Final attribute assignments, allowing assignments during
instance initialization while still preventing reassignment elsewhere.

## Test Plan

- Added new test coverage in `final.md` for the reported issue with
`Self` annotations
- Updated existing tests that were incorrectly expecting errors 
- All 278 mdtest tests pass
- Manually tested with real-world code examples

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-11-11 12:54:05 -08:00
Aria Desires e4374f14ed
[ty] Consider `from thispackage import y` to re-export `y` in `__init__.pyi` (#21387)
Fixes https://github.com/astral-sh/ty/issues/1487

This one is a true extension of non-standard semantics, and is therefore
a certified Hot Take we might conclude is simply a Bad Take (let's see
what ecosystem tests say...).
2025-11-11 14:41:14 -05:00
Alex Waygood 03bd0619e9
[ty] Silence false-positive diagnostics when using `typing.Dict` or `typing.Callable` as the second argument to `isinstance()` (#21386) 2025-11-11 19:30:01 +00:00
Aria Desires bd8812127d
[ty] support absolute `from` imports introducing local submodules in `__init__.py` files (#21372)
By resolving `.` and the LHS of the from import during semantic
indexing, we can check if the LHS is a submodule of `.`, and handle
`from whatever.thispackage.x.y import z` exactly like we do `from .x.y
import z`.

Fixes https://github.com/astral-sh/ty/issues/1484
2025-11-11 13:04:42 -05:00
Alex Waygood 44b0c9ebac
[ty] Allow PEP-604 unions in stubs and `TYPE_CHECKING` blocks prior to 3.10 (#21379) 2025-11-11 14:33:43 +00:00
Micha Reiser 7b237d316f
Add option to provide a reason to `--add-noqa` (#21294)
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-11 14:03:46 +01:00
Micha Reiser 36cce347fd
Reduce notebook memory footprint (#21319) 2025-11-11 10:43:37 +01:00
Douglas Creager 33b942c7ad
[ty] Handle annotated `self` parameter in constructor of non-invariant generic classes (#21325)
This manifested as an error when inferring the type of a PEP-695 generic
class via its constructor parameters:

```py
class D[T, U]:
    @overload
    def __init__(self: "D[str, U]", u: U) -> None: ...
    @overload
    def __init__(self, t: T, u: U) -> None: ...
    def __init__(self, *args) -> None: ...

# revealed: D[Unknown, str]
# SHOULD BE: D[str, str]
reveal_type(D("string"))
```

This manifested because `D` is inferred to be bivariant in both `T` and
`U`. We weren't seeing this in the equivalent example for legacy
typevars, since those default to invariant. (This issue also showed up
for _covariant_ typevars, so this issue was not limited to bivariance.)

The underlying cause was because of a heuristic that we have in our
current constraint solver, which attempts to handle situations like
this:

```py
def f[T](t: T | None): ...
f(None)
```

Here, the `None` argument matches the non-typevar union element, so this
argument should not add any constraints on what `T` can specialize to.
Our previous heuristic would check for this by seeing if the argument
type is a subtype of the parameter annotation as a whole — even if it
isn't a union! That would cause us to erroneously ignore the `self`
parameter in our constructor call, since bivariant classes are
equivalent to each other, regardless of their specializations.

The quick fix is to move this heuristic "down a level", so that we only
apply it when the parameter annotation is a union. This heuristic should
go away completely 🤞 with the new constraint solver.
2025-11-10 19:46:49 -05:00
Aria Desires 9ce3230add
[ty] Make implicit submodule imports only occur in global scope (#21370)
This loses any ability to have "per-function" implicit submodule
imports, to avoid the "ok but now we need per-scope imports" and "ok but
this should actually introduce a global that only exists during this
function" problems. A simple and clean implementation with no weird
corners.

Fixes https://github.com/astral-sh/ty/issues/1482
2025-11-10 18:59:48 -05:00
Aria Desires 2bc6c78e26
[ty] introduce local variables for `from` imports of submodules in `__init__.py(i)` (#21173)
This rips out the previous implementation in favour of a new
implementation with 3 rules:

- **froms are locals**: a `from..import` can only define locals, it does
not have global
side-effects. Specifically any submodule attribute `a` that's implicitly
introduced by either
`from .a import b` or `from . import a as b` (in an `__init__.py(i)`) is
a local and not a
global. If you do such an import at the top of a file you won't notice
this. However if you do
such an import in a function, that means it will only be function-scoped
(so you'll need to do
it in every function that wants to access it, making your code less
sensitive to execution
    order).

- **first from first serve**: only the *first* `from..import` in an
`__init__.py(i)` that imports a
particular direct submodule of the current package introduces that
submodule as a local.
Subsequent imports of the submodule will not introduce that local. This
reflects the fact that
in actual python only the first import of a submodule (in the entire
execution of the program)
introduces it as an attribute of the package. By "first" we mean "the
first time in this scope
(or any parent scope)". This pairs well with the fact that we are
specifically introducing a
local (as long as you don't accidentally shadow or overwrite the local).

- **dot re-exports**: `from . import a` in an `__init__.pyi` is
considered a re-export of `a`
(equivalent to `from . import a as a`). This is required to properly
handle many stubs in the
    wild. Currently it must be *exactly* `from . import ...`.
    
This implementation is intentionally limited/conservative (notably,
often requiring a from import to be relative). I'm going to file a ton
of followups for improvements so that their impact can be evaluated
separately.


Fixes https://github.com/astral-sh/ty/issues/133
2025-11-10 23:04:56 +00:00
Dan Parizher 1fd852fb3f
[`ruff`] Ignore `str()` when not used for simple conversion (`RUF065`) (#21330)
## Summary

Fixed RUF065 (`logging-eager-conversion`) to only flag `str()` calls
when they perform a simple conversion that can be safely removed. The
rule now ignores `str()` calls with no arguments, multiple arguments,
starred arguments, or keyword unpacking, preventing false positives.

Fixes #21315

## Problem Analysis

The RUF065 rule was incorrectly flagging all `str()` calls in logging
statements, even when `str()` was performing actual conversion work
beyond simple type coercion. Specifically, the rule flagged:

- `str()` with no arguments - which returns an empty string
- `str(b"data", "utf-8")` with multiple arguments - which performs
encoding conversion
- `str(*args)` with starred arguments - which unpacks arguments
- `str(**kwargs)` with keyword unpacking - which passes keyword
arguments

These cases cannot be safely removed because `str()` is doing meaningful
work (encoding conversion, argument unpacking, etc.), not just redundant
type conversion.

The root cause was that the rule only checked if the function was
`str()` without validating the call signature. It didn't distinguish
between simple `str(value)` conversions (which can be removed) and more
complex `str()` calls that perform actual work.

## Approach

The fix adds validation to the `str()` detection logic in
`logging_eager_conversion.rs`:

1. **Check argument count**: Only flag `str()` calls with exactly one
positional argument (`str_call_args.args.len() == 1`)
2. **Check for starred arguments**: Ensure the single argument is not
starred (`!str_call_args.args[0].is_starred_expr()`)
3. **Check for keyword arguments**: Ensure there are no keyword
arguments (`str_call_args.keywords.is_empty()`)

This ensures the rule only flags cases like `str(value)` where `str()`
is truly redundant and can be removed, while ignoring cases where
`str()` performs actual conversion work.

The fix maintains backward compatibility - all existing valid test cases
continue to be flagged correctly, while the new edge cases are properly
ignored.

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-11-10 18:04:41 -05:00
Jack O'Connor 5f3e086ee4 [ty] implement `typing.NewType` by adding `Type::NewTypeInstance` 2025-11-10 14:55:47 -08:00
Aria Desires 039a69fa8c
[ty] supress inlay hints for `+1` and `-1` (#21368)
It's everyone's favourite language corner case!

Also having kicked the tires on it, I'm pretty happy to call this (in
conjunction with #21367):

Fixes https://github.com/astral-sh/ty/issues/494

There's cases where you can make noisy Literal hints appear, so we can
always iterate on it, but this handles like, 98% of the cases in the
wild, which is great.

---------

Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
2025-11-10 21:56:25 +00:00
Ibraheem Ahmed 3656b44877
[ty] Use type context for inference of generic constructors (#20933)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1228.

This PR is stacked on https://github.com/astral-sh/ruff/pull/21210.
2025-11-10 21:49:48 +00:00
Ibraheem Ahmed 98869f0307
[ty] Improve generic call expression inference (#21210)
## Summary

Implements https://github.com/astral-sh/ty/issues/1356 and https://github.com/astral-sh/ty/issues/136#issuecomment-3413669994.
2025-11-10 21:29:05 +00:00
Aria Desires d258302b08
[ty] supress some trivial expr inlay hints (#21367)
I'm not 100% sold on this implementation, but it's a strict improvement
and it adds a ton of snapshot tests for future iteration.

Part of https://github.com/astral-sh/ty/issues/494
2025-11-10 19:51:14 +00:00
Dan Parizher deeda56906
[`configuration`] Fix unclear error messages for line-length values exceeding `u16::MAX` (#21329)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-10 18:29:35 +00:00
justin f63a9f2334
[ty] Fix incorrect inference of `enum.auto()` for enums with non-`int` mixins, and imprecise inference of `enum.auto()` for single-member enums (#20541)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-11-10 17:53:08 +00:00
Dan Parizher e4dc406a3d
[`refurb`] Detect empty f-strings (`FURB105`) (#21348)
## Summary

Fixes FURB105 (`print-empty-string`) to detect empty f-strings in
addition to regular empty strings. Previously, the rule only flagged
`print("")` but missed `print(f"")`. This fix ensures both cases are
detected and can be automatically fixed.

Fixes #21346

## Problem Analysis

The FURB105 rule checks for unnecessary empty strings passed to
`print()` calls. The `is_empty_string` helper function was only checking
for `Expr::StringLiteral` with empty values, but did not handle
`Expr::FString` (f-strings). As a result, `print(f"")` was not being
flagged as a violation, even though it's semantically equivalent to
`print("")` and should be simplified to `print()`.

The issue occurred because the function used a `matches!` macro that
only checked for string literals:

```rust
fn is_empty_string(expr: &Expr) -> bool {
    matches!(
        expr,
        Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) if value.is_empty()
    )
}
```

## Approach

1. **Import the helper function**: Added `is_empty_f_string` to the
imports from `ruff_python_ast::helpers`, which already provides logic to
detect empty f-strings.

2. **Update `is_empty_string` function**: Changed the implementation
from a `matches!` macro to a `match` expression that handles both string
literals and f-strings:

   ```rust
   fn is_empty_string(expr: &Expr) -> bool {
       match expr {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) =>
value.is_empty(),
           Expr::FString(f_string) => is_empty_f_string(f_string),
           _ => false,
       }
   }
   ```

The fix leverages the existing `is_empty_f_string` helper function which
properly handles the complexity of f-strings, including nested f-strings
and interpolated expressions. This ensures the detection is accurate and
consistent with how empty strings are detected elsewhere in the
codebase.
2025-11-10 12:41:44 -05:00
Matthew Mckee 1d188476b6
[ty] provide `import` completion when in `from <name> <name>` statement (#21291)
<!--
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

Resolves https://github.com/astral-sh/ty/issues/1494

## Test Plan

Add a test showing if we are in `from <name> <name> ` we provide the
keyword completion "import"
2025-11-10 11:59:45 -05:00
Aria Desires 4821c050ef
[ty] elide redundant inlay hints for function args (#21365)
This elides the following inlay hints:

```py
foo([x=]x)
foo([x=]y.x)
foo([x=]x[0])
foo([x=]x(...))

# composes to complex situations
foo([x=]y.x(..)[0])
```

Fixes https://github.com/astral-sh/ty/issues/1514
2025-11-10 11:42:12 -05:00
Brent Westbrook 835e31b3ff
Fix syntax error false positive on alternative `match` patterns (#21362)
Summary
--

Fixes #21360 by using the union of names instead of overwriting them, as
Micha suggested originally on #21104.

This avoids overwriting the `n` name in the `Subscript` by the empty set
of names visited in the nested OR pattern before visiting the other arm
of the outer OR pattern.

Test Plan
--

A new inline test case taken from the issue
2025-11-10 10:51:51 -05:00
Brent Westbrook 8d1efe964a
Add a new "Opening a PR" section to the contribution guide (#21298)
Summary
--

This PR adds a new section to CONTRIBUTING.md describing the expected
contents of the PR summary and test plan, using the ecosystem report,
and communicating the status of a PR.

This seemed like a pretty good place to insert this in the document, at
the end of the advice on preparing actual code changes, but I'm
certainly open to other suggestions about both the content and
placement.

Test Plan
--

Future PRs :)

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-10 14:12:32 +00:00
Dan Parizher 04e7cecab3
[`flake8-simplify`] Fix SIM222 false positive for `tuple(generator) or None` (`SIM222`) (#21187)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-10 14:27:31 +01:00
Micha Reiser 84a810736d
Rebuild ruff binary instead of sharing it across jobs (#21361) 2025-11-10 14:27:07 +01:00
Micha Reiser f44598dc11
[ty] Fix `--exclude` and `src.exclude` merging (#21341) 2025-11-10 12:52:45 +01:00
David Peter ab46c8de0f
[ty] Add support for properties that return `Self` (#21335)
## Summary

Detect usages of implicit `self` in property getters, which allows us to
treat their signature as being generic.

closes https://github.com/astral-sh/ty/issues/1502

## Typing conformance

Two new type assertions that are succeeding.

## Ecosystem results

Mostly look good. There are a few new false positives related to a bug
with constrained typevars that is unrelated to the work here. I reported
this as https://github.com/astral-sh/ty/issues/1503.

## Test Plan

Added regression tests.
2025-11-10 11:13:36 +01:00
Henry Schreiner a6f2dee33b
Add upstream linter URL to `ruff linter --output-format=json` (#21316) 2025-11-10 10:42:09 +01:00
David Peter 238f151371
[ty] Add support for `Optional` and `Annotated` in implicit type aliases (#21321)
## Summary

Add support for `Optional` and `Annotated` in implicit type aliases

part of https://github.com/astral-sh/ty/issues/221

## Typing conformance changes

New expected diagnostics.

## Ecosystem

A lot of true positives, some known limitations unrelated to this PR.

## Test Plan

New Markdown tests
2025-11-10 10:24:38 +01:00
Micha Reiser 3fa609929f
Save rust cache for CI jobs on main only (#21359) 2025-11-10 10:04:44 +01:00