Commit Graph

8598 Commits

Author SHA1 Message Date
Aria Desires e5818d89fd
[ty] Add "import ..." code-action for unresolved references (#21629)
## Summary

Originally I planned to feed this in as a `fix` but I realized that we
probably don't want to be trying to resolve import suggestions while
we're doing type inference. Thus I implemented this as a fallback when
there's no fixes on a diagnostic, which can use the full lsp machinery.

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

## Test Plan

Works in the IDE, added some e2e tests.
2025-11-27 10:06:38 -05:00
Alex Waygood a7d48ffd40
[ty] Add subdiagnostic hint if a variable with type `Never` is used in a type expression (#21660) 2025-11-27 12:48:18 +00:00
Carl Meyer 77f8fa6906
[ty] more precise inference for a failed specialization (#21651)
## Summary

Previously if an explicit specialization failed (e.g. wrong number of
type arguments or violates an upper bound) we just inferred `Unknown`
for the entire type. This actually caused us to panic on an a case of a
recursive upper bound with invalid specialization; the upper bound would
oscillate indefinitely in fixpoint iteration between `Unknown` and the
given specialization. This could be fixed with a cycle recovery
function, but in this case there's a simpler fix: if we infer
`C[Unknown]` instead of `Unknown` for an invalid attempt to specialize
`C`, that allows fixpoint iteration to quickly converge, as well as
giving a more precise type inference.

Other type checkers actually just go with the attempted specialization
even if it's invalid. So if `C` has a type parameter with upper bound
`int`, and you say `C[str]`, they'll emit a diagnostic but just go with
`C[str]`. Even weirder, if `C` has a single type parameter and you say
`C[str, bytes]`, they'll just go with `C[str]` as the type. I'm not
convinced by this approach; it seems odd to have specializations
floating around that explicitly violate the declared upper bound, or in
the latter case aren't even the specialization the annotation requested.
I prefer `C[Unknown]` for this case.

Fixing this revealed an issue with `collections.namedtuple`, which
returns `type[tuple[Any, ...]]`. Due to
https://github.com/astral-sh/ty/issues/1649 we consider that to be an
invalid specialization. So previously we returned `Unknown`; after this
PR it would be `type[tuple[Unknown]]`, leading to more false positives
from our lack of functional namedtuple support. To avoid that I added an
explicit Todo type for functional namedtuples for now.

## Test Plan

Added and updated mdtests.

The conformance suite changes have to do with `ParamSpec`, so no
meaningful signal there.

The ecosystem changes appear to be the expected effects of having more
precise type information (including occurrences of known issues such as
https://github.com/astral-sh/ty/issues/1495 ). Most effects are just
changes to types in diagnostics.
2025-11-27 13:44:28 +01:00
Micha Reiser 7c7f8d1a17
[ty] Make inlay hint clickable in playground (#21656) 2025-11-27 13:29:11 +01:00
Micha Reiser 761031f729
[ty] Add code action support to playground (#21655) 2025-11-27 10:59:57 +01:00
Alex Waygood 792ec3e96e
Improve docs on how to stop Ruff and ty disagreeing with each other (#21644)
## Summary

Lots of Ruff rules encourage you to make changes that might then cause
ty to start complaining about Liskov violations. Most of these Ruff
rules already refrain from complaining about a method if they see that
the method is decorated with `@override`, but this usually isn't
documented. This PR updates the docs of many Ruff rules to note that
they refrain from complaining about `@override`-decorated methods, and
also adds a similar note to the ty `invalid-method-override`
documentation.

Helps with
https://github.com/astral-sh/ty/issues/1644#issuecomment-3581663859

## Test Plan

- `uvx prek run -a` locally
- CI on this PR
2025-11-27 08:18:21 +00:00
Dhruv Manilawala c7107a5a90
[ty] Use `zip` to perform explicit specialization (#21635)
## Summary

This PR updates the explicit specialization logic to avoid using the
call machinery.

Previously, the logic would use the call machinery by converting the
list of type variables into a `Binding` with a single `Signature` where
all the type variables are positional-only parameters with bounds and
constraints as the annotated type and the default type as the default
parameter value. This has the advantage that it doesn't need to
implement any specific logic but the disadvantages are subpar diagnostic
messages as it would use the ones specific to a function call. But, an
important disadvantage is that the kind of type variable is lost in this
translation which becomes important in #21445 where a `ParamSpec` can
specialize into a list of types which is provided using list literal.
For example,

```py
class Foo[T, **P]: ...

Foo[int, [int, str]]
```

This PR converts the logic to use a simple loop using `zip_longest` as
all type variables and their corresponding type argument maps on a 1-1
basis. They cannot be specified using keyword argument either e.g.,
`dict[_VT=str, _KT=int]` is invalid.

This PR also makes an initial attempt to improve the diagnostic message
to specifically target the specialization part by using words like "type
argument" instead of just "argument" and including information like the
type variable, bounds, and constraints. Further improvements can be made
by highlighting the type variable definition or the bounds / constraints
as a sub-diagnostic but I'm going to leave that as a follow-up.

## Test Plan

Update messages in existing test cases.
2025-11-27 03:52:22 +00:00
Carl Meyer e0f3a064b9
[ty] don't iterate over a hashset (#21649)
## Summary

This caused "deterministic but chaotic" ordering of some intersection
types in diagnostics. When calling a union, we infer the argument type
once per matching parameter type, intersecting the inferred types for
the argument expression, and we did that in an unpredictable order.

We do need a hashset here for de-duplication. Sometimes we call large
unions where the type for a given parameter is the same across the
union, we should infer the argument once per parameter type, not once
per union element. So use an `FxIndexSet` instead of an `FxHashSet`.

## Test Plan

With this change, switching between `main` and
https://github.com/astral-sh/ruff/pull/21646 no longer changes the
ordering of the intersection type in the test in
cca3a8045d
2025-11-26 16:39:49 -08:00
Shunsuke Shibayama 2c0c5ff4e7
[ty] handle recursive type inference properly (#20566)
## Summary

Derived from #17371

Fixes astral-sh/ty#256
Fixes https://github.com/astral-sh/ty/issues/1415
Fixes https://github.com/astral-sh/ty/issues/1433
Fixes https://github.com/astral-sh/ty/issues/1524

Properly handles any kind of recursive inference and prevents panics.

---

Let me explain techniques for converging fixed-point iterations during
recursive type inference.
There are two types of type inference that naively don't converge
(causing salsa to panic): divergent type inference and oscillating type
inference.

### Divergent type inference

Divergent type inference occurs when eagerly expanding a recursive type.
A typical example is this:

```python
class C:
    def f(self, other: "C"):
        self.x = (other.x, 1)

reveal_type(C().x) # revealed: Unknown | tuple[Unknown | tuple[Unknown | tuple[..., Literal[1]], Literal[1]], Literal[1]]
```

To solve this problem, we have already introduced `Divergent` types
(https://github.com/astral-sh/ruff/pull/20312). `Divergent` types are
treated as a kind of dynamic type [^1].

```python
Unknown | tuple[Unknown | tuple[Unknown | tuple[..., Literal[1]], Literal[1]], Literal[1]]
=> Unknown | tuple[Divergent, Literal[1]]
```

When a query function that returns a type enters a cycle, it sets
`Divergent` as the cycle initial value (instead of `Never`). Then, in
the cycle recovery function, it reduces the nesting of types containing
`Divergent` to converge.

```python
0th: Divergent
1st: Unknown | tuple[Divergent, Literal[1]]
2nd: Unknown | tuple[Unknown | tuple[Divergent, Literal[1]], Literal[1]]
=> Unknown | tuple[Divergent, Literal[1]]
```

Each cycle recovery function for each query should operate only on the
`Divergent` type originating from that query.
For this reason, while `Divergent` appears the same as `Any` to the
user, it internally carries some information: the location where the
cycle occurred. Previously, we roughly identified this by having the
scope where the cycle occurred, but with the update to salsa, functions
that create cycle initial values ​​can now receive a `salsa::Id`
(https://github.com/salsa-rs/salsa/pull/1012). This is an opaque ID that
uniquely identifies the cycle head (the query that is the starting point
for the fixed-point iteration). `Divergent` now has this `salsa::Id`.

### Oscillating type inference

Now, another thing to consider is oscillating type inference.
Oscillating type inference arises from the fact that monotonicity is
broken. Monotonicity here means that for a query function, if it enters
a cycle, the calculation must start from a "bottom value" and progress
towards the final result with each cycle. Monotonicity breaks down in
type systems that have features like overloading and overriding.

```python
class Base:
    def flip(self) -> "Sub":
        return Sub()

class Sub(Base):
    def flip(self) -> "Base":
        return Base()

class C:
    def __init__(self, x: Sub):
        self.x = x

    def replace_with(self, other: "C"):
        self.x = other.x.flip()

reveal_type(C(Sub()).x)
```

Naive fixed-point iteration results in `Divergent -> Sub -> Base -> Sub
-> ...`, which oscillates forever without diverging or converging. To
address this, the salsa API has been modified so that the cycle recovery
function receives the value of the previous cycle
(https://github.com/salsa-rs/salsa/pull/1012).
The cycle recovery function returns the union type of the current cycle
and the previous cycle. In the above example, the result type for each
cycle is `Divergent -> Sub -> Base (= Sub | Base) -> Base`, which
converges.

The final result of oscillating type inference does not contain
`Divergent` because `Divergent` that appears in a union type can be
removed, as is clear from the expansion. This simplification is
performed at the same time as nesting reduction.

```
T | Divergent = T | (T | (T | ...)) = T
```

[^1]: In theory, it may be possible to strictly treat types containing
`Divergent` types as recursive types, but we probably shouldn't go that
deep yet. (AFAIK, there are no PEPs that specify how to handle
implicitly recursive types that aren't named by type aliases)

## Performance analysis

A happy side effect of this PR is that we've observed widespread
performance improvements!
This is likely due to the removal of the `ITERATIONS_BEFORE_FALLBACK`
and max-specialization depth trick
(https://github.com/astral-sh/ty/issues/1433,
https://github.com/astral-sh/ty/issues/1415), which means we reach a
fixed point much sooner.

## Ecosystem analysis

The changes look good overall.
You may notice changes in the converged values ​​for recursive types,
this is because the way recursive types are normalized has been changed.
Previously, types containing `Divergent` types were normalized by
replacing them with the `Divergent` type itself, but in this PR, types
with a nesting level of 2 or more that contain `Divergent` types are
normalized by replacing them with a type with a nesting level of 1. This
means that information about the non-divergent parts of recursive types
is no longer lost.

```python
# previous
tuple[tuple[Divergent, int], int] => Divergent
# now
tuple[tuple[Divergent, int], int] => tuple[Divergent, int]
```

The false positive error introduced in this PR occurs in class
definitions with self-referential base classes, such as the one below.

```python
from typing_extensions import Generic, TypeVar

T = TypeVar("T")
U = TypeVar("U")

class Base2(Generic[T, U]): ...

# TODO: no error
# error: [unsupported-base] "Unsupported class base with type `<class 'Base2[Sub2, U@Sub2]'> | <class 'Base2[Sub2[Unknown], U@Sub2]'>`"
class Sub2(Base2["Sub2", U]): ...
```

This is due to the lack of support for unions of MROs, or because cyclic
legacy generic types are not inferred as generic types early in the
query cycle.

## Test Plan

All samples listed in astral-sh/ty#256 are tested and passed without any
panic!

## Acknowledgments

Thanks to @MichaReiser for working on bug fixes and improvements to
salsa for this PR. @carljm also contributed early on to the discussion
of the query convergence mechanism proposed in this PR.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-11-26 08:50:26 -08:00
Dan Parizher adf4f1e3f4
[`flake8-bandit`] Handle string literal bindings in suspicious-url-open-usage (`S310`) (#21469)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-26 09:21:50 +00:00
Micha Reiser 3dbbb76654
Use `diagnostic_diff` testing for flake8-bandit preview tests (#21637) 2025-11-26 09:13:45 +00:00
Luca Chiodini b72120f7ef
[ty] Semantic tokens: mark comprehension targets as definitions (#21636) 2025-11-26 09:33:14 +01:00
Shahar Naveh 33713a7e2a
Add rule to detect unnecessary class properties (#21535)
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Amethyst Reese <amethyst@n7.gg>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-26 09:31:22 +01:00
Aria Desires 5364256190
[ty] hotfix panic in semantic tokens (#21632)
Fixes https://github.com/astral-sh/ty/issues/1637
2025-11-25 17:09:46 -05:00
Alex Waygood 81c97e9e94
[ty] Implement `typing.override` (#21627)
## Summary

Part of https://github.com/astral-sh/ty/issues/155. This implements the
basic check (`@override`-decorated methods should override things!), but
not the strict check specified in
https://typing.python.org/en/latest/spec/class-compat.html#strict-enforcement-per-project,
which should be a separate error code.

## Test Plan

mdtests and snapshots

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-11-25 10:42:40 -08:00
Ibraheem Ahmed 294f863523
[ty] Avoid expression reinference for diagnostics (#21267)
## Summary

We now use the type context for a lot of things, so re-inferring without
type context actually makes diagnostics more confusing (in most cases).
2025-11-25 09:24:00 -08:00
Rasmus Nygren 4628180fac [ty] Improve autocomplete suppressions of keywords in variable bindings
Autocomplete suggestions were not suppressed correctly during
some variable bindings if the parameter name was currently
matching a keyword. E.g. `def f(foo<CURSOR>` was handled
correctly but not `def f(in<CURSOR>`.
2025-11-25 09:09:38 -05:00
Rasmus Nygren de32247f30 [ty] Only suggest completions based on text before the cursor
Previously we extracted the entire token as the query
independently of the cursor position. By not doing that
you avoid having to do special range handling
to figure out the start position of the current token.

It's likely also more intuitive from a user perspective
to only consider characters left of the cursor when
suggesting autocompletions.
2025-11-25 09:09:38 -05:00
Aria Desires 209ea06592
Implement goto-definition and find-references for global/nonlocal statements (#21616)
## Summary

The implementation here is to just record the idents of these statements
in `scopes_by_expression` (which already supported idents but only ones
that happened to appear in expressions), so that `definitions_for_name`
Just Works.

goto-type (and therefore hover) notably does not work on these
statements because the typechecker does not record info for them. I am
tempted to just introduce `type_for_name` which runs
`definitions_for_name` to find other expressions and queries the
inferred type... but that's a bit whack because it won't be the computed
type at the right point in the code. It probably wouldn't be
particularly expensive to just compute/record the type at those nodes,
as if they were a load, because global/nonlocal is so scarce?

## Test Plan

Snapshot tests added/re-enabled.
2025-11-25 08:56:57 -05:00
Matthew Mckee 88bfc32dfc
[ty] Inlay Hint edit follow up (#21621)
<!--
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

Don't allow edits of some more invalid syntax types.

## Test Plan

Add a test for `x = Literal['a']` (similar) to show we don't allow
edits.
2025-11-25 08:56:14 -05:00
Aria Desires 66d233134f
[ty] Implement lsp support for string annotations (#21577)
Fixes https://github.com/astral-sh/ty/issues/1009

## Summary

This adds support for:

* semantic-tokens (syntax highlighting)
* goto-type **(partially implemented, but want to land as-is)**
* goto-declaration
* goto-definition (falls out of goto-declaration)
* hover **(limited by goto-type)**
* find-references
* rename-references (falls out of find-references)

There are 3 major things being introduced here:

* `TypeInferenceBuilder::string_annotations` is a `FxHashSet` of exprs
which were determined to be string annotations during inference. It's
bubbled up in `extras` to hopefully minimize the overhead as in most
contexts it's empty.
* Very happy to hear if this is too hacky and if I should do something
better, but it's IMO important that we get an authoritative answer on
whether something is a string annotation or not.
* `SemanticModel::enter_string_annotation` checks if the expr was marked
by `TypeInferenceBuilder::string_annotations` and then parses the subast
and produces a sub-SemanticModel that sets
`SemanticModel::in_string_annotation_expr`. This expr will be used by
the model whenever we need to query e.g. the scope of the current
expression (otherwise the code will constantly panic as the subast nodes
are not in the current File's AST)
* This hazard consequently encouraged me to refactor a bunch of code to
replace uses of file/db with SemanticModel to minimize hazards (it is no
longer as safe to randomly materialize a SemanticModel in the middle of
analysis, you need to thread through the one you have in case it has
`in_string_annotation_expr` set).
* `GotoTarget::StringAnnotationSubexpr` (and a semantic-tokens impl)
which involves invoking `SemanticModel::enter_string_annotation` before
invoking the same kind of subroutine a normal expression would.
* goto-type (and consequently displaying the type in hover) is the main
hole here, because we can only get the type iff the string annotation is
the entire subexpression (i.e. we can get the type of `"int"` but not
the parts of `"int | str"`). This is shippable IMO.

## Test Plan

Messed around in IDE, wrote a ton of tests.
2025-11-25 13:31:04 +00:00
Micha Reiser 15cb41c1f9
[ty] Add 'remove unused ignore comment' code action (#21582)
## Summary

This PR adds a code action to remove unused ignore comments.

This PR also includes some infrastructure boilerplate to set up code
actions in the editor:

* Extend `snapshot-diagnostics` to render fixes
* Render fixes when using `--output-format=full`
* Hook up edits and the code action request in the LSP
* Add the `Unnecessary` tag to `unused-ignore-comment` diagnostics
* Group multiple unused codes into a single diagnostic

The same fix can be used on the CLI once we add `ty fix` 

Note: `unused-ignore-comment` is currently disabled by default.


https://github.com/user-attachments/assets/f9e21087-3513-4156-85d7-a90b1a7a3489
2025-11-25 08:08:21 -05:00
Micha Reiser eddb9ad38d
[ty] Refactor `CheckSuppressionContext` to use `DiagnosticGuard` (#21587) 2025-11-25 10:54:42 +00:00
Alex Waygood b19ddca69b
[ty] Improve several "Did you mean?" suggestions (#21597) 2025-11-25 10:29:01 +00:00
Alex Waygood adf095e889
[ty] Extend Liskov checks to also cover classmethods and staticmethods (#21598)
## Summary

Building on https://github.com/astral-sh/ruff/pull/21436.

There's nothing conceptually more complicated about this, it just
requires its own set of tests and its own subdiagnostic hint.

I also uncovered another inconsistency between mypy/pyright/pyrefly,
which is fun. In this case, I suggest we go with pyright's behaviour.

## Test Plan

mdtests/snapshots
2025-11-24 23:14:06 +00:00
Jack O'Connor 0631e72187
[ty] support generic aliases in `type[...]`, like `type[C[int]]` (#21552)
Closes https://github.com/astral-sh/ty/issues/1101.
2025-11-24 13:56:42 -08:00
Alex Waygood bab688b76c
[ty] Retain the function-like-ness of `Callable` types when binding `self` (#21614)
## Summary

For something like this:

```py
from typing import Callable

def my_lossy_decorator(fn: Callable[..., int]) -> Callable[..., int]:
    return fn

class MyClass:
    @my_lossy_decorator
    def method(self) -> int:
        return 42
```

we will currently infer the type of `MyClass.method` as a function-like
`Callable`, but we will infer the type of `MyClass().method` as a
`Callable` that is _not_ function-like. That's because a `CallableType`
currently "forgets" whether it was function-like or not during the
`bound_self` transformation:


a57e291311/crates/ty_python_semantic/src/types.rs (L10985-L10987)

This seems incorrect, and it's quite different to what we do when
binding the `self` parameter of `FunctionLiteral` types: `BoundMethod`
types are all seen as subtypes of function-like `Callable` supertypes --
here's `BoundMethodType::into_callable_type`:


a57e291311/crates/ty_python_semantic/src/types.rs (L10844-L10860)

The bug here is also causing lots of false positives in the ecosystem
report on https://github.com/astral-sh/ruff/pull/21611: a decorated
method on a subclass is currently not seen as validly overriding an
undecorated method with the same signature on a superclass, because the
undecorated superclass method is seen as function-like after binding
`self` whereas the decorated subclass method is not.

Fixing the bug required adding a new API in `protocol_class.rs`, because
it turns out that for our purposes in protocol subtyping/assignability,
we really do want a callable type to forget its function-like-ness when
binding `self`.

I initially tried out this change without changing anything in
`protocol_class.rs`. However, it resulted in many ecosystem false
positives and new false positives on the typing conformance test suite.
This is because it would mean that no protocol with a `__call__` method
would ever be seen as a subtype of a `Callable` type, since the
`__call__` method on the protocol would be seen as being function-like
whereas the `Callable` type would not be seen as function-like.

## Test Plan

Added an mdtest that fails on `main`
2025-11-24 21:14:03 +00:00
Douglas Creager 7e277667d1
[ty] Distinguish "unconstrained" from "constrained to any type" (#21539)
Before, we would collapse any constraint of the form `Never ≤ T ≤
object` down to the "always true" constraint set. This is correct in
terms of BDD semantics, but loses information, since "not constraining a
typevar at all" is different than "constraining a typevar to take on any
type". Once we get to specialization inference, we should fall back on
the typevar's default for the former, but not for the latter.

This is much easier to support now that we have a sequent map, since we
need to treat `¬(Never ≤ T ≤ object)` as being impossible, and prune it
when we walk through BDD paths, just like we do for other impossible
combinations.
2025-11-24 15:23:09 -05:00
Matthew Mckee 6f9265d78d
[ty] Double click to insert inlay hint (#21600)
<!--
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/317#issuecomment-3567398107.

I can't get the auto import working great.

I haven't added many places where we specify that the type display is
invalid syntax.

## Test Plan

Nothing yet
2025-11-24 19:48:30 +00:00
Alex Waygood 0c6d652b5f
[ty] Switch the error code from `unresolved-attribute` to `possibly-missing-attribute` for submodules that may not be available (#21618) 2025-11-24 19:15:45 +00:00
Douglas Creager 03fe560164
[ty] Substitute for `typing.Self` when checking protocol members (#21569)
This patch updates our protocol assignability checks to substitute for
any occurrences of `typing.Self` in method signatures, replacing it with
the class being checked for assignability against the protocol.

This requires a new helper method on signatures, `apply_self`, which
substitutes occurrences of `typing.Self` _without_ binding the `self`
parameter.

We also update the `try_upcast_to_callable` method. Before, it would
return a `Type`, since certain types upcast to a _union_ of callables,
not to a single callable. However, even in that case, we know that every
element of the union is a callable. We now return a vector of
`CallableType`. (Actually a smallvec to handle the most common case of a
single callable; and wrapped in a new type so that we can provide helper
methods.) If there is more than one element in the result, it represents
a union of callables. This lets callers get at the `CallableType`
instances in a more type-safe way. (This makes it easier for our
protocol checking code to call the new `apply_self` helper.) We also
provide an `into_type` method so that callers that really do want a
`Type` can get the original result easily.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-11-24 14:05:09 -05:00
Andrew Gallant 68343e7edf [ty] Don't suggest things that aren't subclasses of `BaseException` after `raise`
This only applies to items that have a type associated with them. That
is, things that are already in scope. For items that don't have a type
associated with them (i.e., suggestions from auto-import), we still
suggest them since we can't know if they're appropriate or not. It's not
quite clear on how best to improve here for the auto-import case. (Short
of, say, asking for the type of each such symbol. But the performance
implications of that aren't known yet.)

Note that because of auto-import, we were still suggesting
`NotImplemented` even though astral-sh/ty#1262 specifically cites it as
the motivating example that we *shouldn't* suggest. This was occuring
because auto-import was including symbols from the `builtins` module,
even though those are actually already in scope. So this PR also gets
rid of those suggestions from auto-import.

Overall, this means that, at least, `raise NotImpl` won't suggest
`NotImplemented`.

Fixes astral-sh/ty#1262
2025-11-24 12:55:30 -05:00
Alex Waygood a57e291311
[ty] Add hint about resolved Python version when a user attempts to import a member added on a newer version (#21615)
## Summary

Fixes https://github.com/astral-sh/ty/issues/1620. #20909 added hints if
you do something like this and your Python version is set to 3.10 or
lower:

```py
import typing

typing.LiteralString
```

And we also have hints if you try to do something like this and your
Python version is set too low:

```py
from stdlib_module import new_submodule
```

But we don't currently have any subdiagnostic hint if you do something
like _this_ and your Python version is set too low:

```py
from typing import LiteralString
```

This PR adds that hint!

## Test Plan

snapshots

---------

Co-authored-by: Aria Desires <aria.desires@gmail.com>
2025-11-24 15:12:01 +00:00
Alex Waygood 35bfcff24d
[ty] Add failing mdtest for known `Protocol` panic (#21594)
## Summary

This PR adds a failing mdtest for the panic in
https://github.com/astral-sh/ty/issues/1587. The added snippet currently
panics with this query stacktrace:

```
error[panic]: Panicked at /Users/alexw/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/17bc55d/src/function/execute.rs:321:21 when checking `/Users/alexw/dev/ruff/foo.py`: `ClassLiteral < 'db >::explicit_bases_(Id(4c09)): execute: too many cycle iterations`
info: This indicates a bug in ty.
info: If you could open an issue at https://github.com/astral-sh/ty/issues/new?title=%5Bpanic%5D, we'd be very appreciative!
info: Platform: macos aarch64
info: Version: ruff/0.14.5+105 (d24c891a4 2025-11-22)
info: Args: ["target/debug/ty", "check", "foo.py", "--python-version=3.14"]
info: run with `RUST_BACKTRACE=1` environment variable to show the full backtrace information
info: query stacktrace:
   0: cached_protocol_interface(Id(6805))
             at crates/ty_python_semantic/src/types/protocol_class.rs:795
   1: is_equivalent_to_object_inner(Id(8003))
             at crates/ty_python_semantic/src/types/instance.rs:667
   2: infer_deferred_types(Id(1406))
             at crates/ty_python_semantic/src/types/infer.rs:140
             cycle heads: infer_definition_types(Id(140b)) -> iteration = 200, TypeVarInstance < 'db >::lazy_bound_(Id(5802)) -> iteration = 200
   3: TypeVarInstance < 'db >::lazy_bound_(Id(5803))
             at crates/ty_python_semantic/src/types.rs:8827
   4: infer_definition_types(Id(140c))
             at crates/ty_python_semantic/src/types/infer.rs:94
   5: infer_deferred_types(Id(1405))
             at crates/ty_python_semantic/src/types/infer.rs:140
   6: TypeVarInstance < 'db >::lazy_bound_(Id(5802))
             at crates/ty_python_semantic/src/types.rs:8827
   7: infer_definition_types(Id(140b))
             at crates/ty_python_semantic/src/types/infer.rs:94
   8: infer_scope_types(Id(1000))
             at crates/ty_python_semantic/src/types/infer.rs:70
   9: check_file_impl(Id(c00))
             at crates/ty_project/src/lib.rs:535
```

It's not totally clear to me how to fix this or to what extent it might
be a bug in our `Protocol` internals rather than a bug in our `TypeVar`
internals. (It's sort of interesting that we're trying to evaluate the
upper bound of any `TypeVar`s here!) @carljm suggested that it would be
a good idea to add a failing mdtest in the meantime to document the
panic, which I agree with.

## Test Plan

I verified that we panic on this snippet, and that the test fails if I
remove the `expect-panic` assertion or if I change the asserted error
message.

I experimented with ways of minimizing the snippet further, but I think
any further minimization takes the snippet further away from something a
user would actually be likely to write -- so I think is probably
counterproductive. The failing test added in this PR isn't unreasonable
code at the end of the day; I've seen Python like it in the wild.
2025-11-24 08:21:15 +00:00
Dan Parizher 474b00568a
[`parser`] Fix panic when parsing IPython escape command expressions (#21480)
## Summary

Fixes a panic when parsing IPython escape commands with `Help` kind
(`?`) in expression contexts. The parser now reports an error instead of
panicking.

Fixes #21465.

## Problem

The parser panicked with `unreachable!()` in
`parse_ipython_escape_command_expression` when encountering escape
commands with `Help` kind (`?`) in expression contexts, where only
`Magic` (`%`) and `Shell` (`!`) are allowed.

## Approach

Replaced the `unreachable!()` panic with error handling that adds a
`ParseErrorType::OtherError` and continues parsing, returning a valid
AST node with the error attached.

## Test Plan

Added `test_ipython_escape_command_in_with_statement` and
`test_ipython_help_escape_command_as_expression` to verify the fix.

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2025-11-24 05:40:27 +00:00
Dhruv Manilawala 3b23d3c041
Fix cargo shear in CI (#21609)
Tested using `cargo build` and `cargo build --release`.
2025-11-24 05:35:34 +00:00
Alex Waygood e642874cf1
[ty] Check method definitions on subclasses for Liskov violations (#21436) 2025-11-23 18:08:15 +00:00
Micha Reiser aec225d825
[ty] Fix panic for unclosed string literal in type annotation position (#21592) 2025-11-23 16:51:58 +01:00
Micha Reiser d24c891a4b
[ty] Fix rendering of unused suppression diagnostic (#21580) 2025-11-22 18:42:56 +01:00
Aria Desires 859f9ec21a
[ty] Improve lsp handling of hover/goto on imports (#21572)
* Fixes https://github.com/astral-sh/ty/issues/1011
* Also fixes the fact that we didn't handle `.x` properly *at all* in
hover/goto

It turns out all of our import handling completely ignored the `level`
(number of relative `.`'s) in a `from ..x.y import z` statement. It was
nice seeing how much my understanding of `ty` has improved -- previously
this would have all been opaque to me but now it was just, completely
glaring and blatant.

Fixing this required refactoring all the import code to take the
importing file into consideration. I ended up refactoring a bunch of
code to pass around/require `SemanticModel` more, as it's the natural
API for resolving this kind of import (it actually had an API for this
that was just... dead code, whoops!).
2025-11-22 11:06:16 -05:00
Alex Waygood 3410041b4c
[ty] Improve diagnostics when a submodule is not available as an attribute on a module-literal type (#21561) 2025-11-22 14:07:48 +00:00
Alex Waygood f2ce5e561a
[ty] Improve concise diagnostics for invalid exceptions when a user catches a tuple of objects (#21578) 2025-11-22 13:46:46 +00:00
Carl Meyer f495c6d4ae
[ty] upgrade salsa (#21575) 2025-11-22 11:46:57 +01:00
Aria Desires 768bb24cdf
[ty] make implicit submodule imports re-exported (#21573)
Thus they work in `.pyi` files

Closes https://github.com/astral-sh/ty/issues/1609

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-11-21 17:42:11 -08:00
Prakhar Pratyush 492d676736
[`flake8-implicit-str-concat`] Avoid invalid fix generated by autofix (`ISC003`) (#21517)
## Summary

As reported in #19757:
While attempting ISC003 autofix for an expression with explicit string
concatenation, with either operand being a string literal that wraps
across multiple lines (in parentheses) - it resulted in generating a fix
which caused runtime error.

Example:
```
_ = "abc" + (
    "def"
    "ghi"
)
```
was being auto-fixed to:
```
_ = "abc" (
    "def"
    "ghi"
)
```
which raised `TypeError: 'str' object is not callable`

This commit makes changes to just report diagnostic - no autofix in such
cases.

Fixes #19757.

## Test Plan
Added example scenarios in
`crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py`.

Signed-off-by: Prakhar Pratyush <prakhar1144@gmail.com>
2025-11-21 17:22:35 -08:00
Dan Parizher ddc1417f22
[`pylint`] Fix suppression for empty dict without tuple key annotation (`PLE1141`) (#21290)
## Summary

Fixes the PLE1141 (`dict-iter-missing-items`) rule to allow fixes for
empty dictionaries unless they have type annotations indicating 2-tuple
keys. Previously, the fix was incorrectly suppressed for all empty dicts
due to vacuous truth in the `all()` function.

Fixes #21289

## Problem Analysis

The `is_dict_key_tuple_with_two_elements` function was designed to
suppress the fix when a dictionary's keys are all 2-tuples, as unpacking
tuple keys directly would change runtime behavior.

However, for empty dictionaries, `iter_keys()` returns an empty
iterator, and `all()` on an empty iterator returns `true` (vacuous
truth). This caused the function to incorrectly suppress fixes for empty
dicts, even when there was no indication that future keys would be
2-tuples.

## Approach

1. **Detect empty dictionaries**: Added a check to identify when a dict
literal has no keys.

2. **Handle annotated empty dicts**: For empty dicts with type
annotations:
- Parse the annotation to check if it's `dict[tuple[T1, T2], ...]` where
the tuple has exactly 2 elements
- Support both PEP 484 (`typing.Dict`, `typing.Tuple`) and PEP 585
(`dict`, `tuple`) syntax
   - If tuple keys are detected, suppress the fix (correct behavior)
   - Otherwise, allow the fix

3. **Handle unannotated empty dicts**: For empty dicts without
annotations, allow the fix since there's no indication that keys will be
2-tuples.

4. **Preserve existing behavior**: For non-empty dicts, the original
logic is unchanged - check if all existing keys are 2-tuples.

The implementation includes helper functions:
- `is_annotation_dict_with_tuple_keys()`: Checks if a type annotation
specifies dict with tuple keys
- `is_tuple_type_with_two_elements()`: Checks if a type expression
represents a 2-tuple

Test cases were added to verify:
- Empty dict without annotation triggers the error
- Empty dict with `dict[tuple[int, str], bool]` suppresses the error
- Empty dict with `dict[str, int]` triggers the error
- Existing tests remain unchanged

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-11-21 22:07:18 +00:00
Ibraheem Ahmed 040aa7463b
[ty] Narrow type context during literal promotion in generic class constructors (#21574)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1603.
2025-11-21 17:05:32 -05:00
chiri 09d457aa52
[`pylint`] Fix `PLR1708` false positives on nested functions (#21177)
## Summary

Fixes https://github.com/astral-sh/ruff/issues/21162

## Test Plan

`cargo nextest run pylint`
2025-11-21 15:41:22 -05:00
Andrew Gallant 438ef334d3 [ty] Fix subtraction overflow bug
PR #21549 introduced a subtle overflow bug that seemed impossible, but
can empirically happen. This PR fixes it by saturating to zero.

I did try to write a regression test for this, but couldn't manage it.
Instead, I'll attach before-and-after screen recordings.
2025-11-21 15:07:37 -05:00
Douglas Creager 6cc502781f
[ty] Remove brittle constraint set reveal tests (#21568)
These were added to try to make it clearer that assignability checks
will eventually return more detailed answers than true or false.
However, the constraint set display rendering is still more brittle than
I'd like it to be, and it's more trouble than it's worth to keep them
updated with semantically identically but textually different edits. The
`static_assert`s are sufficient to check correctness, and we can always
add `reveal_type` when needed for further debugging.
2025-11-21 13:57:55 -05:00