Commit Graph

722 Commits

Author SHA1 Message Date
Alex Waygood 3e7e91724c
[ty] Further improve details around which expressions should be deferred in stub files (#21456)
## Summary

- Always restore the previous `deferred_state` after parsing a type
expression: we don't want that state leaking out into other contexts
where we shouldn't be deferring expression inference
- Always defer the right-hand-side of a PEP-613 type alias in a stub
file, allowing for forward references on the right-hand side of `T:
TypeAlias = X | Y` in a stub file

Addresses @carljm's review in
https://github.com/astral-sh/ruff/pull/21401#discussion_r2524260153

## Test Plan

I added a regression test for a regression that the first version of
this PR introduced (we need to make sure the r.h.s. of a PEP-613
`TypeAlias`es is always deferred in a stub file)
2025-11-14 21:07:02 +00:00
Ibraheem Ahmed 2a2b719f00
[ty] Improve generic class constructor inference (#21442)
## Summary

We currently fail to account for the type context when inferring generic
classes constructed with `__new__`, or synthesized `__init__` for
dataclasses.
2025-11-14 20:25:47 +00:00
Ibraheem Ahmed ffb7bdd595
[ty] Propagate type context through conditional expressions (#21443)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1543.
2025-11-14 15:19:08 -05:00
Bhuminjay Soni 8529d79a70
[ty] name is parameter and global is a syntax error (#21312)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-11-14 18:15:34 +00:00
Alex Waygood 8599c7e5b3
[ty] Fixup a few details around version-specific dataclass features (#21453) 2025-11-14 15:04:55 +00:00
Alex Waygood 5f501374c4
[ty] Support attribute-expression `TYPE_CHECKING` conditionals (#21449) 2025-11-14 13:11:49 +00:00
David Peter e9a5337136
[ty] Support stringified annotations in value-position `Annotated` instances (#21447)
## Summary

Infer the first argument `type` inside `Annotated[type, …]` as a type
expression. This allows us to support stringified annotations inside
`Annotated`.

## Ecosystem

* The removed diagnostic on `prefect` shows that we now understand the
`State.data` type annotation in
`src/prefect/client/schemas/objects.py:230`, which uses a stringified
annotation in `Annoated`. The other diagnostics are downstream changes
that result from this, it seems to be a commonly used data type.
* `artigraph` does something like `Annotated[cast(Any,
field_info.annotation), *field_info.metadata]` which I'm not sure we
need to allow? It's unfortunate since this is probably supported at
runtime, but it seems reasonable that they need to add a `# type:
ignore` for that.
* `pydantic` uses something like `Annotated[(self.annotation,
*self.metadata)]` but adds a `# type: ignore`

## Test Plan

New Markdown test
2025-11-14 13:09:09 +00:00
David Peter 05cf53aae8
[ty] Type inference for genererator expressions (#21437)
## Summary

Add type inference for (async) generator expressions.

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

## Test Plan

New Markdown tests.
2025-11-14 13:04:11 +00:00
David Peter 6a26f86778
[ty] Make `__getattr__` available for `ModuleType` instances (#21450)
## Summary

Typeshed has a (fake) `__getattr__` method on `types.ModuleType` with a
return type of `Any`. We ignore this method when accessing attributes on
module *literals*, but with this PR, we respect this method when dealing
with `ModuleType` itself. That is, we allow arbitrary attribute accesses
on instances of `types.ModuleType`. This is useful because dynamic
import mechanisms such as `importlib.import_module` use `ModuleType` as
a return type.

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

## Ecosystem

Massive reduction in diagnostics. The few new diagnostics are true
positives.

## Test Plan

Added regression test.
2025-11-14 13:59:14 +01:00
David Peter 696d7a5d68
[ty] Add synthetic members to completions on dataclasses (#21446)
## Summary

Add synthetic members to completions on dataclasses and dataclass
instances.

Also, while we're at it, add support for `__weakref__` and
`__match_args__`.

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

## Test Plan

New Markdown tests
2025-11-14 11:31:20 +01:00
David Peter 66e9d57797
[ty] Support legacy `typing` special forms in implicit type aliases (#21433)
## Summary

Support various legacy `typing` special forms (`List`, `Dict`, …) in
implicit type aliases.

## Ecosystem impact

A lot of true positives (e.g. on `alerta`)!

## Test Plan

New Markdown tests
2025-11-14 09:08:58 +01:00
David Peter 9e80e5a3a6
[ty] Support `type[…]` and `Type[…]` in implicit type aliases (#21421)
## Summary

Support `type[…]` in implicit type aliases, for example:
```py
SubclassOfInt = type[int]

reveal_type(SubclassOfInt)  # GenericAlias

def _(subclass_of_int: SubclassOfInt):
    reveal_type(subclass_of_int)  # type[int]
```

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

## Typing conformance

```diff
-specialtypes_type.py:138:5: error[type-assertion-failure] Argument does not have asserted type `type[Any]`
-specialtypes_type.py:140:5: error[type-assertion-failure] Argument does not have asserted type `type[Any]`
```

Two new tests passing ✔️ 

```diff
-specialtypes_type.py:146:1: error[unresolved-attribute] Object of type `GenericAlias` has no attribute `unknown`
```

An `TA4.unknown` attribute on a PEP 613 alias (`TA4: TypeAlias =
type[Any]`) is being accessed, and the conformance suite expects this to
be an error. Since we currently use the inferred type for these type
aliases (and possibly in the future as well), we treat this as a direct
access of the attribute on `type[Any]`, which falls back to an access on
`Any` itself, which succeeds. 🔴

```
+specialtypes_type.py:152:16: error[invalid-type-form] `typing.TypeVar` is not a generic class
+specialtypes_type.py:156:16: error[invalid-type-form] `typing.TypeVar` is not a generic class
```

New errors because we don't handle `T = TypeVar("T"); MyType = type[T];
MyType[T]` yet. Support for this is being tracked in
https://github.com/astral-sh/ty/issues/221 🔴

## Ecosystem impact

Looks mostly good, a few known problems. 

## Test Plan

New Markdown tests
2025-11-13 19:02:24 +01:00
Alex Waygood 90b32f3b3b
[ty] Ensure annotation/type expressions in stub files are always deferred (#21401) 2025-11-13 17:14:54 +00:00
David Peter 04ab9170d6
[ty] Further improve subscript assignment diagnostics (#21411)
## Summary

Further improve subscript assignment diagnostics, especially for
`dict`s:

```py
config: dict[str, int] = {}

config["retries"] = "three"
```

<img width="1276" height="274" alt="image"
src="https://github.com/user-attachments/assets/9762c733-8d1c-4a57-8c8a-99825071dc7d"
/>

I have many more ideas, but this looks like a reasonable first step.
Thank you @AlexWaygood for some of the suggestions here.

## Test Plan

Update tests
2025-11-13 13:31:14 +01:00
Alex Waygood cd183c5e1f
[ty] Use the return type of `__get__` for descriptor lookups even when `__get__` is called with incorrect arguments (#21424) 2025-11-13 12:05:10 +00: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
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
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
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
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
Jack O'Connor 5f3e086ee4 [ty] implement `typing.NewType` by adding `Type::NewTypeInstance` 2025-11-10 14:55:47 -08: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
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
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
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
Alex Waygood 73b1fce74a
[ty] Add diagnostics for `isinstance()` and `issubclass()` calls that use invalid PEP-604 unions for their second argument (#21343)
## Summary

This PR adds extra validation for `isinstance()` and `issubclass()`
calls that use `UnionType` instances for their second argument.
According to typeshed's annotations, any `UnionType` is accepted for the
second argument, but this isn't true at runtime: at runtime, all
elements in the `UnionType` must either be class objects or be `None` in
order for the `isinstance()` or `issubclass()` call to reliably succeed:

```pycon
% uvx python3.14                            
Python 3.14.0 (main, Oct 10 2025, 12:54:13) [Clang 20.1.4 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from typing import LiteralString
>>> import types
>>> type(LiteralString | int) is types.UnionType
True
>>> isinstance(42, LiteralString | int)
Traceback (most recent call last):
  File "<python-input-5>", line 1, in <module>
    isinstance(42, LiteralString | int)
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/alexw/Library/Application Support/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/typing.py", line 559, in __instancecheck__
    raise TypeError(f"{self} cannot be used with isinstance()")
TypeError: typing.LiteralString cannot be used with isinstance()
```

## Test Plan

Added mdtests/snapshots
2025-11-10 08:46:31 +00:00
Alex Waygood 020ff1723b
[ty] Add narrowing for `isinstance()` and `issubclass()` checks that use PEP-604 unions (#21334) 2025-11-08 18:20:46 +00:00
Douglas Creager faae72b836
[ty] Clarify behavior of constraint sets for gradual upper bounds and constraints (#21287)
When checking whether a constraint set is satisfied, if a typevar has a
non-fully-static upper bound or constraint, we are free to choose any
materialization that makes the check succeed.

In non-inferable positions, we have to show that the constraint set is
satisfied for all valid specializations, so it's best to choose the most
restrictive materialization, since that minimizes the set of valid
specializations that have to pass.

In inferable positions, we only have to show that the constraint set is
satisfied for _some_ valid specializations, so it's best to choose the
most permissive materialization, since that maximizes our chances of
finding a specialization that passes.
2025-11-07 14:01:39 -05:00
David Peter ed18112cfa
[ty] Add support for `Literal`s in implicit type aliases (#21296)
## Summary

Add support for `Literal` types in implicit type aliases.

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

## Ecosystem analysis

This looks good to me, true positives and known problems.

## Test Plan

New Markdown tests.
2025-11-07 17:46:55 +01:00
Dhruv Manilawala cb2e277482
[ty] Understand legacy and PEP 695 `ParamSpec` (#21139)
## Summary

This PR adds support for understanding the legacy definition and PEP 695
definition for `ParamSpec`.

This is still very initial and doesn't really implement any of the
semantics.

Part of https://github.com/astral-sh/ty/issues/157

## Test Plan

Add mdtest cases.

## Ecosystem analysis

Most of the diagnostics in `starlette` are due to the fact that ty now
understands `ParamSpec` is not a `Todo` type, so the assignability check
fails. The code looks something like:

```py
class _MiddlewareFactory(Protocol[P]):
    def __call__(self, app: ASGIApp, /, *args: P.args, **kwargs: P.kwargs) -> ASGIApp: ...  # pragma: no cover

class Middleware:
    def __init__(
        self,
        cls: _MiddlewareFactory[P],
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> None:
        self.cls = cls
        self.args = args
        self.kwargs = kwargs

# ty complains that `ServerErrorMiddleware` is not assignable to `_MiddlewareFactory[P]`
Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)
```

There are multiple diagnostics where there's an attribute access on the
`Wrapped` object of `functools` which Pyright also raises:
```py
from functools import wraps

def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        return f(*args, **kwds)

	# Pyright: Cannot access attribute "__signature__" for class "_Wrapped[..., Unknown, ..., Unknown]"
      Attribute "__signature__" is unknown [reportAttributeAccessIssue]
	# ty: Object of type `_Wrapped[Unknown, Unknown, Unknown, Unknown]` has no attribute `__signature__` [unresolved-attribute]
    wrapper.__signature__
    return wrapper
```

There are additional diagnostics that is due to the assignability checks
failing because ty now infers the `ParamSpec` instead of using the
`Todo` type which would always succeed. This results in a few
`no-matching-overload` diagnostics because the assignability checks
fail.

There are a few diagnostics related to
https://github.com/astral-sh/ty/issues/491 where there's a variable
which is either a bound method or a variable that's annotated with
`Callable` that doesn't contain the instance as the first parameter.

Another set of (valid) diagnostics are where the code hasn't provided
all the type variables. ty is now raising diagnostics for these because
we include `ParamSpec` type variable in the signature. For example,
`staticmethod[Any]` which contains two type variables.
2025-11-06 11:14:40 -05:00
Alex Waygood f189aad6d2
[ty] Make special cases for `UnionType` slightly narrower (#21276)
Fixes https://github.com/astral-sh/ty/issues/1478
2025-11-06 09:00:43 -05:00
Douglas Creager eda85f3c64
[ty] Constraining a typevar with itself (possibly via union or intersection) (#21273)
This PR carries over some of the `has_relation_to` logic for comparing a
typevar with itself. A typevar will specialize to the same type if it's
mentioned multiple times, so it is always assignable to and a subtype of
itself. (Note that typevars can only specialize to fully static types.)
This is also true when the typevar appears in a union on the right-hand
side, or in an intersection on the left-hand side. Similarly, a typevar
is always disjoint from its negation, so when a negated typevar appears
on the left-hand side, the constraint set is never satisfiable.

(Eventually this will allow us to remove the corresponding clauses from
`has_relation_to`, but that can't happen until more of #20093 lands.)
2025-11-05 12:31:53 -05:00
Ibraheem Ahmed 5c69e00d1c
[ty] Simplify unions containing multiple type variables during inference (#21275)
## Summary

Splitting this one out from https://github.com/astral-sh/ruff/pull/21210. This is also something that should be made obselete by the new constraint solver, but is easy enough to fix now.
2025-11-05 15:03:19 +00:00
David Peter 2e7ab00d51
[ty] Allow values of type `None` in type expressions (#21263)
## Summary

Allow values of type `None` in type expressions. The [typing
spec](https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions)
could be more explicit on whether this is actually allowed or not, but
it seems relatively harmless and does help in some use cases like:

```py
try:
    from module import MyClass
except ImportError:
    MyClass = None  # ty: ignore


def f(m: MyClass):
    pass
``` 

## Test Plan

Updated tests, ecosystem check.
2025-11-04 16:29:55 +01:00
Ibraheem Ahmed 3c8fb68765
[ty] `dict` is not assignable to `TypedDict` (#21238)
## Summary

A lot of the bidirectional inference work relies on `dict` not being
assignable to `TypedDict`, so I think it makes sense to add this before
fully implementing https://github.com/astral-sh/ty/issues/1387.
2025-11-03 16:57:49 -05:00
David Peter d2fe6347fb
[ty] Rename `UnionType` to `types.UnionType` (#21262) 2025-11-03 22:06:56 +01:00
David Peter 1fe958c694
[ty] Implicit type aliases: Support for PEP 604 unions (#21195)
## Summary

Add support for implicit type aliases that use PEP 604 unions:
```py
IntOrStr = int | str

reveal_type(IntOrStr)  # UnionType

def _(int_or_str: IntOrStr):
    reveal_type(int_or_str)  # int | str
```

## Typing conformance

The changes are either removed false positives, or new diagnostics due
to known limitations unrelated to this PR.

## Ecosystem impact

Spot checked, a mix of true positives and known limitations.

## Test Plan

New Markdown tests.
2025-11-03 21:50:25 +01:00
Carl Meyer fe4ee81b97
[ty] prefer submodule over module __getattr__ in from-imports (#21260)
Fixes https://github.com/astral-sh/ty/issues/1053

## Summary

Other type checkers prioritize a submodule over a package `__getattr__`
in `from mod import sub`, even though the runtime precedence is the
other direction. In effect, this is making an implicit assumption that a
module `__getattr__` will not handle (that is, will raise
`AttributeError`) for names that are also actual submodules, rather than
shadowing them. In practice this seems like a realistic assumption in
the ecosystem? Or at least the ecosystem has adapted to it, and we need
to adapt this precedence also, for ecosystem compatibility.

The implementation is a bit ugly, precisely because it departs from the
runtime semantics, and our implementation is oriented toward modeling
runtime semantics accurately. That is, `__getattr__` is modeled within
the member-lookup code, so it's hard to split "member lookup result from
module `__getattr__`" apart from other member lookup results. I did this
via a synthetic `TypeQualifier::FROM_MODULE_GETATTR` that we attach to a
type resulting from a member lookup, which isn't beautiful but it works
well and doesn't introduce inefficiency (e.g. redundant member lookups).

## Test Plan

Updated mdtests.

Also added a related mdtest formalizing our support for a module
`__getattr__` that is explicitly annotated to accept a limited set of
names. In principle this could be an alternative (more explicit) way to
handle the precedence problem without departing from runtime semantics,
if the ecosystem would adopt it.

### Ecosystem analysis

Lots of removed diagnostics which are an improvement because we now
infer the expected submodule.

Added diagnostics are mostly unrelated issues surfaced now because we
previously had an earlier attribute error resulting in `Unknown`; now we
correctly resolve the module so that earlier attribute error goes away,
we get an actual type instead of `Unknown`, and that triggers a new
error.

In scipy and sklearn, the module `__getattr__` which we were respecting
previously is un-annotated so returned a forgiving `Unknown`; now we
correctly see the actual module, which reveals some cases of
https://github.com/astral-sh/ty/issues/133 that were previously hidden
(`scipy/optimize/__init__.py` [imports `from
._tnc`](eff82ca575/scipy/optimize/__init__.py (L429)).)

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-11-03 15:24:01 -05:00
Shunsuke Shibayama b5305b5f32
[ty] Fix panic due to simplifying `Divergent` types out of intersections types (#21253) 2025-11-03 15:41:11 +00:00
Alex Waygood 39f105bc4a
[ty] Use "cannot" consistently over "can not" (#21255) 2025-11-03 10:38:20 -05:00
Carl Meyer 0454a72674
[ty] don't union in default type for annotated parameters (#21208) 2025-11-02 18:21:54 -05:00
Carl Meyer c32234cf0d
[ty] support subscripting typing.Literal with a type alias (#21207)
Fixes https://github.com/astral-sh/ty/issues/1368

## Summary

Add support for patterns like this, where a type alias to a literal type
(or union of literal types) is used to subscript `typing.Literal`:

```py
type MyAlias = Literal[1]
def _(x: Literal[MyAlias]): ...
```

This shows up in the ecosystem report for PEP 613 type alias support.

One interesting case is an alias to `bool` or an enum type. `bool` is an
equivalent type to `Literal[True, False]`, which is a union of literal
types. Similarly an enum type `E` is also equivalent to a union of its
member literal types. Since (for explicit type aliases) we infer the RHS
directly as a type expression, this makes it difficult for us to
distinguish between `bool` and `Literal[True, False]`, so we allow
either one to (or an alias to either one) to appear inside `Literal`,
where other type checkers allow only the latter.

I think for implicit type aliases it may be simpler to support only
types derived from actually subscripting `typing.Literal`, though, so I
didn't make a TODO-comment commitment here.

## Test Plan

Added mdtests, including TODO-filled tests for PEP 613 and implicit type
aliases.

### Conformance suite

All changes here are positive -- we now emit errors on lines that should
be errors. This is a side effect of the new implementation, not the
primary purpose of this PR, but it's still a positive change.

### Ecosystem

Eliminates one ecosystem false positive, where a PEP 695 type alias for
a union of literal types is used to subscript `typing.Literal`.
2025-11-02 12:39:55 -05:00
David Peter 73107a083c
[ty] Type inference for comprehensions (#20962)
## Summary

Adds type inference for list/dict/set comprehensions, including
bidirectional inference:

```py
reveal_type({k: v for k, v in [("a", 1), ("b", 2)]})  # dict[Unknown | str, Unknown | int]

squares: list[int | None] = [x for x in range(10)]
reveal_type(squares)  # list[int | None]
```

## Ecosystem impact

I did spot check the changes and most of them seem like known
limitations or true positives. Without proper bidirectional inference,
we saw a lot of false positives.

## Test Plan

New Markdown tests
2025-11-02 14:35:33 +01:00
Alex Waygood a32d5b8dc4
[ty] Improve exhaustiveness analysis for type variables with bounds or constraints (#21172) 2025-10-31 16:51:11 -04:00
David Peter 1734ddfb3e
[ty] Do not promote literals in contravariant positions of generic specializations (#21171)
## Summary

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

supersedes https://github.com/astral-sh/ruff/pull/20950 by @ibraheemdev 

## Test Plan

New regression test
2025-10-31 17:48:34 +01:00
Ibraheem Ahmed ff3a6a8fbd
[ty] Support type context of union attribute assignments (#21170)
## Summary

Turns out this is easy to implement. Resolves
https://github.com/astral-sh/ty/issues/1375.
2025-10-31 12:41:14 -04:00
Carl Meyer 9664474c51
[ty] rollback preferring declared type on invalid TypedDict creation (#21169)
## Summary

Discussion with @ibraheemdev clarified that
https://github.com/astral-sh/ruff/pull/21168 was incorrect. In a case of
failed inference of a dict literal as a `TypedDict`, we should store the
context-less inferred type of the dict literal as the type of the dict
literal expression itself; the fallback to declared type should happen
at the level of the overall assignment definition.

The reason the latter isn't working yet is because currently we
(wrongly) consider a homogeneous dict type as assignable to a
`TypedDict`, so we don't actually consider the assignment itself as
failed. So the "bug" I observed (and tried to fix) will naturally be
fixed by implementing TypedDict assignability rules.

Rollback https://github.com/astral-sh/ruff/pull/21168 except for the
tests, and modify the tests to include TODOs as needed.

## Test Plan

Updated mdtests.
2025-10-31 12:06:47 -04:00
Luca Chiodini 69b4c29924
Consistently wrap tokens in parser diagnostics in `backticks` instead of 'quotes' (#21163)
The parser currently uses single quotes to wrap tokens. This is
inconsistent with the rest of ruff/ty, which use backticks.

For example, see the inconsistent diagnostics produced in this simple
example: https://play.ty.dev/0a9d6eab-6599-4a1d-8e40-032091f7f50f

Consistently wrapping tokens in backticks produces uniform diagnostics.
Following the style decision of #723, in #2889 some quotes were already
switched into backticks.

This is also in line with Rust's guide on diagnostics
(https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-structure):

> When code or an identifier must appear in a message or label, it
should be surrounded with backticks
2025-10-31 11:59:11 -04:00
Ibraheem Ahmed bb40c34361
[ty] Use declared attribute types as type context (#21143)
## Summary

For example:
```py
class X:
    x: list[int | str]

def _(x: X):
    x.x = [1]
```

Resolves https://github.com/astral-sh/ty/issues/1375.
2025-10-31 15:48:28 +00:00
Carl Meyer 1d111c8780
[ty] prefer declared type on invalid TypedDict creation (#21168)
## Summary

In general, when we have an invalid assignment (inferred assigned type
is not assignable to declared type), we fall back to inferring the
declared type, since the declared type is a more explicit declaration of
the programmer's intent. This also maintains the invariant that our
inferred type for a name is always assignable to the declared type for
that same name. For example:

```py
x: str = 1
reveal_type(x)  # revealed: str
```

We weren't following this pattern for dictionary literals inferred (via
type context) as a typed dictionary; if the literal was not valid for
the annotated TypedDict type, we would just fall back to the normal
inferred type of the dict literal, effectively ignoring the annotation,
and resulting in inferred type not assignable to declared type.

## Test Plan

Added mdtest assertions.
2025-10-31 11:12:06 -04:00
David Peter 0c2cf75869
[ty] Do not promote literals in contravariant position (#21164)
## Summary

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

## Test Plan

Regression tests
2025-10-31 16:00:30 +01:00
Ibraheem Ahmed 1d6ae8596a
[ty] Prefer exact matches when solving constrained type variables (#21165)
## Summary

The solver is currently order-dependent, and will choose a supertype
over the exact type if it appears earlier in the list of constraints. We
could be smarter and try to choose the most precise subtype, but I
imagine this is something the new constraint solver will fix anyways,
and this fixes the issue showing up on
https://github.com/astral-sh/ruff/pull/21070.
2025-10-31 10:58:09 -04:00
Douglas Creager cf4e82d4b0
[ty] Add and test when constraint sets are satisfied by their typevars (#21129)
This PR adds a new `satisfied_by_all_typevar` method, which implements
one of the final steps of actually using these dang constraint sets.
Constraint sets exist to help us check assignability and subtyping of
types in the presence of typevars. We construct a constraint set
describing the conditions under which assignability holds between the
two types. Then we check whether that constraint set is satisfied for
the valid specializations of the relevant typevars (which is this new
method).

We also add a new `ty_extensions.ConstraintSet` method so that we can
test this method's behavior in mdtests, before hooking it up to the rest
of the specialization inference machinery.
2025-10-31 10:53:37 -04:00
Ibraheem Ahmed 1baf98aab3
[ty] Fix `is_disjoint_from` with `@final` classes (#21167)
## Summary

We currently perform a subtyping check instead of the intended subclass
check (and the subtyping check is confusingly named `is_subclass_of`).
This showed up in https://github.com/astral-sh/ruff/pull/21070.
2025-10-31 14:50:54 +00:00
Carl Meyer 3179b05221
[ty] don't assume in diagnostic messages that a TypedDict key error is about subscript access (#21166)
## Summary

Before this PR, we would emit diagnostics like "Invalid key access" for
a TypedDict literal with invalid key, which doesn't make sense since
there's no "access" in that case. This PR just adjusts the wording to be
more general, and adjusts the documentation of the lint rule too.

I noticed this in the playground and thought it would be a quick fix. As
usual, it turned out to be a bit more subtle than I expected, but for
now I chose to punt on the complexity. We may ultimately want to have
different rules for invalid subscript vs invalid TypedDict literal,
because an invalid key in a TypedDict literal is low severity: it's a
typo detector, but not actually a type error. But then there's another
wrinkle there: if the TypedDict is `closed=True`, then it _is_ a type
error. So would we want to separate the open and closed cases into
separate rules, too? I decided to leave this as a question for future.

If we wanted to use separate rules, or use specific wording for each
case instead of the generalized wording I chose here, that would also
involve a bit of extra work to distinguish the cases, since we use a
generic set of functions for reporting these errors.

## Test Plan

Added and updated mdtests.
2025-10-31 10:49:59 -04:00
Aria Desires 172e8d4ae0
[ty] Support implicit imports of submodules in `__init__.pyi` (#20855)
This is a second take at the implicit imports approach, allowing `from .
import submodule` in an `__init__.pyi` to create the
`mypackage.submodule` attribute everyhere.

This implementation operates inside of the
available_submodule_attributes subsystem instead of as a re-export rule.

The upside of this is we are no longer purely syntactic, and absolute
from imports that happen to target submodules work (an intentional
discussed deviation from pyright which demands a relative from import).
Also we don't re-export functions or classes.

The downside(?) of this is star imports no longer see these attributes
(this may be either good or bad. I believe it's not a huge lift to make
it work with star imports but it's some non-trivial reworking).

I've also intentionally made `import mypackage.submodule` not trigger
this rule although it's trivial to change that.

I've tried to cover as many relevant cases as possible for discussion in
the new test file I've added (there are some random overlaps with
existing tests but trying to add them piecemeal felt confusing and
weird, so I just made a dedicated file for this extension to the rules).

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

<!--
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

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
2025-10-31 14:29:24 +00:00
Mahmoud Saada 735ec0c1f9
[ty] Fix generic inference for non-dataclass inheriting from generic dataclass (#21159)
## Summary

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

This PR fixes a regression introduced in alpha.24 where non-dataclass
children of generic dataclasses lost generic type parameter information
during `__init__` synthesis.

The issue occurred because when looking up inherited members in the MRO,
the child class's `inherited_generic_context` was correctly passed down,
but `own_synthesized_member()` (which synthesizes dataclass `__init__`
methods) didn't accept this parameter. It only used
`self.inherited_generic_context(db)`, which returned the parent's
context instead of the child's.

The fix threads the child's generic context through to the synthesis
logic, allowing proper generic type inference for inherited dataclass
constructors.

## Test Plan

- Added regression test for non-dataclass inheriting from generic
dataclass
- Verified the exact repro case from the issue now works
- All 277 mdtest tests passing
- Clippy clean
- Manually verified with Python runtime, mypy, and pyright - all accept
this code pattern

## Verification

Tested against multiple type checkers:
-  Python runtime: Code works correctly
-  mypy: No issues found
-  pyright: 0 errors, 0 warnings
-  ty alpha.23: Worked (before regression)
-  ty alpha.24: Regression
-  ty with this fix: Works correctly

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: David Peter <mail@david-peter.de>
2025-10-31 13:55:17 +01:00
Alex Waygood 13375d0e42
[ty] Use the top materialization of classes for narrowing in class-patterns for `match` statements (#21150) 2025-10-30 20:44:51 +00:00
Douglas Creager c0b04d4b7c
[ty] Update "constraint implication" relation to work on constraints between two typevars (#21068)
It's possible for a constraint to mention two typevars. For instance, in
the body of

```py
def f[S: int, T: S](): ...
```

the baseline constraint set would be `(T ≤ S) ∧ (S ≤ int)`. That is, `S`
must specialize to some subtype of `int`, and `T` must specialize to a
subtype of the type that `S` specializes to.

This PR updates the new "constraint implication" relationship from
#21010 to work on these kinds of constraint sets. For instance, in the
example above, we should be able to see that `T ≤ int` must always hold:

```py
def f[S, T]():
    constraints = ConstraintSet.range(Never, S, int) & ConstraintSet.range(Never, T, S)
    static_assert(constraints.implies_subtype_of(T, int))  # now succeeds!
```

This did not require major changes to the implementation of
`implies_subtype_of`. That method already relies on how our `simplify`
and `domain` methods expand a constraint set to include the transitive
closure of the constraints that it mentions, and to mark certain
combinations of constraints as impossible. Previously, that transitive
closure logic only looked at pairs of constraints that constrain the
same typevar. (For instance, to notice that `(T ≤ bool) ∧ ¬(T ≤ int)` is
impossible.)

Now we also look at pairs of constraints that constraint different
typevars, if one of the constraints is bound by the other — that is,
pairs of the form `T ≤ S` and `S ≤ something`, or `S ≤ T` and `something
≤ S`. In those cases, transitivity lets us add a new derived constraint
that `T ≤ something` or `something ≤ T`, respectively. Having done that,
our existing `implies_subtype_of` logic finds and takes into account
that derived constraint.
2025-10-30 16:11:04 -04:00
Alex Waygood 9bacd19c5a
[ty] Fix lookup of `__new__` on instances (#21147)
## Summary

We weren't correctly modeling it as a `staticmethod` in all cases,
leading us to incorrectly infer that the `cls` argument would be bound
if it was accessed on an instance (rather than the class object).

## Test Plan

Added mdtests that fail on `main`. The primer output also looks good!
2025-10-30 13:42:46 -04:00
David Peter e55bc943e5
[ty] Reachability and narrowing for enum methods (#21130)
## Summary

Adds proper type narrowing and reachability analysis for matching on
non-inferable type variables bound to enums. For example:

```py
from enum import Enum

class Answer(Enum):
    NO = 0
    YES = 1

    def is_yes(self) -> bool:  # no error here!
        match self:
            case Answer.YES:
                return True
            case Answer.NO:
                return False
```

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

## Test Plan

Added regression tests
2025-10-30 15:38:57 +01:00
David Peter 1b0ee4677e
[ty] Use `range` instead of custom `IntIterable` (#21138)
## Summary

We previously didn't understand `range` and wrote these custom
`IntIterable`/`IntIterator` classes for tests. We can now remove them
and make the tests shorter in some places.
2025-10-30 15:21:55 +01:00
David Peter 5139f76d1f
[ty] Infer type of `self` for decorated methods and properties (#21123)
## Summary

Infer a type of unannotated `self` parameters in decorated methods /
properties.

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

## Test Plan

Existing tests, some new tests.
2025-10-29 21:22:38 +00:00
Douglas Creager 17850eee4b
[ty] Reformat constraint set mdtests (#21111)
This PR updates the mdtests that test how our generics solver interacts
with our new constraint set implementation. Because the rendering of a
constraint set can get long, this standardizes on putting the `revealed`
assertion on a separate line. We also add a `static_assert` test for
each constraint set to verify that they are all coerced into simple
`bool`s correctly.

This is a pure reformatting (not even a refactoring!) that changes no
behavior. I've pulled it out of #20093 to reduce the amount of effort
that will be required to review that PR.
2025-10-28 14:59:49 -04:00
Douglas Creager 4d2ee41e24
[ty] Move constraint set mdtest functions into `ConstraintSet` class (#21108)
We have several functions in `ty_extensions` for testing our constraint
set implementation. This PR refactors those functions so that they are
all methods of the `ConstraintSet` class, rather than being standalone
top-level functions. 🎩 to @sharkdp for pointing out that
`KnownBoundMethod` gives us what we need to implement that!
2025-10-28 14:32:41 -04:00
Douglas Creager 29462ea1d4
[ty] Add new "constraint implication" typing relation (#21010)
This PR adds the new **_constraint implication_** relationship between
types, aka `is_subtype_of_given`, which tests whether one type is a
subtype of another _assuming that the constraints in a particular
constraint set hold_.

For concrete types, constraint implication is exactly the same as
subtyping. (A concrete type is any fully static type that is not a
typevar. It can _contain_ a typevar, though — `list[T]` is considered
concrete.)

The interesting case is typevars. The other typing relationships (TODO:
will) all "punt" on the question when considering a typevar, by
translating the desired relationship into a constraint set. At some
point, though, we need to resolve a constraint set; at that point, we
can no longer punt on the question. Unlike with concrete types, the
answer will depend on the constraint set that we are considering.
2025-10-27 22:01:08 -04:00
Alex Waygood db0e921db1
[ty] Fix bug where ty would think all types had an `__mro__` attribute (#20995) 2025-10-27 11:19:12 +00:00
Ibraheem Ahmed 304ac22e74
[ty] Use constructor parameter types as type context (#21054)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1408.
2025-10-24 20:14:18 +00:00
Douglas Creager c3de8847d5
[ty] Consider domain of BDD when checking whether always satisfiable (#21050)
That PR title might be a bit inscrutable.

Consider the two constraints `T ≤ bool` and `T ≤ int`. Since `bool ≤
int`, by transitivity `T ≤ bool` implies `T ≤ int`. (Every type that is
a subtype of `bool` is necessarily also a subtype of `int`.) That means
that `T ≤ bool ∧ T ≰ int` is an impossible combination of constraints,
and is therefore not a valid input to any BDD. We say that that
assignment is not in the _domain_ of the BDD.

The implication `T ≤ bool → T ≤ int` can be rewritten as `T ≰ bool ∨ T ≤
int`. (That's the definition of implication.) If we construct that
constraint set in an mdtest, we should get a constraint set that is
always satisfiable. Previously, that constraint set would correctly
_display_ as `always`, but a `static_assert` on it would fail.

The underlying cause is that our `is_always_satisfied` method would only
test if the BDD was the `AlwaysTrue` terminal node. `T ≰ bool ∨ T ≤ int`
does not simplify that far, because we purposefully keep around those
constraints in the BDD structure so that it's easier to compare against
other BDDs that reference those constraints.

To fix this, we need a more nuanced definition of "always satisfied".
Instead of evaluating to `true` for _every_ input, we only need it to
evaluate to `true` for every _valid_ input — that is, every input in its
domain.
2025-10-24 13:37:56 -04:00
Ibraheem Ahmed f17ddd62ad
[ty] Avoid duplicate diagnostics during multi-inference of standalone expressions (#21056)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1428.
2025-10-24 13:21:39 -04:00
Micha Reiser adbf05802a
[ty] Fix rare panic with highly cyclic `TypeVar` definitions (#21059) 2025-10-24 18:30:54 +02:00
Alex Waygood bf74c824eb
[ty] Delegate truthiness inference of an enum `Literal` type to its enum-instance supertype (#21060) 2025-10-24 14:34:16 +01:00
Alex Waygood e196c2ab37
[ty] Consider `__len__` when determining the truthiness of an instance of a tuple class or a `@final` class (#21049) 2025-10-24 09:29:55 +00:00
Shunsuke Shibayama 48f1771877
[ty] fix infinite recursion with generic type aliases (#20969)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-23 14:14:30 +00:00
Alex Waygood dab3d4e917
[ty] Improve `invalid-argument-type` diagnostics where a union type was provided (#21044) 2025-10-23 13:16:21 +00:00
Micha Reiser e92fd51a2c
[ty] Add cycle handling to `lazy_default` (#20967) 2025-10-23 10:05:08 +02:00
David Peter 589e8ac0d9
[ty] Infer type for implicit `self` parameters in method bodies (#20922)
## Summary

Infer a type of `Self` for unannotated `self` parameters in methods of
classes.

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

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

## Conformance tests changes

```diff
+enums_member_values.py:85:9: error[invalid-assignment] Object of type `int` is not assignable to attribute `_value_` of type `str`
```
A true positive ✔️ 

```diff
-generics_self_advanced.py:35:9: error[type-assertion-failure] Argument does not have asserted type `Self@method2`
-generics_self_basic.py:14:9: error[type-assertion-failure] Argument does not have asserted type `Self@set_scale
```

Two false positives going away ✔️ 

```diff
+generics_syntax_infer_variance.py:82:9: error[invalid-assignment] Cannot assign to final attribute `x` on type `Self@__init__`
```

This looks like a true positive to me, even if it's not marked with `#
E` ✔️

```diff
+protocols_explicit.py:56:9: error[invalid-assignment] Object of type `tuple[int, int, str]` is not assignable to attribute `rgb` of type `tuple[int, int, int]`
```

True positive ✔️ 

```
+protocols_explicit.py:85:9: error[invalid-attribute-access] Cannot assign to ClassVar `cm1` from an instance of type `Self@__init__`
```

This looks like a true positive to me, even if it's not marked with `#
E`. But this is consistent with our understanding of `ClassVar`, I
think. ✔️

```py
+qualifiers_final_annotation.py:52:9: error[invalid-assignment] Cannot assign to final attribute `ID4` on type `Self@__init__`
+qualifiers_final_annotation.py:65:9: error[invalid-assignment] Cannot assign to final attribute `ID7` on type `Self@method1`
```

New true positives ✔️ 

```py
+qualifiers_final_annotation.py:52:9: error[invalid-assignment] Cannot assign to final attribute `ID4` on type `Self@__init__`
+qualifiers_final_annotation.py:57:13: error[invalid-assignment] Cannot assign to final attribute `ID6` on type `Self@__init__`
+qualifiers_final_annotation.py:59:13: error[invalid-assignment] Cannot assign to final attribute `ID6` on type `Self@__init__`
```

This is a new false positive, but that's a pre-existing issue on main
(if you annotate with `Self`):
https://play.ty.dev/3ee1c56d-7e13-43bb-811a-7a81e236e6ab  => reported
as https://github.com/astral-sh/ty/issues/1409

## Ecosystem

* There are 5931 new `unresolved-attribute` and 3292 new
`possibly-missing-attribute` attribute errors, way too many to look at
all of them. I randomly sampled 15 of these errors and found:
* 13 instances where there was simply no such attribute that we could
plausibly see. Sometimes [I didn't find it
anywhere](8644d886c6/openlibrary/plugins/openlibrary/tests/test_listapi.py (L33)).
Sometimes it was set externally on the object. Sometimes there was some
[`setattr` dynamicness going
on](a49f6b927d/setuptools/wheel.py (L88-L94)).
I would consider all of them to be true positives.
* 1 instance where [attribute was set on `obj` in
`__new__`](9e87b44fd4/sympy/tensor/array/array_comprehension.py (L45C1-L45C36)),
which we don't support yet
  * 1 instance [where the attribute was defined via `__slots__`

](e250ec0fc8/lib/spack/spack/vendor/pyrsistent/_pdeque.py (L48C5-L48C14))
* I see 44 instances [of the false positive
above](https://github.com/astral-sh/ty/issues/1409) with `Final`
instance attributes being set in `__init__`. I don't think this should
block this PR.

## Test Plan

New Markdown tests.

---------

Co-authored-by: Shaygan Hooshyari <sh.hooshyari@gmail.com>
2025-10-23 09:34:39 +02:00
Douglas Creager 766ed5b5f3
[ty] Some more simplifications when rendering constraint sets (#21009)
This PR adds another useful simplification when rendering constraint
sets: `T = int` instead of `T = int ∧ T ≠ str`. (The "smaller"
constraint `T = int` implies the "larger" constraint `T ≠ str`.
Constraint set clauses are intersections, and if one constraint in a
clause implies another, we can throw away the "larger" constraint.)

While we're here, we also normalize the bounds of a constraint, so that
we equate e.g. `T ≤ int | str` with `T ≤ str | int`, and change the
ordering of BDD variables so that all constraints with the same typevar
are ordered adjacent to each other.

Lastly, we also add a new `display_graph` helper method that prints out
the full graph structure of a BDD.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-22 13:38:44 -04:00
David Peter 81c1d36088
[ty] Make `attributes.md` mdtests faster (#21030)
## Summary

That example was too extreme for debug mode.
2025-10-22 16:40:58 +02:00
David Peter 58a68f1bbd
[ty] Fall back to `Divergent` for deeply nested specializations (#20988)
## Summary

Fall back to `C[Divergent]` if we are trying to specialize `C[T]` with a
type that itself already contains deeply nested specialized generic
classes. This is a way to prevent infinite recursion for cases like
`self.x = [self.x]` where type inference for the implicit instance
attribute would not converge.

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

## Test Plan

Regression tests.
2025-10-22 14:29:10 +02:00
David Peter 2dbca6370b
[ty] Avoid ever-growing default types (#20991)
## Summary

We currently panic in the seemingly rare case where the type of a
default value of a parameter depends on the callable itself:
```py
class C:
    def f(self: C):
        self.x = lambda a=self.x: a
```

Types of default values are only used for display reasons, and it's
unclear if we even want to track them (or if we should rather track the
actual value). So it didn't seem to me that we should spend a lot of
effort (and runtime) trying to achieve a theoretically correct type here
(which would be infinite).

Instead, we simply replace *nested* default types with `Unknown`, i.e.
only if the type of the default value is a callable itself.

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

## Test Plan

Regression tests
2025-10-21 19:13:36 +02:00
David Peter e1cada1ec3
[ty] Disable panicking mdtest (#21016)
## Summary

Only run the "pull types" test after performing the "actual" mdtest. We
observed that the order matters. There is currently one mdtest which
panics when checked in the CLI or the playground. With this change, it
also panics in the mdtest suite.

reopens https://github.com/astral-sh/ty/issues/837?
2025-10-21 14:40:23 +02:00
Alex Waygood 1f8297cfe6
[ty] Improve error messages for unresolved attribute diagnostics (#20963)
## Summary

- Type checkers (and type-checker authors) think in terms of types, but
I think most Python users think in terms of values. Rather than saying
that a _type_ `X` "has no attribute `foo`" (which I think sounds strange
to many users), say that "an object of type `X` has no attribute `foo`"
- Special-case certain types so that the diagnostic messages read more
like normal English: rather than saying "Type `<class 'Foo'>` has no
attribute `bar`" or "Object of type `<class 'Foo'>` has no attribute
`bar`", just say "Class `Foo` has no attribute `bar`"

## Test Plan

Mdtests and snapshots updated
2025-10-19 10:58:25 +01:00
David Peter 3c229ae58a
[ty] `dataclass_transform`: Support for fields with an `alias` (#20961)
## Summary

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

## Conformance tests

Two false positives removed, as expected.

## Test Plan

New Markdown tests
2025-10-18 18:20:39 +00:00
Alex Waygood 68c1fa86c8
[ty] Fix panic when attempting to validate the members of a protocol that inherits from a protocol in another module (#20956) 2025-10-18 15:01:46 +01:00
Alex Waygood 16efe53a72
[ty] Fix panic on recursive class definitions in a stub that use constrained type variables (#20955) 2025-10-18 13:02:55 +00:00
Micha Reiser 7532155c9b
[ty] Add suggestion to unknown rule diagnostics, rename `unknown-rule` lint to `ignore-comment-unknown-rule` (#20948) 2025-10-18 12:44:21 +02:00
Bhuminjay Soni 7198e53182
[syntax-errors] Alternative `match` patterns bind different names (#20682)
<!--
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

<!-- What's the purpose of the change? What does it do, and why? -->
This PR implements semantic syntax error where alternative patterns bind
different names

## Test Plan

<!-- How was it tested? -->
I have written inline tests as directed in #17412

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-17 21:35:48 +00:00
Shunsuke Shibayama e4384fc212
[ty] impl `VarianceInferable` for `KnownInstanceType` (#20924)
## Summary

Derived from #20900

Implement `VarianceInferable` for `KnownInstanceType` (especially for
`KnownInstanceType::TypeAliasType`).

The variance of a type alias matches its value type. In normal usage,
type aliases are expanded to value types, so the variance of a type
alias can be obtained without implementing this. However, for example,
if we want to display the variance when hovering over a type alias, we
need to be able to obtain the variance of the type alias itself (cf.
#20900).

## Test Plan

I couldn't come up with a way to test this in mdtest, so I'm testing it
in a test submodule at the end of `types.rs`.
I also added a test to `mdtest/generics/pep695/variance.md`, but it
passes without the changes in this PR.
2025-10-17 21:12:19 +02:00
Alex Waygood c7e2bfd759
[ty] `continue` and `break` statements outside loops are syntax errors (#20944)
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-17 17:13:40 +00:00
David Peter cfbd42c22a
[ty] Support `dataclass_transform` for base class models (#20783)
## Summary

Support `dataclass_transform` when used on a (base) class.

## Typing conformance

* The changes in `dataclasses_transform_class.py` look good, just a few
mistakes due to missing `alias` support.
* I didn't look closely at the changes in
`dataclasses_transform_converter.py` since we don't support `converter`
yet.

## Ecosystem impact

The impact looks huge, but it's concentrated on a single project (ibis).
Their setup looks more or less like this:

* the real `Annotatable`:
d7083c2c96/ibis/common/grounds.py (L100-L101)
* the real `DataType`:
d7083c2c96/ibis/expr/datatypes/core.py (L161-L179)
* the real `Array`:
d7083c2c96/ibis/expr/datatypes/core.py (L1003-L1006)


```py
from typing import dataclass_transform

@dataclass_transform()
class Annotatable:
    pass

class DataType(Annotatable):
    nullable: bool = True

class Array[T](DataType):
    value_type: T
```

They expect something like `Array([1, 2])` to work, but ty, pyright,
mypy, and pyrefly would all expect there to be a first argument for the
`nullable` field on `DataType`. I don't really understand on what
grounds they expect the `nullable` field to be excluded from the
signature, but this seems to be the main reason for the new diagnostics
here. Not sure if related, but it looks like their typing setup is not
really complete
(https://github.com/ibis-project/ibis/issues/6844#issuecomment-1868274770,
this thread also mentions `dataclass_transform`).

## Test Plan

Update pre-existing tests.
2025-10-17 14:04:31 +02:00
Mark Z. Ding fc3b341529
[ty] Truncate Literal type display in some situations (#20928) 2025-10-17 11:50:58 +00:00
Aria Desires 64edfb6ef6
[ty] add legacy namespace package support (#20897)
Detect legacy namespace packages and treat them like namespace packages
when looking them up as the *parent* of the module we're interested in.
In all other cases treat them like a regular package.

(This PR is coauthored by @MichaReiser in a shared coding session)

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

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-17 03:16:37 +00:00
Ibraheem Ahmed 96b156303b
[ty] Prefer declared type for invariant collection literals (#20927)
## Summary

Prefer the declared type for collection literals, e.g.,
```py
x: list[Any] = [1, "2", (3,)]
reveal_type(x)  # list[Any]
```

This solves a large part of https://github.com/astral-sh/ty/issues/136
for invariant generics, where respecting the declared type is a lot more
important. It also means that annotated dict literals with `dict[_,
Any]` is a way out of https://github.com/astral-sh/ty/issues/1248.
2025-10-16 16:11:28 -04:00
Ibraheem Ahmed 25023cc0ea
[ty] Use declared variable types as bidirectional type context (#20796)
## Summary

Use the declared type of variables as type context for the RHS of assignment expressions, e.g.,
```py
x: list[int | str]
x = [1]
reveal_type(x)  # revealed: list[int | str]
```
2025-10-16 15:40:39 -04:00
Ibraheem Ahmed 1ade4f2081
[ty] Avoid unnecessarily widening generic specializations (#20875)
## Summary

Ignore the type context when specializing a generic call if it leads to
an unnecessarily wide return type. For example, [the example mentioned
here](https://github.com/astral-sh/ruff/pull/20796#issuecomment-3403319536)
works as expected after this change:
```py
def id[T](x: T) -> T:
    return x

def _(i: int):
    x: int | None = id(i)
    y: int | None = i
    reveal_type(x)  # revealed: int
    reveal_type(y)  # revealed: int
```

I also added extended our usage of `filter_disjoint_elements` to tuple
and typed-dict inference, which resolves
https://github.com/astral-sh/ty/issues/1266.
2025-10-16 19:17:37 +00:00
David Peter 8dad58de37
[ty] Support dataclass-transform `field_specifiers` (#20888)
## Summary

Add support for the `field_specifiers` parameter on
`dataclass_transform` decorator calls.

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

## Conformance test results

All true positives ✔️ 

## Ecosystem analysis

* `trio`: this is the kind of change that I would expect from this PR.
The code makes use of a dataclass `Outcome` with a `_unwrapped: bool =
attr.ib(default=False, eq=False, init=False)` field that is excluded
from the `__init__` signature, so we now see a bunch of
constructor-call-related errors going away.
* `home-assistant/core`: They have a `domain: str = attr.ib(init=False,
repr=False)` field and then use
  ```py
    @domain.default
    def _domain_default(self) -> str:
        # …
  ```
This accesses the `default` attribute on `dataclasses.Field[…]` with a
type of `default: _T | Literal[_MISSING_TYPE.MISSING]`, so we get those
"Object of type `_MISSING_TYPE` is not callable" errors. I don't really
understand how that is supposed to work. Even if `_MISSING_TYPE` would
be absent from that union, what does this try to call? pyright also
issues an error and it doesn't seem to work at runtime? So this looks
like a true positive?
* `attrs`: Similar here. There are some new diagnostics on code that
tries to access `.validator` on a field. This *does* work at runtime,
but I'm not sure how that is supposed to type-check (without a [custom
plugin](2c6c395935/mypy/plugins/attrs.py (L575-L602))).
pyright errors on this as well.
* A handful of new false positives because we don't support `alias` yet

## Test Plan

Updated tests.
2025-10-16 20:49:11 +02:00
Brent Westbrook e64d772788
Standardize syntax error construction (#20903)
Summary
--

This PR unifies the two different ways Ruff and ty construct syntax
errors. Ruff has been storing the primary message in the diagnostic
itself, while ty attached the message to the primary annotation:

```
> ruff check try.py
invalid-syntax: name capture `x` makes remaining patterns unreachable
 --> try.py:2:10
  |
1 | match 42:
2 |     case x: ...
  |          ^
3 |     case y: ...
  |

Found 1 error.
> uvx ty check try.py
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
Checking ------------------------------------------------------------ 1/1 files                                                                                                 
error[invalid-syntax]
 --> try.py:2:10
  |
1 | match 42:
2 |     case x: ...
  |          ^ name capture `x` makes remaining patterns unreachable
3 |     case y: ...
  |

Found 1 diagnostic
```

I think there are benefits to both approaches, and I do like ty's
version, but I feel like we should pick one (and it might help with
#20901 eventually). I slightly prefer Ruff's version, so I went with
that. Hopefully this isn't too controversial, but I'm happy to close
this if it is.

Note that this shouldn't change any other diagnostic formats in ty
because
[`Diagnostic::primary_message`](98d27c4128/crates/ruff_db/src/diagnostic/mod.rs (L177))
was already falling back to the primary annotation message if the
diagnostic message was empty. As a result, I think this change will
partially resolve the FIXME therein.

Test Plan
--

Existing tests with updated snapshots
2025-10-16 11:56:32 -04:00
Aria Desires 7155a62e5c
[ty] Add version hint for failed stdlib attribute accesses (#20909)
This is the ultra-minimal implementation of

* https://github.com/astral-sh/ty/issues/296

that was previously discussed as a good starting point. In particular we
don't actually bother trying to figure out the exact python versions,
but we still mention "hey btw for No Reason At All... you're on python
3.10" when you try to access something that has a definition rooted in
the stdlib that we believe exists sometimes.
2025-10-16 14:07:33 +00:00
Aria Desires 6a1e91ce97
[ty] Check typeshed VERSIONS for parent modules when reporting failed stdlib imports (#20908)
This is a drive-by improvement that I stumbled backwards into while
looking into

* https://github.com/astral-sh/ty/issues/296

I was writing some simple tests for "thing not in old version of stdlib"
diagnostics and checked what was added in 3.14, and saw
`compression.zstd` and to my surprise discovered that `import
compression.zstd` and `from compression import zstd` had completely
different quality diagnostics.

This is because `compression` and `compression.zstd` were *both*
introduced in 3.14, and so per VERSIONS policy only an entry for
`compression` was added, and so we don't actually have any definite info
on `compression.zstd` and give up on producing a diagnostic. However the
`from compression import zstd` form fails on looking up `compression`
and we *do* have an exact match for that, so it gets a better
diagnostic!

(aside: I have now learned about the VERSIONS format and I *really* wish
they would just enumerate all the submodules but, oh well!)

The fix is, when handling an import failure, if we fail to find an exact
match *we requery with the parent module*. In cases like
`compression.zstd` this lets us at least identify that, hey, not even
`compression` exists, and luckily that fixes the whole issue. In cases
where the parent module and submodule were introduced at different times
then we may discover that the parent module is in-range and that's fine,
we don't produce the richer stdlib diagnostic.
2025-10-16 13:25:08 +00:00
Carl Meyer d23826ce46
[ty] cache Type::is_redundant_with (#20477)
Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-10-16 13:46:56 +02:00
Micha Reiser 5fb142374d
Fix run-away for mutually referential instance attributes (#20645) 2025-10-16 13:24:41 +02:00
David Peter c8133104e8
[ty] Use field-specifier return type as the default type for the field (#20915)
## Summary

`dataclasses.field` and field-specifier functions of commonly used
libraries like `pydantic`, `attrs`, and `SQLAlchemy` all return the
default type for the field (or `Any`) instead of an actual `Field`
instance, even if this is not what happens at runtime. Let's make use of
this fact and assume that *all* field specifiers return the type of the
default value of the field.

For standard dataclasses, this leads to more or less the same outcome
(see test diff for details), but this change is important for 3rd party
dataclass-transformers.

## Test Plan

Tested the consequences of this change on the field-specifiers branch as
well.
2025-10-16 13:13:45 +02:00
David Peter 0cc663efcd
[ty] Do not assume that `field`s have a default value (#20914)
## Summary

fixes https://github.com/astral-sh/ty/issues/1366

## Test Plan

Added regression test
2025-10-16 12:49:24 +02:00
Eric Mark Martin c9dfb51f49
[ty] Fix match pattern value narrowing to use equality semantics (#20882)
## Summary

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

Fix match statement value patterns to use equality comparison semantics
instead of incorrectly narrowing to literal types directly. Value
patterns use equality for matching, and equality can be overridden, so
we can't always narrow to the matched literal.

## Test Plan

Updated match.md with corrected expected types and an additional example
with explanation

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-10-16 07:50:32 +00:00
Alex Waygood fd568f0221
[ty] Heterogeneous unpacking support for unions (#20377) 2025-10-15 19:30:03 +01:00
github-actions[bot] cafb96aa7a
[ty] Sync vendored typeshed stubs (#20876)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
Co-authored-by: David Peter <mail@david-peter.de>
2025-10-15 11:13:32 +02:00
Alex Waygood 43eddc566f
[ty] Improve and extend tests for instance attributes redeclared in subclasses (#20866)
Part of https://github.com/astral-sh/ty/issues/1345
2025-10-14 19:31:34 +01:00
Alex Waygood 9090aead0f
[ty] Fix further issues in `super()` inference logic (#20843) 2025-10-14 12:48:47 +00:00
David Peter 6341bb7403
[ty] Treat `Callable` dunder members as bound method descriptors (#20860)
## Summary

Dunder methods (at least the ones defined in the standard library)
always take an instance of the class as the first parameter. So it seems
reasonable to generally treat them as bound method descriptors if they
are defined via a `Callable` type.

This removes just a few false positives from the ecosystem, but solves
three user-reported issues:

closes https://github.com/astral-sh/ty/issues/908
closes https://github.com/astral-sh/ty/issues/1143
closes https://github.com/astral-sh/ty/issues/1209

In addition to the change here, I also considered [making `ClassVar`s
bound method descriptors](https://github.com/astral-sh/ruff/pull/20861).
However, there was zero ecosystem impact. So I think we can also close
https://github.com/astral-sh/ty/issues/491 with this PR.

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

## Test Plan

Added regression test
2025-10-14 14:27:52 +02:00
David Peter ac2c530377
[ty] Handle decorators which return unions of `Callable`s (#20858)
## Summary

If a function is decorated with a decorator that returns a union of
`Callable`s, also treat it as a union of function-like `Callable`s.

Labeling as `internal`, since the previous change has not been released
yet.

## Test Plan

New regression test.
2025-10-14 09:47:50 +00:00
Douglas Creager aba0bd568e
[ty] Diagnostic for generic classes that reference typevars in enclosing scope (#20822)
Generic classes are not allowed to bind or reference a typevar from an
enclosing scope:

```py
def f[T](x: T, y: T) -> None:
    class Ok[S]: ...
    # error: [invalid-generic-class]
    class Bad1[T]: ...
    # error: [invalid-generic-class]
    class Bad2(Iterable[T]): ...

class C[T]:
    class Ok1[S]: ...
    # error: [invalid-generic-class]
    class Bad1[T]: ...
    # error: [invalid-generic-class]
    class Bad2(Iterable[T]): ...
```

It does not matter if the class uses PEP 695 or legacy syntax. It does
not matter if the enclosing scope is a generic class or function. The
generic class cannot even _reference_ an enclosing typevar in its base
class list.

This PR adds diagnostics for these cases.

In addition, the PR adds better fallback behavior for generic classes
that violate this rule: any enclosing typevars are not included in the
class's generic context. (That ensures that we don't inadvertently try
to infer specializations for those typevars in places where we
shouldn't.) The `dulwich` ecosystem project has [examples of
this](d912eaaffd/dulwich/config.py (L251))
that were causing new false positives on #20677.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-13 19:30:49 -04:00
David Peter 4b8e278a88
[ty] Treat `Callable`s as bound-method descriptors in special cases (#20802)
## Summary

Treat `Callable`s as bound-method descriptors if `Callable` is the
return type of a decorator that is applied to a function definition. See
the [rendered version of the new test
file](https://github.com/astral-sh/ruff/blob/david/callables-as-descriptors/crates/ty_python_semantic/resources/mdtest/call/callables_as_descriptors.md)
for the full description of this new heuristic.

I could imagine that we want to treat `Callable`s as bound-method
descriptors in other cases as well, but this seems like a step in the
right direction. I am planning to add other "use cases" from
https://github.com/astral-sh/ty/issues/491 to this test suite.

partially addresses https://github.com/astral-sh/ty/issues/491
closes https://github.com/astral-sh/ty/issues/1333

## Ecosystem impact

All positive

* 2961 removed `unsupported-operator` diagnostics on `sympy`, which was
one of the main motivations for implementing this change
* 37 removed `missing-argument` diagnostics, and no added call-error
diagnostics, which is an indicator that this heuristic shouldn't cause
many false positives
* A few removed `possibly-missing-attribute` diagnostics when accessing
attributes like `__name__` on decorated functions. The two added
`unused-ignore-comment` diagnostics are also cases of this.
* One new `invalid-assignment` diagnostic on `dd-trace-py`, which looks
suspicious, but only because our `invalid-assignment` diagnostics are
not great. This is actually a "Implicit shadowing of function"
diagnostic that hides behind the `invalid-assignment` diagnostic,
because a module-global function is being patched through a
`module.func` attribute assignment.

## Test Plan

New Markdown tests.
2025-10-13 21:17:47 +02:00
David Peter d912f13661
[ty] Do not bind self to non-positional parameters (#20850)
## Summary

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

## Test Plan

Regression test
2025-10-13 20:44:27 +02:00
David Peter 195e8f0684
[ty] Treat functions, methods, and dynamic types as function-like `Callable`s (#20842)
## Summary

Treat functions, methods, and dynamic types as function-like `Callable`s

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

## Ecosystem analysis

All removed diagnostics look like cases of
https://github.com/astral-sh/ty/issues/1344

## Test Plan

Added regression test
2025-10-13 15:21:55 +02:00
Alex Waygood d83d7a0dcd
[ty] Fix false-positive diagnostics on `super()` calls (#20814) 2025-10-13 10:57:46 +00:00
David Peter 9b9c9ae092
[ty] Prefer declared base class attribute over inferred attribute on subclass (#20764)
## Summary

When accessing an (instance) attribute on a given class, we were
previously traversing its MRO, and building a union of types (if the
attribute was available on multiple classes in the MRO) until we found a
*definitely bound* symbol. The idea was that possibly unbound symbols in
a subclass might only partially shadow the underlying base class
attribute.

This behavior was problematic for two reasons:
* if the attribute was definitely bound on a class (e.g. `self.x =
None`), we would have stopped iterating, even if there might be a `x:
str | None` declaration in a base class (the bug reported in
https://github.com/astral-sh/ty/issues/1067).
* if the attribute originated from an implicit instance attribute
assignment (e.g. `self.x = 1` in method `Sub.foo`), we might stop
looking and miss another implicit instance attribute assignment in a
base class method (e.g. `self.x = 2` in method `Base.bar`).

With this fix, we still iterate the MRO of the class, but we only stop
iterating if we find a *definitely declared* symbol. In this case, we
only return the declared attribute type. Otherwise, we keep building a
union of inferred attribute types.

The implementation here seemed to be the easiest fix for
https://github.com/astral-sh/ty/issues/1067 that also kept the ecosystem
impact low (the changes that I see all look correct). However, as the
Markdown tests show, there are other things to fix in this area. For
example, we should do a similar thing for *class attributes*. This is
more involved, though (affects many different areas and probably
involves a change to our descriptor protocol implementation), so I'd
like to postpone this to a follow-up.

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

## Test Plan

Updated Markdown tests, including a regression test for
https://github.com/astral-sh/ty/issues/1067.
2025-10-13 09:28:57 +02:00
Alex Waygood 7064c38e53
[ty] Filter out `revealed-type` and `undefined-reveal` diagnostics from mdtest snapshots (#20820) 2025-10-12 18:39:32 +00:00
Shunsuke Shibayama dc64c08633
[ty] bidirectional type inference using function return type annotations (#20528)
## Summary

Implements bidirectional type inference using function return type
annotations.

This PR was originally proposed to solve astral-sh/ty#1167, but this
does not fully resolve it on its own.
Additionally, I believe we need to allow dataclasses to generate their
own `__new__` methods, [use constructor return types ​​for
inference](5844c0103d/crates/ty_python_semantic/src/types.rs (L5326-L5328)),
and a mechanism to discard type narrowing like `& ~AlwaysFalsy` if
necessary (at a more general level than this PR).

## Test Plan

`mdtest/bidirectional.md` is added.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Ibraheem Ahmed <ibraheem@ibraheem.ca>
2025-10-11 00:38:35 +00:00
Shunsuke Shibayama 11a9e7ee44
[ty] use type context more aggressively to infer values ​​when constructing a `TypedDict` (#20806)
## Summary

Based on @ibraheemdev's comment on #20792:

> I think we can also update our bidirectional inference code, [which
makes the same
assumption](https://github.com/astral-sh/ruff/blob/main/crates/ty_python_semantic/src/types/infer/builder.rs?rgh-link-date=2025-10-09T21%3A30%3A31Z#L5860).

This PR also adds more test cases for how `TypedDict` annotations affect
generic call inference.

## Test Plan

New tests in `typed_dict.md`
2025-10-10 16:51:16 -07:00
David Peter ae83a1fd2d
[ty] Additional tests for `dataclass_transform` (class-level overwrites, `field_specifiers`) (#20788)
## Summary

Adds a set of basic new tests corresponding to open points in
https://github.com/astral-sh/ty/issues/1327, to document the state of
support for `dataclass_transform`.
2025-10-10 11:22:06 +00:00
Alex Waygood 44807c4a05
[ty] Better implementation of assignability for intersections with negated gradual elements (#20773) 2025-10-10 11:10:17 +00:00
David Peter 69f9182033
[ty] Annotations are deferred by default for 3.14+ (#20799)
## Summary

Type annotations are deferred by default starting with Python 3.14. No
`from __future__ import annotations` import is necessary.

## Test Plan

New Markdown test
2025-10-10 12:05:03 +02:00
David Peter 949a4f1c42
[ty] Simplify and fix `CallableTypeOf[..]` implementation (#20797)
## Summary

Simplify and fix the implementation of
`ty_extensions.CallableTypeOf[..]`.

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

## Test Plan

Added regression test.
2025-10-10 12:04:37 +02:00
Carl Meyer 8248193ed9
[ty] defer inference of legacy TypeVar bound/constraints/defaults (#20598)
## Summary

This allows us to handle self-referential bounds/constraints/defaults
without panicking.

Handles more cases from https://github.com/astral-sh/ty/issues/256

This also changes the way we infer the types of legacy TypeVars. Rather
than understanding a constructor call to `typing[_extension].TypeVar`
inside of any (arbitrarily nested) expression, and having to use a
special `assigned_to` field of the semantic index to try to best-effort
figure out what name the typevar was assigned to, we instead understand
the creation of a legacy `TypeVar` only in the supported syntactic
position (RHS of a simple un-annotated assignment with one target). In
any other position, we just infer it as creating an opaque instance of
`typing.TypeVar`. (This behavior matches all other type checkers.)

So we now special-case TypeVar creation in `TypeInferenceBuilder`, as a
special case of an assignment definition, rather than deeper inside call
binding. This does mean we re-implement slightly more of
argument-parsing, but in practice this is minimal and easy to handle
correctly.

This is easier to implement if we also make the RHS of a simple (no
unpacking) one-target assignment statement no longer a standalone
expression. Which is fine to do, because simple one-target assignments
don't need to infer the RHS more than once. This is a bonus performance
(0-3% across various projects) and significant memory-usage win, since
most assignment statements are simple one-target assignment statements,
meaning we now create many fewer standalone-expression salsa
ingredients.

This change does mean that inference of manually-constructed
`TypeAliasType` instances can no longer find its Definition in
`assigned_to`, which regresses go-to-definition for these aliases. In a
future PR, `TypeAliasType` will receive the same treatment that
`TypeVar` did in this PR (moving its special-case inference into
`TypeInferenceBuilder` and supporting it only in the correct syntactic
position, and lazily inferring its value type to support recursion),
which will also fix the go-to-definition regression. (I decided a
temporary edge-case regression is better in this case than doubling the
size of this PR.)

This PR also tightens up and fixes various aspects of the validation of
`TypeVar` creation, as seen in the tests.

We still (for now) treat all typevars as instances of `typing.TypeVar`,
even if they were created using `typing_extensions.TypeVar`. This means
we'll wrongly error on e.g. `T.__default__` on Python 3.11, even if `T`
is a `typing_extensions.TypeVar` instance at runtime. We share this
wrong behavior with both mypy and pyrefly. It will be easier to fix
after we pull in https://github.com/python/typeshed/pull/14840.

There are some issues that showed up here with typevar identity and
`MarkTypeVarsInferable`; the fix here (using the new `original` field
and `is_identical_to` methods on `BoundTypeVarInstance` and
`TypeVarInstance`) is a bit kludgy, but it can go away when we eliminate
`MarkTypeVarsInferable`.

## Test Plan

Added and updated mdtests.

### Conformance suite impact

The impact here is all positive:

* We now correctly error on a legacy TypeVar with exactly one constraint
type given.
* We now correctly error on a legacy TypeVar with both an upper bound
and constraints specified.

### Ecosystem impact

Basically none; in the setuptools case we just issue slightly different
errors on an invalid TypeVar definition, due to the modified validation
code.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-09 21:08:37 +00:00
Ibraheem Ahmed b086ffe921
[ty] Type-context aware literal promotion (#20776)
## Summary

Avoid literal promotion when a literal type annotation is provided, e.g.,
```py
x: list[Literal[1]] = [1]
```

Resolves https://github.com/astral-sh/ty/issues/1198. This does not fix
issue https://github.com/astral-sh/ty/issues/1284, but it does make it
more relevant because after this change, it is possible to directly
instantiate a generic type with a literal specialization.
2025-10-09 16:53:53 -04:00
Shunsuke Shibayama db91ac7dce
[ty] allow any string `Literal` type expression as a key when constructing a `TypedDict` (#20792) 2025-10-09 18:24:11 +00:00
David Peter 75f3c0e8e6
[ty] Respect `dataclass_transform` parameters for metaclass-based models (#20780)
## Summary

Respect parameters such as `frozen_default` for metaclass-based
`@dataclass_transformer` models.

Related to: https://github.com/astral-sh/ty/issues/1260

## Typing conformance changes

Those are all correct (new true positives)

## Test Plan

New Markdown tests
2025-10-09 13:24:20 +00:00
wangxiaolei f0d0b57900
[ty] `dataclass_transform`: Support `frozen_default` and `kw_only_default` (#20761)
## Summary

- Add support for eq, kw_only, and frozen parameter overrides in
@dataclass_transform
- Previously only order parameter override was supported
- Update test documentation to reflect fixed behavior
- Resolves issue where kw_only_default and frozen_default could not be
overridden

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

## Test Plan

New Markdown tests

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-10-09 09:34:49 +02:00
Alex Waygood f054b8a55e
[ty] Improve assignability/subtyping between two protocol types (#20368) 2025-10-08 18:37:30 +00:00
Alex Waygood b9c84add07
[ty] Disambiguate classes that live in different modules but have the same fully qualified names (#20756)
## Summary

Even disambiguating classes using their fully qualified names is not
enough for some diagnostics. We've seen real-world examples in the
ecosystem (and https://github.com/astral-sh/ruff/pull/20368 introduces
some more!) where two types can be different, but can still have the
same fully qualified name. In these cases, our disambiguation machinery
needs to print the file path and line number of the class in order to
disambiguate classes with similar names in our diagnostics.

Helps with https://github.com/astral-sh/ty/issues/1306

## Test Plan

Mdtests
2025-10-08 18:27:40 +01:00
David Peter 150ea92d03
[ty] Add tests for instance attributes in class hierarchies (#20767)
## Summary

This adds a couple of new test cases related to
https://github.com/astral-sh/ty/issues/1067 and beyond that. For now,
they are just documenting the current (problematic) behavior. Since the
topic has some subtleties, I'd like to merge this prior to the actual
bugfix(es) in order to evaluate the changes in an easier way.
2025-10-08 17:46:47 +02:00
David Peter 6b94e620fe
[ty] Fix accidental Liskov violation in protocol tests (#20763)
## Summary

We have the following test in `protocols.md`:
```py
class HasX(Protocol):
    x: int

# […]

class Foo:
    x: int

# […]

class FooBool(Foo):
    x: bool

static_assert(not is_subtype_of(FooBool, HasX))
static_assert(not is_assignable_to(FooBool, HasX))
```

If `Foo` was indeed intended to be a base class of `FooBool`, then `x:
bool` should be reported as a Liskov violation. And then it's a matter
of definition whether or not these assertions should hold true or not
(should the incorrect override take precedence or not?). So it looks to
me like this is just an oversight, probably a copy-paste error from
another test right before it, where `FooSub` is indeed intended to be a
subclass of `Foo`.

I am fixing this because this test started to fail on a branch of mine
that changes how attribute lookup in inheritance chains works.
2025-10-08 14:04:37 +02:00
Mark Z. Ding f95eb90951
[ty] Truncate type display for long unions in some situations (#20730)
## Summary

Fixes [astral-sh/ty#1307](https://github.com/astral-sh/ty/issues/1307)

Unions with length <= 5 are unaffected to minimize test churn
Unions with length > 5 will only display the first 3 elements + "...
omitted x union elements"
Here "length" is defined as the number of elements after condensation to
literals

Edit: we no longer truncate in revel case. 
Before:

> info: Attempted to call union type `(def f1() -> int) | (def f2(name:
str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) ->
int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) |
(Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`

After:

> info: Attempted to call union type `(def f1() -> int) | (def f2(name:
str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union
elements`

The below comparisons are outdated, but left here as a reference.

Before:
```reveal_type(x)  # revealed: Literal[1, 2] | A | B | C | D | E | F | G```
```reveal_type(x) # revealed: Result1A | Result1B | Result2A | Result2B
| Result3 | Result4```
After:
```reveal_type(x)  # revealed: Literal[1, 2] | A | B | ... omitted 5 union elements```
```reveal_type(x) # revealed: Result1A | Result1B | Result2A | ...
omitted 3 union elements```

This formatting is consistent with
`crates/ty_python_semantic/src/types/call/bind.rs` line 2992

## Test Plan

Cosmetic only, covered and verified by changes in mdtest
2025-10-08 11:21:26 +01:00
David Peter 1f1542db51
[ty] Use 3.14 as the default version (#20759)
## Summary

Bump the latest supported Python version of ty to 3.14 and updates some
references from 3.13 to 3.14.

This also fixes a bug with `dataclasses.field` on 3.14 (which adds a new
keyword-only parameter to that function, breaking our previously naive
matching on the parameter structure of that function).

## Test Plan

A `ty check` on a file with template strings (without any further
configuration) doesn't raise errors anymore.
2025-10-08 11:38:47 +02:00
Carl Meyer 5d3a35e071
[ty] fix implicit Self on generic class with typevar default (#20754)
## Summary

Typevar attributes (bound/constraints/default) can be either lazily
evaluated or eagerly evaluated. Currently they are lazily evaluated for
PEP 695 typevars, and eager for legacy and synthetic typevars.
https://github.com/astral-sh/ruff/pull/20598 will make them lazy also
for legacy typevars, and the ecosystem report on that PR surfaced the
issue fixed here (because legacy typevars are much more common in the
ecosystem than PEP 695 typevars.)

Applying a transform to a typevar (normalization, materialization, or
mark-inferable) will reify all lazy attributes and create a new typevar
with eager attributes. In terms of Salsa identity, this transformed
typevar will be considered different from the original typevar, whether
or not the attributes were actually transformed.

In general, this is not a problem, since all typevars in a given generic
context will be transformed, or not, together.

The exception to this was implicit-self vs explicit Self annotations.
The typevar we created for implicit self was created initially using
inferable typevars, whereas an explicit Self annotation is initially
non-inferable, then transformed via mark-inferable when accessed as part
of a function signature. If the containing class (which becomes the
upper bound of `Self`) is generic, and has e.g. a lazily-evaluated
default, then the explicit-Self annotation will reify that default in
the upper bound, and the implicit-self would not, leading them to be
treated as different typevars, and causing us to fail to solve a call to
a method such as `def method(self) -> Self` correctly.

The fix here is to treat implicit-self more like explicit-Self,
initially creating it as non-inferable and then using the mark-inferable
transform on it. This is less efficient, but restores the invariant that
all typevars in a given generic context are transformed together, or
not, fixing the bug.

In the improved-constraint-solver work, the separation of typevars into
"inferable" and "non-inferable" is expected to disappear, along with the
mark-inferable transform, which would render both this bug and the fix
moot. So this fix is really just temporary until that lands.

There is a performance regression, but not a huge one: 1-2% on most
projects, 5% on one outlier. This seems acceptable, given that it should
be fully recovered by removing the mark-inferable transform.

## Test Plan

Added mdtests that failed before this change.
2025-10-08 01:38:24 +00:00
Alex Waygood ff386b4797
[ty] Improve diagnostics for bad `@overload` definitions (#20745) 2025-10-07 21:52:57 +00:00
Douglas Creager 416e956fe0
[ty] Infer better specializations of unions with `None` (etc) (#20749)
This PR adds a specialization inference special case that lets us handle
the following examples better:

```py
def f[T](t: T | None) -> T: ...
def g[T](t: T | int | None) -> T | int: ...

def _(x: str | None):
    reveal_type(f(x))  # revealed: str (previously str | None)

def _(y: str | int | None):
    reveal_type(g(x))  # revealed: str | int (previously str | int | None)
```

We already have a special case for when the formal is a union where one
element is a typevar, but it maps the entire actual type to the typevar
(as you can see in the "previously" results above).

The new special case kicks in when the actual is also a union. Now, we
filter out any actual union elements that are already subtypes of the
formal, and only bind whatever types remain to the typevar. (The `|
None` pattern appears quite often in the ecosystem results, but it's
more general and works with any number of non-typevar union elements.)

The new constraint solver should handle this case as well, but it's
worth adding this heuristic now with the old solver because it
eliminates some false positives from the ecosystem report, and makes the
ecosystem report less noisy on the other constraint solver PRs.
2025-10-07 13:33:42 -04:00
Brent Westbrook 88c0ce3e38
Update default and latest Python versions for 3.14 (#20725)
Summary
--

Closes #19467 and also removes the warning about using Python 3.14
without
preview enabled.

I also bumped `PythonVersion::default` to 3.9 because it reaches EOL
this month,
but we could also defer that for now if we wanted.

The first three commits are related to the `latest` bump to 3.14; the
fourth commit
bumps the default to 3.10.

Note that this PR also bumps the default Python version for ty to 3.10
because
there was a test asserting that it stays in sync with
`ast::PythonVersion`.

Test Plan
--

Existing tests

I spot-checked the ecosystem report, and I believe these are all
expected. Inbits doesn't specify a target Python version, so I guess
we're applying the default. UP007, UP035, and UP045 all use the new
default value to emit new diagnostics.
2025-10-07 12:23:11 -04:00
David Peter 23ebfe7777
[ty] Fix tiny mistake in protocol tests (#20743) 2025-10-07 11:58:35 +00:00